1# 折叠展开动效 2## 场景介绍 3由于目前移动端需要展示的内容越来越多,但是移动端的空间弥足珍贵,在有限的空间内不可能罗列展示全部种类内容,因此折叠/展开功能就可以解决当前问题,本文就介绍下如何使用ArkTS来实现折叠展开动效。 4 5## 效果呈现 6折叠展开动效定义:点击展开按钮,下拉动画展示内容,点击折叠按钮,折叠动画折叠内容。 7本例最终效果如下: 8 9 10## 运行环境 11本例基于以下环境开发,开发者也可以基于其它适配的版本进行开发: 12- IDE: DevEco Studio 3.1 Release 13- SDK: Ohos_sdk_public 3.2.12.5(API Version 9 Release) 14## 实现思路 15创建折叠时的文本组件,根据List组件中的groupcollapse和groupexpand事件自定义一个CollapseAndExpand组件,父组件通过维护flag和onFlagChange来控制折叠/展开的动效,设置动效所需的参数,添加逻辑来展示展开后的文本。 16 17## 开发步骤 181. 创建自定义接口IRowItem。 19 具体代码如下: 20 21 ```ts 22 interface IRowItem { 23 id?: number; 24 title?: string; 25 name1?: string; 26 name2?: string; 27 name3?: string; 28 flag?: boolean; 29 type?: string; 30 onFlagChange?: () => void; 31 } 32 ``` 33 342. 创建自定义组件CollapseAndExpandDemo,根据自定义接口IRowItem添加内容,创建UI展示文本。 35具体代码如下: 36 37 ```ts 38 @Entry 39 @Component{ 40 ... 41 build() { 42 Column() { 43 Row() { 44 Image($r("app.media.ic_public_back")) 45 .width(20) 46 .height(20) 47 Text('周免英雄') 48 .fontSize(18) 49 .fontWeight(FontWeight.Bold) 50 .margin({ left: 10 }) 51 } 52 .width('100%') 53 .margin({ bottom: 30 }) 54 55 Column() { 56 RowItem({ props: { title: 'AAAAA', name1: 'BBBBB', name2: 'CCCCC', name3: '武器大师' } }) 57 // 文本折叠时,type为DOWN 58 RowItem({ props: { name1: 'DDDDD', name2: 'EEEEE', name3: 'FFFFF', type: 'DOWN', onFlagChange: this.onFlagChange } }) 59 60 //被折叠的文本内容 61 ... 62 63 RowItem({ props: { title: '商城', name1: '免费', name2: '特价', name3: 'VIP' } }) 64 RowItem({ props: { title: '分类', name1: '按职业', name2: '按位置', name3: '按城市' } }) 65 } 66 .width('100%') 67 } 68 ``` 69 70 被折叠文本信息。 71 具体代码如下: 72 73 ```ts 74 CollapseAndExpand({ 75 items: [ 76 { id: 0, name1: 'GGGGG', name2: 'HHHHH', name3: 'JJJJJ' }, 77 { id: 1, name1: 'KKKKK', name2: 'LLLLL', name3: 'MMMMM' }, 78 { id: 2, name1: 'NNNNN', name2: 'OOOOO', name3: 'PPPPP' }, 79 // 文本展开时,type为UP 80 { id: 3, name1: 'QQQQQ', name2: 'RRRRR', name3: 'SSSSS', type: 'UP', onFlagChange: this.onFlagChange } 81 ], 82 }) 83 ``` 84 85 863. 将步骤2创建的文本进行渲染。 87具体如下: 88 89 ```ts 90 build() { 91 Flex() { 92 Text(this.props.title) 93 .fontSize(14) 94 .fontWeight(FontWeight.Bold) 95 .layoutWeight(1) 96 .fontColor(Color.Red) 97 .margin({ right: 10 }) 98 Flex({ alignItems: ItemAlign.Center }) { 99 Text(this.props.name1).fontSize(14).margin({ right: 10 }) 100 Text(this.props.name2).fontSize(14).margin({ right: 10 }) 101 Text(this.props.name3).fontSize(14).margin({ right: 10 }) 102 ... 103 } 104 } 105 } 106 ``` 1074. 创建自定义组件CollapseAndExpand。 108根据自定义组件说明动效,@Provide负责数据更新,并且触发渲染;@Consume在感知数据更新后,重新渲染。 109具体代码如下: 110 111 ```ts 112 @Entry 113 @Component 114 struct CollapseAndExpandDemo { 115 @Provide("flag") flag: boolean = false 116 private onFlagChange = () => { 117 animateTo({ 118 duration: 650, 119 curve: Curve.Smooth 120 }, () => { 121 this.flag = !this.flag; 122 }) 123 } 124 125 ... 126 127 @Component 128 struct CollapseAndExpand { 129 private items: IRowItem[] = []; 130 @Consume("flag") flag: boolean; 131 132 build() { 133 Column() { 134 ForEach(this.items, (item: IRowItem) => { 135 RowItem({ props: item }) 136 }, (item: IRowItem) => item.id.toString()) 137 } 138 .width('100%') 139 .clip(true) 140 .height(this.flag ? 130 : 0) 141 } 142 } 143 ``` 1445. 根据步骤4最终的flag以及props的type值,判断折叠展开的效果实现。 145 具体代码如下: 146 147 ```ts 148 build() { 149 ... 150 // 当文本折叠(flag为false且type为down)时,展示展开按钮 151 // 当文本展开(flag为true且type为up)时,展示折叠按钮 152 if (!this.flag && this.props.type === 'DOWN' || this.flag && this.props.type === 'UP') { 153 Image($r("app.media.icon")) 154 .width(16) 155 .height(16) 156 .objectFit(ImageFit.Contain) 157 .rotate({ angle: !this.flag && this.props.type === 'DOWN' ? 0 : 180 }) 158 // 点击按钮后旋转180°,展示折叠按钮 159 .onClick(() => 160 this.props.onFlagChange() 161 ) 162 .transition({ type: TransitionType.All, opacity: 0 }) 163 } 164 } 165 ``` 166 167 168## 完整代码 169示例代码如下: 170```ts 171interface IRowItem { 172 id?: number; 173 title?: string; 174 name1?: string; 175 name2?: string; 176 name3?: string; 177 flag?: boolean; 178 type?: string; 179 onFlagChange?: () => void; 180} 181 182@Entry 183@Component 184struct CollapseAndExpandDemo { 185 @Provide("flag") flag: boolean = false 186 private onFlagChange = () => { 187 animateTo({ 188 duration: 650, 189 curve: Curve.Smooth 190 }, () => { 191 this.flag = !this.flag; 192 }) 193 } 194 195 build() { 196 Column() { 197 Row() { 198 Image($r("app.media.ic_public_back")).width(20).height(20) 199 Text('周免英雄') 200 .fontSize(18) 201 .fontWeight(FontWeight.Bold) 202 .margin({ left: 10 }) 203 } 204 .width('100%') 205 .margin({ bottom: 30 }) 206 207 Column() { 208 RowItem({ 209 props: { title: '英雄', name1: 'AAAAA', name2: 'BBBBB', name3: 'CCCCC' } }) 210 RowItem({ 211 props: { 212 name1: 'DDDDD', 213 name2: 'EEEEE', 214 name3: 'FFFFF', 215 // 文本折叠时,type为DOWN 216 type: 'DOWN', 217 onFlagChange: this.onFlagChange 218 } 219 }) 220 // 直接调用折叠展开组件 221 CollapseAndExpand({ 222 items: [ 223 { id: 0, name1: 'GGGGG', name2: 'HHHHH', name3: 'JJJJJ' }, 224 { id: 1, name1: 'KKKKK', name2: 'LLLLL', name3: 'MMMMM' }, 225 { id: 2, name1: 'NNNNN', name2: 'OOOOO', name3: 'PPPPP' }, 226 { id: 3, 227 name1: 'QQQQQ', 228 name2: 'RRRRR', 229 name3: 'SSSSS', 230 // 文本折叠时,type为UP 231 type: 'UP', 232 onFlagChange: this.onFlagChange } 233 ], 234 }) 235 236 RowItem({ props: { title: '商城', name1: '免费', name2: '特价', name3: 'VIP' } }) 237 RowItem({ props: { title: '分类', name1: '按职业', name2: '按位置', name3: '按城市' } }) 238 } 239 .width('100%') 240 241 } 242 .height('100%') 243 .padding({ top: 30, right: 30, left: 30 }) 244 } 245} 246 247@Component 248struct RowItem { 249 private props: IRowItem; 250 @Consume("flag") flag: boolean 251 252 build() { 253 Flex() { 254 Text(this.props.title) 255 .fontSize(14) 256 .fontWeight(FontWeight.Bold) 257 .layoutWeight(1) 258 .fontColor(Color.Red) 259 .margin({ right: 10 }) 260 Flex({ alignItems: ItemAlign.Center }) { 261 Text(this.props.name1).fontSize(14).margin({ right: 10 }) 262 Text(this.props.name2).fontSize(14).margin({ right: 10 }) 263 Text(this.props.name3).fontSize(14).margin({ right: 10 }) 264 265 // 当文本折叠(flag为false且type为down)时,展示展开按钮 266 // 当文本展开(flag为true且type为up)时,展示折叠按钮 267 if (!this.flag && this.props.type === 'DOWN' || this.flag && this.props.type === 'UP') { 268 Image($r("app.media.ic_public_arrow_down_0")) 269 .width(16) 270 .height(16) 271 .objectFit(ImageFit.Contain) 272 .rotate({ angle: !this.flag && this.props.type === 'DOWN' ? 0 : 180 }) 273 // 点击展开按钮后旋转180°,展示折叠按钮 274 .onClick(() => this.props.onFlagChange()) 275 .transition({ type: TransitionType.All, opacity: 0 }) 276 } 277 } 278 .layoutWeight(3) 279 } 280 .width('100%') 281 .height(16) 282 .margin({ top: 15 }) 283 } 284} 285 286@Component 287struct CollapseAndExpand { 288 private items: IRowItem[] = []; 289 @Consume("flag") flag: boolean; 290 291 build() { 292 Column() { 293 ForEach(this.items, (item: IRowItem) => { 294 RowItem({ props: item }) 295 }, (item: IRowItem) => item.id.toString()) 296 } 297 .width('100%') 298 .clip(true) 299 .height(this.flag ? 130 : 0) 300 } 301} 302``` 303## 参考 304[显示动画](../application-dev/reference/apis-arkui/arkui-ts/ts-explicit-animation.md) 305 306[@Provide和@Consume:与后代组件双向同步](../application-dev/quick-start/arkts-provide-and-consume.md) 307 308[list开发指导](../application-dev/ui/ui-js-components-list.md) 309 310 311 312 313 314