属性动画
animateTo
1 |
animateTo(value: AnimateParam, event: () => void): void |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
import { curves } from '@kit.ArkUI'; @Entry @Component struct AnimateToDemo { @State animate: boolean = false; // 第一步: 声明相关状态变量 @State rotateValue: number = 0; // 组件一旋转角度 @State translateX: number = 0; // 组件二偏移量 @State opacityValue: number = 1; // 组件二透明度 // 第二步:将状态变量设置到相关可动画属性接口 build() { Row() { // 组件一 Column() { } .rotate({ angle: this.rotateValue }) .backgroundColor('#317AF7') .justifyContent(FlexAlign.Center) .width(100) .height(100) .borderRadius(30) .onClick(() => { this.getUIContext()?.animateTo({ curve: curves.springMotion() }, () => { this.animate = !this.animate; // 第三步:闭包内通过状态变量改变UI界面 // 这里可以写任何能改变UI的逻辑比如数组添加,显隐控制,系统会检测改变后的UI界面与之前的UI界面的差异,对有差异的部分添加动画 // 组件一的rotate属性发生变化,所以会给组件一添加rotate旋转动画 this.rotateValue = this.animate ? 90 : 0; // 组件二的透明度发生变化,所以会给组件二添加透明度的动画 this.opacityValue = this.animate ? 0.6 : 1; // 组件二的translate属性发生变化,所以会给组件二添加translate偏移动画 this.translateX = this.animate ? 50 : 0; }) }) // 组件二 Column() { } .justifyContent(FlexAlign.Center) .width(100) .height(100) .backgroundColor('#D94838') .borderRadius(30) .opacity(this.opacityValue) .translate({ x: this.translateX }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import { curves } from '@kit.ArkUI' @Entry @Component struct Index { @State animate: boolean = false @State rotateValue: number = 0 @State translateX: number = 0 @State opacityValue: number = 1 build() { Column() { Row() .width(100) .height(100) .borderRadius(30) .backgroundColor('#317AF7') .rotate({ angle: this.rotateValue }) .onClick(() => { this.getUIContext()?.animateTo({ curve: curves.springMotion() }, () => { this.animate = !this.animate this.rotateValue = this.animate ? 90 : 0 this.translateX = this.animate ? 50 : 0 this.opacityValue = this.animate ? 0.6 : 1 }) }) Row() .width(100) .height(100) .borderRadius(30) .backgroundColor('#D94838') .opacity(this.opacityValue) .translate({ x: this.translateX }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } } |
animation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
import { curves } from '@kit.ArkUI'; @Entry @Component struct AnimationDemo { @State animate: boolean = false; // 第一步: 声明相关状态变量 @State rotateValue: number = 0; // 组件一旋转角度 @State translateX: number = 0; // 组件二偏移量 @State opacityValue: number = 1; // 组件二透明度 // 第二步:将状态变量设置到相关可动画属性接口 build() { Row() { // 组件一 Column() { } .opacity(this.opacityValue) .rotate({ angle: this.rotateValue }) // 第三步:通过属性动画接口开启属性动画 .animation({ curve: curves.springMotion() }) .backgroundColor('#317AF7') .justifyContent(FlexAlign.Center) .width(100) .height(100) .borderRadius(30) .onClick(() => { this.animate = !this.animate; // 第四步:闭包内通过状态变量改变UI界面 // 这里可以写任何能改变UI的逻辑比如数组添加,显隐控制,系统会检测改变后的UI界面与之前的UI界面的差异,对有差异的部分添加动画 // 组件一的rotate属性发生变化,所以会给组件一添加rotate旋转动画 this.rotateValue = this.animate ? 90 : 0; // 组件二的translate属性发生变化,所以会给组件二添加translate偏移动画 this.translateX = this.animate ? 50 : 0; // 父组件column的opacity属性有变化,会导致其子节点的透明度也变化,所以这里会给column和其子节点的透明度属性都加动画 this.opacityValue = this.animate ? 0.6 : 1; }) // 组件二 Column() { } .justifyContent(FlexAlign.Center) .width(100) .height(100) .backgroundColor('#D94838') .borderRadius(30) .opacity(this.opacityValue) .translate({ x: this.translateX }) .animation({ curve: curves.springMotion() }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } } |
自定义属性动画
@AnimatableExtend
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// 第一步:使用@AnimatableExtend装饰器,自定义可动画属性接口 @AnimatableExtend(Text) function animatableWidth(width: number) { .width(width) // 调用系统属性接口,逐帧回调函数每帧修改可动画属性的值,实现逐帧布局的效果。 } @Entry @Component struct AnimatablePropertyExample { @State textWidth: number = 80; build() { Column() { Text("AnimatableProperty") .animatableWidth(this.textWidth)// 第二步:将自定义可动画属性接口设置到组件上 .animation({ duration: 2000, curve: Curve.Ease }) // 第三步:为自定义可动画属性接口绑定动画 Button("Play") .onClick(() => { this.textWidth = this.textWidth == 80 ? 160 : 80; // 第四步:改变自定义可动画属性的参数,产生动画 }) } .width("100%") .height('100%') .justifyContent(FlexAlign.Center) .padding(10) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
declare type Point = number[]; // 定义可动画属性接口的参数类型,实现AnimatableArithmetic<T>接口中加法、减法、乘法和判断相等函数 class PointClass extends Array<number> { constructor(value: Point) { super(value[0], value[1]) } add(rhs: PointClass): PointClass { let result: Point = new Array<number>() as Point; for (let i = 0; i < 2; i++) { result.push(rhs[i] + this[i]) } return new PointClass(result); } subtract(rhs: PointClass): PointClass { let result: Point = new Array<number>() as Point; for (let i = 0; i < 2; i++) { result.push(this[i] - rhs[i]); } return new PointClass(result); } multiply(scale: number): PointClass { let result: Point = new Array<number>() as Point; for (let i = 0; i < 2; i++) { result.push(this[i] * scale) } return new PointClass(result); } } // 定义可动画属性接口的参数类型,实现AnimatableArithmetic<T>接口中加法、减法、乘法和判断相等函数 // 模板T支持嵌套实现AnimatableArithmetic<T>的类型 class PointVector extends Array<PointClass> implements AnimatableArithmetic<Array<Point>> { constructor(initialValue: Array<Point>) { super(); if (initialValue.length) { initialValue.forEach((p: Point) => this.push(new PointClass(p))) } } // implement the IAnimatableArithmetic interface plus(rhs: PointVector): PointVector { let result = new PointVector([]); const len = Math.min(this.length, rhs.length) for (let i = 0; i < len; i++) { result.push(this[i].add(rhs[i])) } return result; } subtract(rhs: PointVector): PointVector { let result = new PointVector([]); const len = Math.min(this.length, rhs.length) for (let i = 0; i < len; i++) { result.push(this[i].subtract(rhs[i])) } return result; } multiply(scale: number): PointVector { let result = new PointVector([]); for (let i = 0; i < this.length; i++) { result.push(this[i].multiply(scale)) } return result; } equals(rhs: PointVector): boolean { if (this.length !== rhs.length) { return false; } for (let index = 0, size = this.length; index < size; ++index) { if (this[index][0] !== rhs[index][0] || this[index][1] !== rhs[index][1]) { return false; } } return true; } } // 自定义可动画属性接口 @AnimatableExtend(Polyline) function animatablePoints(points: PointVector) { .points(points) } @Entry @Component struct AnimatedShape { squareStartPointX: number = 75; squareStartPointY: number = 25; squareWidth: number = 150; squareEndTranslateX: number = 50; squareEndTranslateY: number = 50; @State pointVec1: PointVector = new PointVector([ [this.squareStartPointX, this.squareStartPointY], [this.squareStartPointX + this.squareWidth, this.squareStartPointY], [this.squareStartPointX + this.squareWidth, this.squareStartPointY + this.squareWidth], [this.squareStartPointX, this.squareStartPointY + this.squareWidth] ]); @State pointVec2: PointVector = new PointVector([ [this.squareStartPointX + this.squareEndTranslateX, this.squareStartPointY + this.squareStartPointY], [this.squareStartPointX + this.squareWidth + this.squareEndTranslateX, this.squareStartPointY + this.squareStartPointY], [this.squareStartPointX + this.squareWidth, this.squareStartPointY + this.squareWidth], [this.squareStartPointX, this.squareStartPointY + this.squareWidth] ]); @State color: Color = Color.Green; @State fontSize: number = 20.0; @State polyline1Vec: PointVector = this.pointVec1; @State polyline2Vec: PointVector = this.pointVec2; build() { Row() { Polyline() .width(300) .height(200) .backgroundColor("#0C000000") .fill('#317AF7') .animatablePoints(this.polyline1Vec) .animation({ duration: 2000, delay: 0, curve: Curve.Ease }) .onClick(() => { if (this.polyline1Vec.equals(this.pointVec1)) { this.polyline1Vec = this.pointVec2; } else { this.polyline1Vec = this.pointVec1; } }) } .width('100%').height('100%').justifyContent(FlexAlign.Center) } } |
转场动画
出现/消失转场
单个示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
import { curves } from '@kit.ArkUI'; @Entry @Component struct TransitionEffectDemo { @State isPresent: boolean = false; // 第一步,创建TransitionEffect private effect: TransitionEffect = // 创建默认透明度转场效果,并指定了springMotion(0.6, 0.8)曲线 TransitionEffect.OPACITY.animation({ curve: curves.springMotion(0.6, 0.8) }) // 通过combine方法,这里的动画参数会跟随上面的TransitionEffect,也就是springMotion(0.6, 0.8) .combine(TransitionEffect.scale({ x: 0, y: 0 })) // 添加旋转转场效果,这里的动画参数会跟随上面带animation的TransitionEffect,也就是springMotion(0.6, 0.8) .combine(TransitionEffect.rotate({ angle: 90 })) // 添加平移转场效果,这里的动画参数使用指定的springMotion() .combine(TransitionEffect.translate({ y: 150 }).animation({ curve: curves.springMotion() })) // 添加move转场效果,这里的动画参数会跟随上面的TransitionEffect,也就是springMotion() .combine(TransitionEffect.move(TransitionEdge.END)) build() { Stack() { if (this.isPresent) { Column() { Text('ArkUI') .fontWeight(FontWeight.Bold) .fontSize(20) .fontColor(Color.White) } .justifyContent(FlexAlign.Center) .width(150) .height(150) .borderRadius(10) .backgroundColor(0xf56c6c) // 第二步:将转场效果通过transition接口设置到组件 .transition(this.effect) } // 边框 Column() .width(155) .height(155) .border({ width: 5, radius: 10, color: Color.Black }) // 第三步:新增或者删除组件触发转场,控制新增或者删除组件 Button('Click') .margin({ top: 320 }) .onClick(() => { this.isPresent = !this.isPresent; }) } .width('100%') .height('60%') } } |
多个delay示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
const ITEM_COUNTS = 9; const ITEM_COLOR = '#ED6F21'; const INTERVAL = 30; const DURATION = 300; @Entry @Component struct Index1 { @State isGridShow: boolean = false; private dataArray: number[] = new Array(ITEM_COUNTS); aboutToAppear(): void { for (let i = 0; i < ITEM_COUNTS; i++) { this.dataArray[i] = i; } } build() { Stack() { if (this.isGridShow) { Grid() { ForEach(this.dataArray, (item: number, index: number) => { GridItem() { Stack() { Text((item + 1).toString()) } .size({ width: 50, height: 50 }) .backgroundColor(ITEM_COLOR) .transition(TransitionEffect.OPACITY .combine(TransitionEffect.scale({ x: 0.5, y: 0.5 }))// 对每个方格的转场添加delay,实现组件的渐次出现消失效果 .animation({ duration: DURATION, curve: Curve.Friction, delay: INTERVAL * index })) .borderRadius(10) } // 消失时,如果不对方格的所有父控件添加转场效果,则方格的消失转场不会生效 // 此处让方格的父控件在出现消失转场时一直以0.99的透明度显示,使得方格的转场效果不受影响 .transition(TransitionEffect.opacity(0.99)) }, (item: number) => item.toString()) } .columnsTemplate('1fr 1fr 1fr') .rowsGap(15) .columnsGap(15) .size({ width: 180, height: 180 }) // 消失时,如果不对方格的所有父控件添加转场效果,则方格的消失转场不会生效 // 此处让父控件在出现消失转场时一直以0.99的透明度显示,使得方格的转场效果不受影响 .transition(TransitionEffect.opacity(0.99)) } } .size({ width: '100%', height: '100%' }) .onClick(() => { this.getUIContext()?.animateTo({ duration: DURATION + INTERVAL * (ITEM_COUNTS - 1), curve: Curve.Friction }, () => { this.isGridShow = !this.isGridShow; }) }) } } |
导航转场
PageOne.ets简单代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
// PageOne.ets @Entry @Component struct PageOne { @Provide('pathInfos') pathInfos: NavPathStack = new NavPathStack() private listArray: string[] = ['WLAN', 'Bluetooth', 'Personal Hotpot', 'Connect & Share'] build() { Column() { Navigation(this.pathInfos) { ForEach(this.listArray, (item: string) => { Text(item) .onClick(() => { this.pathInfos.pushPathByName(`${item}`, '详情页面参数') }) }) } .title('设置') .mode(NavigationMode.Auto) } } } @Builder export function MyCommonPageBuilder(name: string, param: string) { MyCommonPage({ name, value: param }) } @Component export struct MyCommonPage { pathInfos: NavPathStack = new NavPathStack() name: String = '' @State value: String = '' build() { NavDestination() { Column() { Text(`${this.name}设置页面`) Text(JSON.stringify(this.value)) Button('返回') .onClick(() => { this.pathInfos.pop() }) } } .title(`${this.name}`) .onReady((ctx: NavDestinationContext) => { this.pathInfos = ctx.pathStack }) } } |
PageOne.ets使用@Extend写法代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
@Entry @Component struct PageOne { @Provide('pathInfos') pathInfos: NavPathStack = new NavPathStack() private listArray: string[] = ['WLAN', 'Bluetooth', 'Personal Hotpot', 'Connect & Share'] build() { Column() { Navigation(this.pathInfos) { TextInput({ placeholder: '输入关键字搜索' }) .inputExtend() List({ space: 12, initialIndex: 0 }) { ForEach(this.listArray, (item: string) => { ListItem() { Row() { this.initialLetterBuilder(item.slice(0, 1)) Column() { Text(item) .fontSize(16) } .alignItems(HorizontalAlign.Center) Blank() this.rightArrowBuilder() } .listRowExtend() } .width('100%') .onClick(() => { this.pathInfos.pushPathByName(`${item}`, '详情页面参数') }) }, (item: string): string => item) } .listExtend() } .navigationExtend() } .columnExtend() } @Builder initialLetterBuilder(letter: string) { Row() { Text(letter) .fontColor(Color.White) .fontSize(14) .fontWeight(FontWeight.Bold) } .width(30) .height(30) .backgroundColor('#a8a8a8') .margin({ right: 20 }) .borderRadius(20) .justifyContent(FlexAlign.Center) } @Builder rightArrowBuilder() { Row() .width(12) .height(12) .border({ width: { top: 2, right: 2 }, color: 0xcccccc }) .rotate({ angle: 45 }) } } @Extend(Column) function columnExtend() { .size({ width: '100%', height: '100%' }) .backgroundColor(0xf4f4f5) } @Extend(Navigation) function navigationExtend() { .width('100%') .title('设置') .mode(NavigationMode.Auto) } @Extend(List) function listExtend() { .listDirection(Axis.Vertical) .edgeEffect(EdgeEffect.Spring) .sticky(StickyStyle.Header) .chainAnimation(false) .width('100%') } @Extend(Row) function listRowExtend() { .width('90%') .padding(15) .backgroundColor(Color.White) .shadow({ radius: 100, color: '#ededed' }) .borderRadius(15) .alignItems(VerticalAlign.Center) } @Extend(TextInput) function inputExtend() { .width('90%') .height(40) .margin({ bottom: 10 }) } @Builder export function MyCommonPageBuilder(name: string, param: string) { MyCommonPage({ name, value: param }) } @Component export struct MyCommonPage { pathInfos: NavPathStack = new NavPathStack() name: String = '' @State value: String = '' build() { NavDestination() { Column() { Text(`${this.name}设置页面`) .width('100%') .fontSize(20) .fontColor(0x333333) .textAlign(TextAlign.Center) .textShadow({ radius: 2, offsetX: 4, offsetY: 4, color: 0x909399 }) .padding({ top: 30 }) Text(JSON.stringify(this.value)) .width('100%') .fontSize(18) .fontColor(0x666666) .textAlign(TextAlign.Center) .padding({ top: 45 }) Button('返回') .width('50%') .height(40) .margin({ top: 50 }) .onClick(() => { this.pathInfos.pop() }) } .size({ width: '100%', height: '100%' }) } .title(`${this.name}`) .onReady((ctx: NavDestinationContext) => { this.pathInfos = ctx.pathStack }) } } |
PageOne.ets官方代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
//PageOne.ets @Entry @Component struct NavigationDemo { @Provide('pathInfos') pathInfos: NavPathStack = new NavPathStack(); private listArray: Array<string> = ['WLAN', 'Bluetooth', 'Personal Hotpot', 'Connect & Share']; build() { Column() { Navigation(this.pathInfos) { TextInput({ placeholder: '输入关键字搜索' }) .width('90%') .height(40) .margin({ bottom: 10 }) // 通过List定义导航的一级界面 List({ space: 12, initialIndex: 0 }) { ForEach(this.listArray, (item: string) => { ListItem() { Row() { Row() { Text(`${item.slice(0, 1)}`) .fontColor(Color.White) .fontSize(14) .fontWeight(FontWeight.Bold) } .width(30) .height(30) .backgroundColor('#a8a8a8') .margin({ right: 20 }) .borderRadius(20) .justifyContent(FlexAlign.Center) Column() { Text(item) .fontSize(16) .margin({ bottom: 5 }) } .alignItems(HorizontalAlign.Start) Blank() Row() .width(12) .height(12) .margin({ right: 15 }) .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: 15, top: 15, bottom: 15 }) .backgroundColor(Color.White) } .width('100%') .onClick(() => { this.pathInfos.pushPathByName(`${item}`, '详情页面参数')//将name指定的NaviDestination页面信息入栈,传递的参数为param }) }, (item: string): string => item) } .listDirection(Axis.Vertical) .edgeEffect(EdgeEffect.Spring) .sticky(StickyStyle.Header) .chainAnimation(false) .width('100%') } .width('100%') .mode(NavigationMode.Auto) .title('设置') // 设置标题文字 } .size({ width: '100%', height: '100%' }) .backgroundColor(0xf4f4f5) } } //PageOne.ets @Builder export function MyCommonPageBuilder(name: string, param: string) { MyCommonPage({ name: name, value: param }) } @Component export struct MyCommonPage { pathInfos: NavPathStack = new NavPathStack(); name: String = ''; @State value: String = ''; build() { NavDestination() { Column() { Text(`${this.name}设置页面`) .width('100%') .fontSize(20) .fontColor(0x333333) .textAlign(TextAlign.Center) .textShadow({ radius: 2, offsetX: 4, offsetY: 4, color: 0x909399 }) .padding({ top: 30 }) Text(`${JSON.stringify(this.value)}`) .width('100%') .fontSize(18) .fontColor(0x666666) .textAlign(TextAlign.Center) .padding({ top: 45 }) Button('返回') .width('50%') .height(40) .margin({ top: 50 }) .onClick(() => { //弹出路由栈栈顶元素,返回上个页面 this.pathInfos.pop(); }) } .size({ width: '100%', height: '100%' }) }.title(`${this.name}`) .onReady((ctx: NavDestinationContext) => { //NavDestinationContext获取当前所在的页面栈 this.pathInfos = ctx.pathStack; }) } } |
PageTwo.ets简单代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
//PageTwo.ets @Builder export function MySharePageBuilder(name: string, param: string) { MySharePage({ name: name }) } @Component export struct MySharePage { pathInfos: NavPathStack = new NavPathStack(); name: String = ''; private listArray: Array<string> = ['Projection', 'Print', 'VPN', 'Private DNS', 'NFC']; build() { NavDestination() { Column() { ForEach(this.listArray, (item: string) => { Text(item) .onClick(() => { this.pathInfos.pushPathByName(`${item}`, '页面设置参数') }) }, (item: string): string => item) } } .title(`${this.name}`) .onReady((ctx: NavDestinationContext) => { //NavDestinationContext获取当前所在的页面栈 this.pathInfos = ctx.pathStack; }) } } |
PageTwo.ets官方代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
//PageTwo.ets @Builder export function MySharePageBuilder(name: string, param: string) { MySharePage({ name: name }) } @Component export struct MySharePage { pathInfos: NavPathStack = new NavPathStack(); name: String = ''; private listArray: Array<string> = ['Projection', 'Print', 'VPN', 'Private DNS', 'NFC']; build() { NavDestination() { Column() { List({ space: 12, initialIndex: 0 }) { ForEach(this.listArray, (item: string) => { ListItem() { Row() { Row() { Text(`${item.slice(0, 1)}`) .fontColor(Color.White) .fontSize(14) .fontWeight(FontWeight.Bold) } .width(30) .height(30) .backgroundColor('#a8a8a8') .margin({ right: 20 }) .borderRadius(20) .justifyContent(FlexAlign.Center) Column() { Text(item) .fontSize(16) .margin({ bottom: 5 }) } .alignItems(HorizontalAlign.Start) Blank() Row() .width(12) .height(12) .margin({ right: 15 }) .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: 15, top: 15, bottom: 15 }) .backgroundColor(Color.White) } .width('100%') .onClick(() => { this.pathInfos.pushPathByName(`${item}`, '页面设置参数') }) }, (item: string): string => item) } .listDirection(Axis.Vertical) .edgeEffect(EdgeEffect.Spring) .sticky(StickyStyle.Header) .width('100%') } .size({ width: '100%', height: '100%' }) }.title(`${this.name}`) .onReady((ctx: NavDestinationContext) => { //NavDestinationContext获取当前所在的页面栈 this.pathInfos = ctx.pathStack; }) } } |
工程配置文件module.json5中配置 {“routerMap”: “$profile:route_map”}。
route_map.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
{ "routerMap": [ { "name": "WLAN", "pageSourceFile": "src/main/ets/pages/PageOne.ets", "buildFunction": "MyCommonPageBuilder" }, { "name": "Bluetooth", "pageSourceFile": "src/main/ets/pages/PageOne.ets", "buildFunction": "MyCommonPageBuilder" }, { "name": "Personal Hotpot", "pageSourceFile": "src/main/ets/pages/PageOne.ets", "buildFunction": "MyCommonPageBuilder" }, { "name": "Connect & Share", "pageSourceFile": "src/main/ets/pages/PageTwo.ets", "buildFunction": "MySharePageBuilder" }, { "name": "Projection", "pageSourceFile": "src/main/ets/pages/PageOne.ets", "buildFunction": "MyCommonPageBuilder" }, { "name": "Print", "pageSourceFile": "src/main/ets/pages/PageOne.ets", "buildFunction": "MyCommonPageBuilder" }, { "name": "VPN", "pageSourceFile": "src/main/ets/pages/PageOne.ets", "buildFunction": "MyCommonPageBuilder" }, { "name": "Private DNS", "pageSourceFile": "src/main/ets/pages/PageOne.ets", "buildFunction": "MyCommonPageBuilder" }, { "name": "NFC", "pageSourceFile": "src/main/ets/pages/PageOne.ets", "buildFunction": "MyCommonPageBuilder" } ] } |
模态转场
使用bindContentCover构建全屏模态转场效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
import { curves } from '@kit.ArkUI'; @Entry @Component struct BindContentCoverDemo { // 第一步:定义全屏模态转场效果bindContentCover // 模态转场控制变量 @State isPresent: boolean = false; // 第二步:定义模态展示界面 // 通过@Builder构建模态展示界面 @Builder MyBuilder() { Column() { Button('关闭') .onClick(() => { this.isPresent = !this.isPresent; }) } .size({ width: '100%', height: '100%' }) .justifyContent(FlexAlign.Center) .backgroundColor(0xf5f5f5) // 通过转场动画实现出现消失转场动画效果 .transition(TransitionEffect.translate({ y: 1000 }).animation({ curve: curves.springMotion(0.6, 0.8) })) } build() { Column() { Button('弹出全屏模态组件') .bindContentCover(this.isPresent, this.MyBuilder(), { modalTransition: ModalTransition.DEFAULT, onDisappear: () => { if (this.isPresent) { this.isPresent = !this.isPresent; } } }) .onClick(() => { // 第三步:通过模态接口调起模态展示界面,通过转场动画或者共享元素动画去实现对应的动画效果 // 改变状态变量,显示模态界面 this.isPresent = !this.isPresent; }) } } } |
使用bindSheet构建半模态转场效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
@Entry @Component struct BindSheetDemo { // 半模态转场显示隐藏控制 @State isShowSheet: boolean = false; // 通过@Builder构建半模态展示界面 @Builder mySheet() { Column() { Text('内容') } .width('100%') .height('100%') .backgroundColor(Color.White) } build() { Column() { Button('使用bindSheet构建半模态转场效果')// 通过选定的半模态接口,绑定模态展示界面,style中包含两个参数,一个是设置半模态的高度,不设置时默认高度是Large,一个是是否显示控制条DragBar,默认是true显示控制条,通过onDisappear控制状态变量变换。 .bindSheet(this.isShowSheet, this.mySheet(), { height: 300, dragBar: false, onDisappear: () => { this.isShowSheet = !this.isShowSheet; } }) .onClick(() => { this.isShowSheet = !this.isShowSheet; }) } .width('100%') } } |
使用bindMenu实现菜单弹出效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
class BMD { value: ResourceStr = '' action: () => void = () => { } } @Entry @Component struct BindMenuDemo { // 第一步: 定义一组数据用来表示菜单按钮项 @State items: BMD[] = [ { value: '菜单项1', action: () => { console.info('handle Menu1 select') } }, { value: '菜单项2', action: () => { console.info('handle Menu2 select') } }, ] build() { Column() { Button('click') .backgroundColor(0x409eff) .borderRadius(5)// 第二步: 通过bindMenu接口将菜单数据绑定给元素 .bindMenu(this.items) } .justifyContent(FlexAlign.Center) .width('100%') .height(437) } } |
使用bindContextMenu实现菜单弹出效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@Entry @Component struct BindContextMenuDemo { // 通过@Builder构建自定义菜单项 @Builder myMenu() { Column() { Text('Menu1') .height(40) Text('Menu2') .height(40) } .width(140) .backgroundColor(0xf1f1f1) } build() { Column() { Button('长按或右键点击') .bindContextMenu(this.myMenu, ResponseType.LongPress) } .width('100%') .alignItems(HorizontalAlign.Center) } } |
使用bindPopUp实现气泡弹窗效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
@Entry @Component struct BindPopupDemo { // 第一步:定义变量控制弹窗显示 @State customPopup: boolean = false; // 第二步:popup构造器定义弹框内容 @Builder popupBuilder() { Column({ space: 2 }) { Row().width(64) .height(64) .backgroundColor(0x409eff) Text('Popup') .fontSize(10) .fontColor(Color.White) } .justifyContent(FlexAlign.SpaceAround) .width(100) .height(100) .padding(5) } build() { Column() { Button('click')// 第四步:创建点击事件,控制弹窗显隐 .onClick(() => { this.customPopup = !this.customPopup; }) .backgroundColor(0xf56c6c)// 第三步:使用bindPopup接口将弹窗内容绑定给元素 .bindPopup(this.customPopup, { builder: this.popupBuilder, placement: Placement.Top, maskColor: 0x33000000, popupColor: 0xf56c6c, enableArrow: true, onStateChange: (e) => { if (!e.isVisible) { this.customPopup = false; } } }) } .justifyContent(FlexAlign.Center) .width('100%') .height(437) } } |
使用if实现模态转场
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
@Entry @Component struct ModalTransitionWithIf { // 第一步:定义状态变量控制页面显示 @State isShowShare: boolean = false; private shareFunc(): void { this.getUIContext()?.animateTo({ duration: 500 }, () => { this.isShowShare = !this.isShowShare; }) } build() { // 第二步:定义Stack布局显示当前页面和模态页面 Stack() { Column() { Button('click').onClick(() => { this.shareFunc(); }) } .width('100%') .height('100%') // 第三步:在if中定义模态页面,显示在最上层,通过if控制模态页面出现消失 if (this.isShowShare) { Column() { Button('Close') .onClick(() => { this.shareFunc(); }) } .width('100%') .height('100%') .backgroundColor(0xffffff) // 第四步:定义模态页面出现消失转场方式 .transition(TransitionEffect.OPACITY .combine(TransitionEffect.translate({ x: '100%' })) .combine(TransitionEffect.scale({ x: 0.95, y: 0.95 }))) } } } } |
共享元素转场
不新建容器并直接变化原容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
class PostData { avatar: Resource = $r('app.media.app_icon'); name: string = ''; message: string = ''; images: Resource[] = []; } @Entry @Component struct Index { @State isExpand: boolean = false; @State @Watch('onItemClicked') selectedIndex: number = -1; private allPostData: PostData[] = [ { avatar: $r('app.media.app_icon'), name: 'Alice', message: '天气晴朗', images: [$r('app.media.app_icon'), $r('app.media.app_icon')] }, { avatar: $r('app.media.app_icon'), name: 'Bob', message: '你好世界', images: [$r('app.media.app_icon')] }, { avatar: $r('app.media.app_icon'), name: 'Carl', message: '万物生长', images: [$r('app.media.app_icon'), $r('app.media.app_icon'), $r('app.media.app_icon')] }]; private onItemClicked(): void { if (this.selectedIndex < 0) { return; } this.getUIContext()?.animateTo({ duration: 350, curve: Curve.Friction }, () => { this.isExpand = !this.isExpand; }); } build() { Column({ space: 20 }) { ForEach(this.allPostData, (postData: PostData, index: number) => { // 当点击了某个post后,会使其余的post消失下树 if (!this.isExpand || this.selectedIndex === index) { Column() { Post({ data: postData, selecteIndex: this.selectedIndex, index: index }) } .width('100%') // 对出现消失的post添加透明度转场和位移转场效果 .transition(TransitionEffect.OPACITY .combine(TransitionEffect.translate({ y: index < this.selectedIndex ? -250 : 250 })) .animation({ duration: 350, curve: Curve.Friction })) } }, (postData: PostData, index: number) => index.toString()) } .size({ width: '100%', height: '100%' }) .backgroundColor('#40808080') } } @Component export default struct Post { @Link selecteIndex: number; @Prop data: PostData; @Prop index: number; @State itemHeight: number = 250; @State isExpand: boolean = false; @State expandImageSize: number = 100; @State avatarSize: number = 50; build() { Column({ space: 20 }) { Row({ space: 10 }) { Image(this.data.avatar) .size({ width: this.avatarSize, height: this.avatarSize }) .borderRadius(this.avatarSize / 2) .clip(true) Text(this.data.name) } .justifyContent(FlexAlign.Start) Text(this.data.message) Row({ space: 15 }) { ForEach(this.data.images, (imageResource: Resource, index: number) => { Image(imageResource) .size({ width: this.expandImageSize, height: this.expandImageSize }) }, (imageResource: Resource, index: number) => index.toString()) } if (this.isExpand) { Column() { Text('评论区')// 对评论区文本添加出现消失转场效果 .transition(TransitionEffect.OPACITY .animation({ duration: 350, curve: Curve.Friction })) .padding({ top: 10 }) } .transition(TransitionEffect.asymmetric( TransitionEffect.opacity(0.99) .animation({ duration: 350, curve: Curve.Friction }), TransitionEffect.OPACITY.animation({ duration: 0 }) )) .size({ width: '100%' }) } } .backgroundColor(Color.White) .size({ width: '100%', height: this.itemHeight }) .alignItems(HorizontalAlign.Start) .padding({ left: 10, top: 10 }) .onClick(() => { this.selecteIndex = -1; this.selecteIndex = this.index; this.getUIContext()?.animateTo({ duration: 350, curve: Curve.Friction }, () => { // 对展开的post做宽高动画,并对头像尺寸和图片尺寸加动画 this.isExpand = !this.isExpand; this.itemHeight = this.isExpand ? 780 : 250; this.avatarSize = this.isExpand ? 75 : 50; this.expandImageSize = (this.isExpand && this.data.images.length > 0) ? (360 - (this.data.images.length + 1) * 15) / this.data.images.length : 100; }) }) } } |