diff --git a/zh-cn/application-dev/ui/arkts-animation.md b/zh-cn/application-dev/ui/arkts-animation.md index 80de9162b5467d2d7db93d55d99572e653a07a31..8c7bdfb5998118f5001ca65e01a85ea8a6a0431c 100644 --- a/zh-cn/application-dev/ui/arkts-animation.md +++ b/zh-cn/application-dev/ui/arkts-animation.md @@ -4,8 +4,19 @@ UI(用户界面)中包含开发者与设备进行交互时所看到的各种组件(如时间、壁纸等)。属性作为接口,用于控制组件的行为。例如,开发者可通过位置属性调整组件在屏幕上的位置。 -属性值的变化,通常会引起UI的变化。动画可在UI发生改变时,添加流畅的过渡效果。以应用启动为例,当用户点击应用图标时,应用窗口应取代桌面,作为屏幕的主要显示内容。如果不添加动画,相关属性将在一瞬间完成变化,应用窗口直接替换桌面,出现不连贯的视觉感受。动画可以解决UI变化时在视觉上不连续的问题。 +属性值的变化,通常会引起UI的变化。动画可在UI发生改变时,添加流畅的过渡效果。如果不加入动画,属性将在一瞬间完成变化。造成突兀感的同时,容易导致用户失去视觉焦点。 +![zh-cn_image_20230822](figures/zh-cn_image_20230822.gif) + +动画的目的包括: + +- 使界面的过渡自然流畅。 +- 增强用户从界面获得的反馈感和互动感。 +- 在内容加载等场景中,增加用户的耐心,缓解等待带来的不适感。 +- 引导用户了解和操作设备。 + + +在需要为UI变化添加过渡的场景,都可以使用动画,如开机、应用启动退出、下拉进入控制中心等。这些动画可向用户提供关于其操作的反馈,并有助于让用户始终关注界面。 ArkUI中提供多种动画接口(属性动画、转场动画等),用于驱动属性值按照设定的动画参数,从起始值逐渐变化到终点值。尽管变化过程中参数值并非绝对的连续,而是具有一定的离散性。但由于人眼会产生视觉暂留,所以最终看到的就是一个“连续“的动画。UI的一次改变称为一个动画帧,对应一次屏幕刷新。决定动画流畅度的一个重要指标就是帧率FPS(Frame Per Second),即每秒的动画帧数,帧率越高则动画就会越流畅。ArkUI中,动画参数包含了如动画时长、动画曲线等参数。动画曲线作为主要因素,决定了属性值变化的规律。以线性动画曲线为例,在动画时长内,属性值将从起点值匀速变化到终点值。属性过快或过慢的变化,都可能带来不好的视觉感受,影响用户体验。因此动画参数特别是动画曲线,需要结合场景和曲线特点进行设计和调整。 @@ -20,8 +31,8 @@ ArkUI中提供多种动画接口(属性动画、转场动画等),用于驱 - 属性动画:最基础的动画类型,按照动画参数逐帧驱动属性的变化,产生一帧帧的动画效果。 - 转场动画:为组件在出现和消失时添加过渡动画。为了保证动画一致性,部分接口动画曲线已内置,不支持开发者自定义。 - - 不推荐使用Ability去组合界面:Ability是一个任务,会在多任务界面独立显示一个卡片,Ability之间的跳转是任务之间的跳转。以应用内查看大图的典型场景为例,不建议应用内调用图库Ability去打开图片查看大图,会导致任务的跳转,图库Ability也会加入多任务界面中。正确的方式是应用内构建大图组件,通过模态转场去调起大图组件,所有的界面都在一个Ability内闭环。 - - 使用Navigation组件实现导航,不要使用page导航方式。page+router方式会导致页面之间的割裂,不利于实现联动的转场效果,并且不支持一次开发多端部署。 + - 不推荐在应用内使用UIAbility组合所有的界面:UIAbility是一个任务,会在多任务界面独立显示一个卡片,UIAbility之间的跳转是任务之间的跳转。以应用内查看大图的典型场景为例,不建议应用内调用图库的UIAbility去打开图片查看大图,会导致任务的跳转,图库的UIAbility也会加入多任务界面中。正确的方式是应用内构建大图组件,通过模态转场去调起大图组件,一个任务内的所有的界面都在一个UIAbility内闭环。 + - 导航转场中,应使用Navigation组件实现转场动画。过去的page+router方式在实现导航转场过程中,因为page和page之间相互独立,其联动动画效果受限。不仅容易导致页面之间的割裂,并且不支持一次开发多端部署。 - 组件动画:组件提供默认动效(如List的滑动动效)便于开发者使用,同时部分组件还支持定制化动效。 diff --git a/zh-cn/application-dev/ui/arkts-component-animation.md b/zh-cn/application-dev/ui/arkts-component-animation.md index dcce81d8180d68035cbfc220571be840df0e73c6..831132f9b4f194a7f1ebadce6e4f1d0a2c4ce069 100644 --- a/zh-cn/application-dev/ui/arkts-component-animation.md +++ b/zh-cn/application-dev/ui/arkts-component-animation.md @@ -224,7 +224,6 @@ export const taskDataArr: Array = export struct TaskSwitchMainPage { displayWidth: number = WindowManager.getInstance().getDisplayWidth(); scroller: Scroller = new Scroller(); - bgImage: Resource = $r('app.media.share'); cardSpace: number = 0; // 卡片间距 cardWidth: number = this.displayWidth / 2 - this.cardSpace / 2; // 卡片宽度 cardHeight: number = 400; // 卡片高度 diff --git a/zh-cn/application-dev/ui/arkts-enter-exit-transition.md b/zh-cn/application-dev/ui/arkts-enter-exit-transition.md index 1077e0de31361cafa07788b85ffc4ab14447cdb8..0d7f6e774940a5b8b8d886bcd0a7e1f08ab87a80 100644 --- a/zh-cn/application-dev/ui/arkts-enter-exit-transition.md +++ b/zh-cn/application-dev/ui/arkts-enter-exit-transition.md @@ -67,7 +67,7 @@ ``` - 完整的示例代码和效果如下。 + 完整的示例代码和效果如下: ```ts import curves from '@ohos.curves'; diff --git a/zh-cn/application-dev/ui/arkts-navigation-transition.md b/zh-cn/application-dev/ui/arkts-navigation-transition.md index 9dc638b55139e336dc4732705aadfdd3496a968a..9001828b0b4ba8d14fa1e988c71e5628991dac1d 100644 --- a/zh-cn/application-dev/ui/arkts-navigation-transition.md +++ b/zh-cn/application-dev/ui/arkts-navigation-transition.md @@ -12,13 +12,81 @@ ```ts +@Component +export struct MyFirstIndex { + @Consume('pathInfos') pathInfos: NavPathStack + name: string = '' + @State value: string = '' + + build() { + NavDestination() { + Column() { + Blank() + Text('通过点击NavRouter区域进入导航目标页面' + this.value) + .fontStyle(FontStyle.Italic) + .lineHeight(35) + .fontSize(25) + .fontColor(Color.Black) + .textAlign(TextAlign.Center) + .letterSpacing(5) + .textShadow({ radius: 2, offsetX: 4, offsetY: 4, color: 0x909399 }) + .padding({ left: 30, right: 30 }) + + Blank() + + Button('返回上级页面') + .backgroundColor(Color.Black) + .onClick(() => { + this.pathInfos.pop() + }) + Blank() + } + .size({ width: '100%', height: '100%' }) + }.title(this.name + '二级页面') + } +} + +@Component +export struct MySecondIndex { + @Consume('pathInfos') pathInfos: NavPathStack; + name: String = ''; + @State value: String = '' + + build() { + NavDestination() { + Column() { + Blank() + Text('通过更新路由栈数据对象进入导航目标界面' + this.value) + .fontStyle(FontStyle.Italic) + .lineHeight(35) + .fontSize(25) + .fontColor(Color.Black) + .textAlign(TextAlign.Center) + .letterSpacing(5) + .textShadow({ radius: 2, offsetX: 4, offsetY: 4, color: 0x909399 }) + .padding({ left: 30, right: 30 }) + + Blank() + + Button('返回上级页面') + .backgroundColor(Color.Black) + .onClick(() => { + this.pathInfos.pop() + }) + Blank() + } + .size({ width: '100%', height: '100%' }) + }.title(this.name + '二级页面') + } +} + @Entry @Component struct NavigationDemo { - private listArray: Array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + @Provide('pathInfos') pathInfos: NavPathStack = new NavPathStack() + private listArray: Array = [0, 1, 2] - // 设置标题栏菜单组件,如果不需要标题栏组件,可以不设置 - @Builder NavigationMenus() { + @Builder NavPathStack() { Column() { Text('menu') .fontColor('#182431') @@ -30,102 +98,103 @@ struct NavigationDemo { .alignItems(HorizontalAlign.Start) } + // Navigation的navDestination属性方法设置的构造函数,在路由栈变化时触发该构建数创建新的路由页面 + @Builder myRouter(name: string, param: string) { + if (name == '方式一进入') { + MyFirstIndex({ name: name, value: param }) + } + if (name == '方式二进入') { + MySecondIndex({ name: name, value: param }) + } + } + build() { - Stack() { - Column() { - // 定义Navigation组件,设置显示模式,设置标题 - Navigation() { - // 这里定义了一个输入法框组件 - TextInput({ placeholder: 'search...' }) - .width('90%') - .height(40) - .backgroundColor('#ededed') - .margin({ bottom: 10 }) - - // 通过List定义导航的一级界面 - List({ space: 12, initialIndex: 0 }) { - ForEach(this.listArray, (item) => { - ListItem() { - // 通过NavRouter定义导航转场,通过NavDestination定义导航目标界面,界面之间通过组件间的状态变量或者普通变量传递参数 - // NavRouter必须包含两个子组件,第一个组件是导航一级界面,第二个子组件必须为NavDestination为导航目标界面 - NavRouter() { - // 第一个组件:导航的一级界面显示的组件 + Column() { + Navigation(this.pathInfos) { + TextInput({ placeholder: 'search...' }) + .width('90%') + .height(40) + .margin({ bottom: 10 }) + + // 通过List定义导航的一级界面 + List({ space: 12, initialIndex: 0 }) { + ForEach(this.listArray, (item) => { + ListItem() { + // 通过NavDestination定义导航目标界面,界面之间同故宫组件间的状态变量或者普通变量传递参数 + // NavRouter点击之后会传递name和param参数给Navigation的navDestination书香方法设置的builder函数(myRouter) + NavRouter({ name: '方式一进入', param: '' + item }) { + Row() { Row() { - Row() - .width(40) - .height(40) - .backgroundColor('#a8a8a8') - .margin({ right: 12 }) - .borderRadius(20) - - Column() { - Text('导航一级页面') - .fontSize(16) - .lineHeight(21) - .fontWeight(FontWeight.Medium) - Text('点击跳转目标子页面') - .fontSize(13) - .lineHeight(21) - .fontColor('#a8a8a8') - } - .alignItems(HorizontalAlign.Start) - - Blank() - - Row() - .width(15) - .height(15) - .margin({ right: 12 }) - .border({ - width: { top: 2, right: 2 }, - color: 0xcccccc - }) - .rotate({ angle: 45 }) + Text('' + item) + .fontColor(Color.White) + .fontSize(15) + .fontWeight(FontWeight.Bold) } - .borderRadius(15) - .shadow({ radius: 100, color: '#ededed' }) - .width('90%') - .alignItems(VerticalAlign.Center) - .padding({ left: 16, top: 12, bottom: 12 }) - .height(80) - - // 第二个组件:导航的目的界面 - NavDestination() { - // 目的界面的内容,这里一般为自定义的Component - Column() { - Text("导航目标页面" + item + "内容") - .fontSize(20) - .fontColor(Color.Black) - .textAlign(TextAlign.Center) - .width('100%') - .height('100%') - } - .width('100%') - .height('100%') - .backgroundColor(0xf5f5f5) + .width(40) + .height(40) + .backgroundColor('#a8a8a8') + .margin({ right: 12 }) + .borderRadius(20) + .justifyContent(FlexAlign.Center) + + Column() { + Text('导航一级页面') + .fontSize(16) + .lineHeight(21) + .fontWeight(FontWeight.Medium) + Text('点击跳转目标子页面' + item) + .fontSize(13) + .lineHeight(21) + .fontColor('#a8a8a8') } - .title('导航目标页面') // 这里定义二级界面的标题 + .alignItems(HorizontalAlign.Start) + + Blank() + + Row() + .width(15) + .height(15) + .margin({ right: 12 }) + .border({ + width: { top: 2, right: 2 }, + color: 0xcccccc + }) + .rotate({ angle: 45 }) } + .borderRadius(15) + .shadow({ radius: 100, color: '#ededed' }) + .width('90%') + .alignItems(VerticalAlign.Center) + .padding({ left: 16, top: 12, bottom: 12 }) + .height(80) + .backgroundColor(Color.White) } - .width('100%') - }, item => item) - } - .listDirection(Axis.Vertical) - .edgeEffect(EdgeEffect.Spring) - .sticky(StickyStyle.Header) - .chainAnimation(false) - .borderRadius(15) - .width('100%') - .height('100%') + } + .width('100%') + }, item => item) } + .listDirection(Axis.Vertical) + .edgeEffect(EdgeEffect.Spring) + .sticky(StickyStyle.Header) + .chainAnimation(false) .width('100%') - .mode(NavigationMode.Auto) // 设置显示模式为Auto - .title('导航转场') // 设置标题文字 - .titleMode(NavigationTitleMode.Full) // 设置标题栏模式 - .menus(this.NavigationMenus) // 设置标题栏菜单 + + Column() + .height('20%') + + Button('点击进入下一页') + .onClick(() => { + // 通过操作绑定的路由栈数据对象触发Navigation更新,基于pathInfos数据变化场景触发navDestination属性方法构建新页面 + this.pathInfos.pushPathByName('方式二进入', 4) + }) } + .navDestination(this.myRouter) .width('100%') + .mode(NavigationMode.Auto) + .title('导航转场') // 设置标题文字 } + .size({ width: '100%', height: '100%' }) + .backgroundColor(0xf4f4f5) } } ``` diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001588458252.gif b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001588458252.gif index cf42cf73d80c25d5907aa105a5763ad9dc2cee2d..e1f95fe85496ec749ec080b6d4ff1898ff66f236 100644 Binary files a/zh-cn/application-dev/ui/figures/zh-cn_image_0000001588458252.gif and b/zh-cn/application-dev/ui/figures/zh-cn_image_0000001588458252.gif differ diff --git a/zh-cn/application-dev/ui/figures/zh-cn_image_20230822.gif b/zh-cn/application-dev/ui/figures/zh-cn_image_20230822.gif new file mode 100644 index 0000000000000000000000000000000000000000..f2be85c6d1f7deddaf8bc6081d7b715b7c222ba2 Binary files /dev/null and b/zh-cn/application-dev/ui/figures/zh-cn_image_20230822.gif differ