提交 bf45d548 编写于 作者: D duangavin123

新增案例

Signed-off-by: Nduangavin123 <duanxichao@huawei.com>
上级 ccc0d9ff
......@@ -21,7 +21,9 @@
- [时钟开发示例](time-styles-shift.md)
- [弹簧动画开发](how-to-develop-spring-animation.md)
- [水波纹动画开发](water-wave-animation.md)
- [属性动画示例](property-animation.md)
- [如何使用OpenGL实现3D图形](how-to-use-opengl-to-draw-3d-graphics.md)
- [控制页面刷新范围](overall-and-part-refresh.md)
### 网络管理
- [如何请求并加载网络图片](how-to-load-images-from-internet.md)
......
# 控制页面刷新范围
## 场景说明
在实现页面UI时,业务方需要根据业务逻辑动态更新组件的状态,常见的如在手机桌面长按某个App的图标时,图标背景色、大小等会发生变化。根据业务需要,有时我们需要触发单个组件的状态更新,有时需要触发部分或全部组件的状态更新。那么如何控制组件状态刷新的范围呢?本例将为大家提供一种参考方案。
## 效果呈现
本例最终效果如下:
![part-and-full-refresh](figures/overall-and-part-refresh.gif)
## 运行环境
本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:
- IDE: DevEco Studio 3.1 Release
- SDK: Ohos_sdk_public 3.2.12.5(API Version 9 Release)
## 实现思路
类似于React、SwiftUI等声明式UI编程框架,ArkUI也具备声明式的范式,通过页面的状态数据驱动UI的更新,其UI更新机制可以通过如下表达式来体现:
***UI=f(state)***
用户构建了UI模型,其中参数state代表页面组件的运行时状态。当state改变时,UI作为返回结果,也将进行对应的改变刷新。f作为状态管理机制,维护组件运行时的状态变化所带来的UI重新渲染。组件的状态改变可通过状态变量进行控制。
基于上述理论,如果要控制页面的更新范围,我们必须要:**定义准确状态变量,并控制状态变量影响的组件范围。**
本例中包含8个APP卡片,其中涉及5种状态变化,按照局部刷新和全局刷新可分为:
- 局部刷新(单个卡片变化)
- 点击卡片,卡片背景色变为红色。
- 点击卡片,卡片进行缩放。
- 拖拽卡片,卡片位置变化。
- 全局刷新(全部卡片变化)
- 长按某个卡片,为所有卡片添加删除图标。
- 点击删除图标外的任意地方,删除图标消失。
所以处理思路为,控制局部刷新的状态变量在子组件中定义,绑定子组件,控制全局刷新的状态变量在父组件中进行定义,并由父组件传递给所有子组件。如下图:
![solving-plan](figures/solving-plan.PNG)
## 开发步骤
由于本例重点讲解刷新区域的控制,所以开发步骤会着重讲解相关实现,不相关的内容不做介绍,全量代码可参考完整代码章节。
1. 创建APP卡片组件作为子组件,每个卡片包含文本和删除图标。
具体代码如下:
```ts
@Component
export struct AppItem {
...
build() {
Stack({ alignContent: Alignment.TopEnd }) {
Image($r('app.media.ic_public_close'))
.height(30)
.width(30)
.zIndex(2)
.offset({
x: -12,
y: 12
})
Text(this.data.title)
.width(100)
.height(100)
.fontSize(16)
.margin(10)
.textAlign(TextAlign.Center)
.borderRadius(10)
}
}
}
```
2. 创建父组件,并在父组件中引用子组件。
具体代码如下:
```ts
@Entry
@Component
struct Sample {
...
build() {
Stack({ alignContent: Alignment.Bottom }) {
Flex({ wrap: FlexWrap.Wrap }) {
// 通过循环渲染加载所有子组件
ForEach(this.items, (item: ItemProps, index: number) => {
// 引用App卡片子组件
AppItem({data: this.items[index]})
}, (item: ItemProps) => item.id.toString())
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.backgroundColor('#ffffff')
.margin({ top:50 })
}
}
```
3. 由于卡片背景色变化、卡片缩放、卡片拖拽在触发时都是针对单个卡片的状态变化,所以在卡片子组件中定义相应的状态变量,用来控制单个卡片的状态变化。
本例中定义状态变量“data”用来控制卡片拖拽时位置的刷新;定义状态变量”downFlag“用来监听卡片是否被按下,从而控制卡片背景色及缩放状态的更新。
具体代码如下:
```ts
@Component
export struct AppItem {
// 定义状态变量data,用来控制卡片被拖拽时位置的刷新
@State data: ItemProps = {};
// 定义状态变量downFlag用来监听卡片是否被按下,从而控制卡片背景色及缩放状态的更新
@State downFlag: boolean = false;
...
build() {
Stack({ alignContent: Alignment.TopEnd }) {
Image($r('app.media.ic_public_close'))
.height(30)
.width(30)
.zIndex(2)
.offset({
x: -12,
y: 12
})
Text(this.data.title)
.width(100)
.height(100)
.fontSize(16)
.margin(10)
.textAlign(TextAlign.Center)
.borderRadius(10)
// 根据状态变量downFlag的变化,更新背景色
.backgroundColor(this.downFlag ? '#EEA8AB' : '#86C7CC')
// 背景色更新时添加属性动画
.animation({
duration: 500,
curve: Curve.Friction
})
// 绑定onTouch事件,监听卡片是否被按下,根据不同状态改变downFlag的值
.onTouch((event: TouchEvent) => {
if (event.type == TouchType.Down) {
this.downFlag = true
} else if (event.type == TouchType.Up) {
this.downFlag = false
}
})
}
// 根据状态变量downFlag的变化,控制卡片的缩放
.scale(this.downFlag ? { x: 0.8, y: 0.8 } : { x: 1, y: 1 })
// 通过状态变量data的变化,控制卡片位置的更新
.offset({
x: this.data.offsetX,
y: this.data.offsetY
})
// 拖动触发该手势事件
.gesture(
GestureGroup(GestureMode.Parallel,
...
PanGesture(this.panOption)
.onActionStart((event: GestureEvent) => {
console.info('Pan start')
})
// 拖动卡片时,改变状态变量data的值
.onActionUpdate((event: GestureEvent) => {
this.data.offsetX = this.data.positionX + event.offsetX
this.data.offsetY = this.data.positionY + event.offsetY
})
.onActionEnd(() => {
this.data.positionX = this.data.offsetX
this.data.positionY = this.data.offsetY
console.info('Pan end')
})
)
)
}
}
```
4. 长按卡片,卡片右上角会出现删除图标。由于所有卡片右上角都会出现删除图标,所以这里需要做全局的刷新。本例在父组件中定义状态变量“deleteVisibility”,在调用子组件时,将其作为参数传递给所有卡片子组件,并且通过@Link装饰器与子组件进行双向绑定,从而可以控制所有卡片子组件中删除图标的更新。
**父组件代码:**
```ts
@Entry
@Component
struct Sample {
...
// 定义状态变量deleteVisibility,控制App卡片上删除图标的更新
@State deleteVisibility: boolean = false
...
build() {
Stack({ alignContent: Alignment.Bottom }) {
Flex({ wrap: FlexWrap.Wrap }) {
// 通过循环渲染加载所有子组件
ForEach(this.items, (item: ItemProps, index: number) => {
// 将状态变量deleteVisibility传递给每一个子组件,从而deleteVisibility变化时可以触发所有子组件的更新
AppItem({ deleteVisibility: $deleteVisibility, data: this.items[index], onDeleteClick: this.delete })
}, (item: ItemProps) => item.id.toString())
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.backgroundColor('#ffffff')
.margin({ top:50 })
.onClick(() => {
this.deleteVisibility = false
})
}
```
**子组件代码:**
```ts
@Component
export struct AppItem {
...
// 定义deleteVisibility状态变量,并通过@Link装饰器与父组件中的同名变量双向绑定,该变量值发生变化时父子组件可双向同步
@Link deleteVisibility: boolean;
...
build() {
Stack({ alignContent: Alignment.TopEnd }) {
// 通过deleteVisibility控制删除图标的隐藏和显示,当deleteVisibility值为true时显示,为false时隐藏
if(this.deleteVisibility){
Image($r('app.media.ic_public_close'))
.height(30)
.width(30)
.zIndex(2)
// 控制删除图标的显隐
.visibility(Visibility.Visible)
.offset({
x: -12,
y: 12
})
.onClick(() => this.onDeleteClick(this.data.id))
}else{
Image($r('app.media.ic_public_close'))
.height(30)
.width(30)
.zIndex(2)
.visibility(Visibility.Hidden)
.offset({
x: -12,
y: 12
})
.onClick(() => this.onDeleteClick(this.data.id))
}
...
.gesture(
GestureGroup(GestureMode.Parallel,
// 识别长按手势
LongPressGesture({ repeat: true })
.onAction((event: GestureEvent) => {
if (event.repeat) {
// 长按时改变deleteVisibility的值为true,从而更新删除图标为显示状态
this.deleteVisibility = true
}
console.info('LongPress onAction')
}),
...
)
)
}
}
```
## 完整代码
本例完整代码如下:
data.ets文件(数据模型文件)
```ts
// data.ets
// AppItem组件接口信息
export interface ItemProps {
id?: number,
title?: string,
offsetX?: number, // X偏移量
offsetY?: number, // Y偏移量
positionX?: number, // 在X的位置
positionY?: number, // 在Y的位置
}
// AppItem初始数据
export const initItemsData: ItemProps[] = [
{
id: 1,
title: 'APP1',
offsetX: 0,
offsetY: 0,
positionX: 0,
positionY: 0
},
{
id: 2,
title: 'APP2',
offsetX: 0,
offsetY: 0,
positionX: 0,
positionY: 0
},
{
id: 3,
title: 'APP3',
offsetX: 0,
offsetY: 0,
positionX: 0,
positionY: 0
},
{
id: 4,
title: 'APP4',
offsetX: 0,
offsetY: 0,
positionX: 0,
positionY: 0
},
{
id: 5,
title: 'APP5',
offsetX: 0,
offsetY: 0,
positionX: 0,
positionY: 0
},
{
id: 6,
title: 'APP6',
offsetX: 0,
offsetY: 0,
positionX: 0,
positionY: 0
},
{
id: 7,
title: 'APP7',
offsetX: 0,
offsetY: 0,
positionX: 0,
positionY: 0
},
{
id: 8,
title: 'APP8',
offsetX: 0,
offsetY: 0,
positionX: 0,
positionY: 0
},
]
```
AppItem.ets文件(卡片子组件)
```ts
// AppItem.ets
import { ItemProps } from '../model/data';
@Component
export struct AppItem {
// 定义状态变量data,用来控制卡片被拖拽时位置的刷新
@State data: ItemProps = {};
// 定义状态变量downFlag用来监听卡片是否被按下,从而控制卡片背景色及缩放状态的更新
@State downFlag: boolean = false;
// 定义deleteVisibility状态变量,并通过@Link装饰器与父组件中的同名变量双向绑定,该变量值发生变化时父子组件可双向同步
@Link deleteVisibility: boolean;
private onDeleteClick: (id: number) => void;
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
build() {
Stack({ alignContent: Alignment.TopEnd }) {
// 通过deleteVisibility控制删除图标的隐藏和显示,当deleteVisibility值为true时显示,为false时隐藏
if(this.deleteVisibility){
Image($r('app.media.ic_public_close'))
.height(30)
.width(30)
.zIndex(2)
// 控制删除图标的显隐
.visibility(Visibility.Visible)
.offset({
x: -12,
y: 12
})
.onClick(() => this.onDeleteClick(this.data.id))
}else{
Image($r('app.media.ic_public_close'))
.height(30)
.width(30)
.zIndex(2)
.visibility(Visibility.Hidden)
.offset({
x: -12,
y: 12
})
.onClick(() => this.onDeleteClick(this.data.id))
}
Text(this.data.title)
.width(100)
.height(100)
.fontSize(16)
.margin(10)
.textAlign(TextAlign.Center)
.borderRadius(10)
// 根据状态变量downFlag的变化,更新背景色
.backgroundColor(this.downFlag ? '#EEA8AB' : '#86C7CC')
// 背景色更新时添加属性动画
.animation({
duration: 500,
curve: Curve.Friction
})
// 绑定onTouch事件,监听卡片是否被按下,根据不同状态改变downFlag的值
.onTouch((event: TouchEvent) => {
if (event.type == TouchType.Down) {
this.downFlag = true
} else if (event.type == TouchType.Up) { // 手指抬起
this.downFlag = false
}
})
}
// 根据状态变量downFlag的变化,控制卡片的缩放
.scale(this.downFlag ? { x: 0.8, y: 0.8 } : { x: 1, y: 1 })
// 通过状态变量data的变化,控制卡片位置的更新
.offset({
x: this.data.offsetX,
y: this.data.offsetY
})
// 拖动触发该手势事件
.gesture(
GestureGroup(GestureMode.Parallel,
// 识别长按手势
LongPressGesture({ repeat: true })
.onAction((event: GestureEvent) => {
if (event.repeat) {
// 长按时改变deleteVisibility的值为true,从而更新删除图标为显示状态
this.deleteVisibility = true
}
console.info('LongPress onAction')
}),
PanGesture(this.panOption)
.onActionStart((event: GestureEvent) => {
console.info('Pan start')
})
// 拖动卡片时,改变状态变量data的值
.onActionUpdate((event: GestureEvent) => {
this.data.offsetX = this.data.positionX + event.offsetX
this.data.offsetY = this.data.positionY + event.offsetY
})
.onActionEnd(() => {
this.data.positionX = this.data.offsetX
this.data.positionY = this.data.offsetY
console.info('Pan end')
})
)
)
}
}
```
Index.ets文件(父组件)
```ts
// Index.ets
import { AppItem } from '../components/MyItem';
import { initItemsData } from '../model/data';
import { ItemProps } from '../model/data';
@Entry
@Component
struct Sample {
@State items: ItemProps[] = [];
// 定义状态变量deleteVisibility,控制App卡片上删除图标的更新
@State deleteVisibility: boolean = false
// 删除指定id组件
private delete = (id: number) => {
const index = this.items.findIndex(item => item.id === id);
this.items.splice(index, 1);
}
// 生命周期函数:组件即将出现时调用
aboutToAppear() {
this.items = [...initItemsData];
}
build() {
Stack({ alignContent: Alignment.Bottom }) {
Flex({ wrap: FlexWrap.Wrap }) {
// 通过循环渲染加载所有子组件
ForEach(this.items, (item: ItemProps, index: number) => {
// 将状态变量deleteVisibility传递给每一个子组件,从而deleteVisibility变化时可以触发所有子组件的更新
AppItem({ deleteVisibility: $deleteVisibility, data: this.items[index], onDeleteClick: this.delete })
}, (item: ItemProps) => item.id.toString())
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
.backgroundColor('#ffffff')
.margin({ top:50 })
.onClick(() => {
// 点击组件,deleteVisibility值变为false,从而隐藏所有卡片的删除图标
this.deleteVisibility = false
})
}
}
```
## 参考
- [@Link](../application-dev/quick-start/arkts-link.md)
- [显隐控制](../application-dev/reference/arkui-ts/ts-universal-attributes-visibility.md)
- [组合手势](../application-dev/reference/arkui-ts/ts-combined-gestures.md)
\ No newline at end of file
# 属性动画
## 场景介绍
在日常开发过程中,通常会出现因为状态变化导致组件属性值发生变化,
如:数字键盘按键时,对应数字按钮背景色加深,呈现被点击效果;放开按键时,呈现取消选中效果,
如:手机UI中图标按下时,图标出现弹性缩放效果,
如:在做数据统计时,随着数据值的变化,统计曲线随之变化等动画效果,
本例将为大家介绍下如何通过属性动画实现上述场景。
## 效果呈现
效果图如下:
| 场景 | 效果图 |
|------------|-----------------------|
| 场景1:属性动画 | ![属性动画](figures/attribute_animation.gif) |
| 场景2:弹性动态 | ![弹性动态](figures/Elastic_animation.gif) |
| 场景3:时间曲线 | ![时间曲线](figures/time_curve.gif) |
## 运行环境
本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发
- IDE: DevEco Studio 3.1 Release
- SDK: Ohos_sdk_public 3.2.12.5(API Version 9 Release)
## 场景1:属性动画
### 实现思路
本实例涉及到的主要特性及其实现方案如下:
* 通过Column、Grid、button、Text等关键组件以及visibility属性搭建UI局框架,以及渲染数字按钮。
* 设置状态变量flag,用来控制属性状态的变化,同时向Button组件添加onTouch事件,更新按钮的当前状态,从而可以根据监听到的按钮状态加载对应的动画效果。
* 默认状态为按钮放开状态(flag为false)。
* 当按钮按下时,更新按钮的状态(flag:false -> true)。
* 当按钮放开时,更新按钮的状态(flag:true -> false)。
* 根据按钮组件的按下/放开状态,通过三元运算判断,使用属性动画更新按钮的选中/取消效果。
* 当按钮按下时,加载属性动画:按钮被选中效果。
* 当按钮放开时,加载属性动画:按钮取消选中效果。
### 开发步骤
针对实现思路中所提到的内容,具体关键开发步骤如下:
1. 通过Column、Grid、button、Text等关键组件以及visibility属性搭建UI局框架,以及渲染数字按钮。
具体代码如下:
```ts
private numGrid: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, -1, 0, -1]
build() {
Row() {
Column() {
Grid() {
//通过ForEach循环遍历,显示数字数字按钮
ForEach(this.numGrid, (item, index) => {
GridItem() {
Button() {
Text(`${item}`)
.fontSize(30)
}
.backgroundColor('#fff')
.width(100)
.height(100)
// item为-1时,数字按钮不显示
.visibility(item == -1 ? Visibility.Hidden : Visibility.Visible)
}
}, item => item)
}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.width(330)
.height(440)
}
.width('100%')
.height('100%')
}
}
```
2. 设置状态变量flag,用来控制属性状态的变化,同时向Button组件添加onTouch事件,更新按钮的当前状态,从而可以根据监听到的按钮状态加载对应的动画效果。
具体代码如下:
```ts
//状态变量flag,用于监听按钮按下和放开的状态,默认至为false(放开状态)
@State flag: boolean = false;
...
.onTouch((event:TouchEvent) => {
//当按钮按下时,更新按钮的状态(flag:false -> true)
if (event.type == TouchType.Down) {
this.flag = true
this.currIndex = index
}
//当按钮放开时,更新按钮的状态(flag:true -> false)
if (event.type == TouchType.Up) {
this.flag = false
}
})
```
3. 根据按钮组件的按下/放开状态,通过三元运算判断并更新按钮背景色。
具体代码如下:
```ts
Button(){
...
// 当按钮被按下(flag变更为true)时,当前按钮backgroundColor属性变更("#fff" -> "#D2C3D5" )
// 当按钮被放开(flag变更为false)时,当前按钮backgroundColor属性变更("#D2C3D5" -> "#FFF" )
.backgroundColor(this.flag && this.currIndex == index ? '#D2C3D5' : '#fff')
.animation({
duration: 200
})
}
```
### 完整代码
```ts
@Entry
@Component
struct Index {
private currIndex: number = -1
private numGrid: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, -1, 0, -1]
//状态变量flag,用于监听按钮按下和放开的状态,默认至为false(放开状态)
@State flag: boolean = false;
build() {
Row() {
Column() {
Grid() {
ForEach(this.numGrid, (item, index) => {
GridItem() {
Button() {
Text(`${item}`)
.fontSize(30)
}
// 当按钮被按下(flag变更为true)时,当前按钮backgroundColor属性变更("#fff" -> "#D2C3D5" )
// 当按钮被放开(flag变更为false)时,当前按钮backgroundColor属性变更("#D2C3D5" -> "#FFF" )
.backgroundColor(this.flag && this.currIndex == index ? '#D2C3D5' : '#fff')
.animation({
duration: 200
})
.width(100)
.height(100)
.visibility(item == -1 ? Visibility.Hidden : Visibility.Visible)
.onTouch((event:TouchEvent) => {
//当按钮按下时,更新按钮的状态(flag:false -> true)
if (event.type == TouchType.Down) {
this.flag = true
this.currIndex = index
}
//当按钮放开时,更新按钮的状态(flag:true -> false)
if (event.type == TouchType.Up) {
this.flag = false
}
})
}
}, item => item)
}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.width(330)
.height(440)
}
.width('100%')
.height('100%')
}
}
}
```
## 场景2:弹性动效
### 实现思路
针对弹性动效,涉及到的主要特征分为两部分:
* 组件描绘:先通过List、ListItem、Image等组件将控件表描绘出来。
* 组件状态变化:设置状态变量downFlag,控制按钮的当前状态;同时向Column组件添加onTouch事件,监听组件的当前状态。
* 组件默认状态为放开状态(downFlag:false)。
* 当按下Image组件时,更新组件的状态为true。
* 当放开Image组件时,更新组件的状态为false。
* 动画播放:使用属性动画绘制Image组件不同状态下的曲线动画。
* 组件按下,触发第一阶段缩小动效,370ms内缩小控件0.8倍。
* 组件放开,触发第二阶段放大动效,370ms控件从当前值恢复到正常大小。
### 开发步骤
针对实现思路中所提到的内容,具体关键开发步骤如下:
1. 先通过List、ListItem、Image等组件将控件表描绘出来。
具体代码如下:
```ts
private arr: number[]=[1,2,3,4,5]
...
List({space:10}){
ForEach(this.arr,(item,index)=>{
ListItem(){
Image($r("app.media.app_icon"))
.width(80)
.height(80)
}
})
}
.margin({top:20})
.padding({left:20})
.listDirection(Axis.Horizontal)
```
2. 设置状态变量downFlag,控制Image组件的当前状态,同时向Image组件添加onTouch事件,获取并更新组件的当前状态,从而可以根据监听到的组件状态加载对应的动画效果。
具体代码如下:
```ts
...
// 状态变量downFlag,用于监听Image组件按下和放开的状态
@State downFlag: boolean = false;
...
// 添加onTouch事件,监听状态
.onTouch((event: TouchEvent) => {
// 当Column组件按下时,组件的状态更新为true
if (event.type == TouchType.Down) {
this.downFlag = true
// 当Column组件按下时,组件的状态更新为false
} else if (event.type == TouchType.Up) {
this.downFlag = false
}
})
```
3. 根据Image组件的按下/放开状态,呈现不同的弹性效果(按下时组件缩小,动画以阻尼曲线的形式缩小至0.8倍,放开时组件动画以阻尼曲线的形式恢复至初始大小)。
具体代码如下:
```ts
···
Image($r("app.media.app_icon"))
// 当downFlag状态为按下(true)时,组件在370ms内缩小至0.5倍;当downFlag状态为放开(false)时,组件在370ms内恢复至初始大小;
.scale(this.downFlag ? { x: 0.8, y: 0.8 } : { x: 1, y: 1 })
.animation({
duration: 370,
curve: Curve.Friction
})
```
### 完整代码
```ts
@Entry
@Component
struct Index {
// 状态变量downFlag,用于监听Image组件按下和放开的状态
@State downFlag: boolean = false;
private arr: number[]=[1,2,3,4,5]
private curIndex : number = -1
build() {
List({space:10}){
ForEach(this.arr,(item,index)=>{
ListItem(){
Image($r("app.media.app_icon"))
// 当downFlag状态为按下(true)时,组件在370ms内缩小至0.8倍;当downFlag状态为放开(false)时,组件在370ms内恢复至初始大小;
.scale(this.downFlag && this.curIndex == index ? { x: 0.8, y: 0.8 } : { x: 1, y: 1 })
.animation({
duration: 370,
curve: Curve.Friction
})
.width(80)
.height(80)
// 添加onTouch事件,监听状态
.onTouch((event: TouchEvent) => {
// 当Image组件按下时,组件的状态更新为true
if (event.type == TouchType.Down) {
this.downFlag = true
this.curIndex = index
// 当Image组件按下时,组件的状态更新为false
} else if (event.type == TouchType.Up) {
this.downFlag = false
}
})
}
})
}
.margin({top:20})
.padding({left:20})
// 列表排列方向水平
.listDirection(Axis.Horizontal)
}
}
```
## 场景3:时间曲线
时间曲线主要是在相同的周期内,使用不同的曲线属性,呈现出不同的动画。
ArkUI中,时间曲线有两种使用方式,一种是Curve类型的枚举,一种是ohos.curve导入。
### 实现思路
* 设置自定义变量item,用于存放不同类型的曲线,本案例中主要有以下类型曲线:
1. Curve类型的枚举:
* Linear:表示动画从头到尾的速度都是相同的。
* EaseInOut:表示动画以低速开始和结束,CubicBezier(0.42, 0.0, 0.58, 1.0)。
* Smooth:平滑曲线,cubic-bezier(0.4, 0.0, 0.4, 1.0)。
2. ohos.curve导入:
* springCurve类的弹簧曲线。
* 通过Flex、Column、button组件将UI布局以及框架搭建。
* Column组件内部通过ForEach,遍历展示每条曲线。
* 设置状态变量flag,监听当前button的状态,同时向button组件添加onClick事件,更新按钮的状态,从而可以根据监听到的按钮状态加载对应的动画效果。
* 向Text组件添加width属性,根据按钮的状态,在同一周期内,通过width的变化呈现曲线的不同动画。
### 开发步骤
1. 设置自定义变量item,用于存放不同类型的曲线。
具体代码如下:
```ts
private items: object[] = [
{
color: '#a320bf',
title: '线性曲线',
// curve类型曲线:表示动画从头到尾的速度都是相同的。
curve: Curve.Linear,
},
{
color: '#17d4be',
title: '低速结束曲线',
// curve类型曲线:表示动画以低速结束
curve: Curve.EaseInOut,
},
{
color: '#0e2d8c',
title: '平滑曲线',
// curve类型曲线:平滑曲线
curve: Curve.Smooth,
},
{
color: '#d4bb19',
title: 'Curves.spring',
// ohos.curve导入的弹簧曲线,以初速度为20的弹簧进行平移
curve: Curves.springCurve(20, 1, 1, 1.2),
},
```
2. 通过Flex、Column、button组件将UI布局以及框架搭建。
具体代码如下:
```ts
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Column({ space: 50 }) {
...
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding({ left: 12, right: 12, top: 22, bottom: 22 })
Button(){
Text("Go!")
.fontSize(20)
.fontColor('#fff')
}
.borderRadius(20)
.fontColor('#FFFFFF')
.fontSize('12fp')
.fontWeight(FontWeight.Bolder)
.backgroundColor('#15587c')
.padding(25)
.margin({top:50})
}
.height('100%')
.backgroundColor('#F1F3F5')
.padding({ left: '3%', right: '3%' })
```
3. Column组件内部通过ForEach,遍历展示每条曲线。
具体代码如下:
```ts
ForEach(this.items, (item, index) => {
Text(item['title'])
.fontSize(20)
.fontColor('#FFFFFF')
.height(80)
.textAlign(TextAlign.Start)
.backgroundColor(item['color'])
.borderRadius(20)
.padding({left:10})
}, item => JSON.stringify(item))
```
4. 设置状态变量flag,监听当前button的状态,同时向button组件添加onClick事件,更新按钮的状态,从而可以根据监听到的按钮状态加载对应的动画效果。
具体代码如下:
```ts
//状态变量flag,用于监听按钮按下状态
@State flag: boolean = true
...
Button(){
}
// 通过点击事件,反转flag的值
.onClick(() => {
this.flag = !this.flag
})
```
5. 向Text组件添加width属性,根据按钮的状态,在同一周期内,呈现不同效果的时间曲线动画。
具体代码如下:
```ts
ForEach(this.items, (item, index) => {
Text(item['title'])
...
// 当flag为true时,width为15%;当flag为false时,width为95%
.width(this.flag ? '15%': '95%')
.animation({ duration: 2000, curve: item['curve'] })
}, item => JSON.stringify(item))
```
### 完整代码
```ts
import Curves from '@ohos.curves';
@Entry
@Component
struct Index {
// 状态变量flag,用于监听按钮按下状态
@State flag: boolean = true
private items: object[] = [
{
color: '#a320bf',
title: '线性曲线',
// curve类型曲线:表示动画从头到尾的速度都是相同的。
curve: Curve.Linear,
},
{
color: '#17d4be',
title: '低速结束曲线',
// curve类型曲线:表示动画以低速结束
curve: Curve.EaseInOut,
},
{
color: '#0e2d8c',
title: '平滑曲线',
// curve类型曲线:平滑曲线
curve: Curve.Smooth,
},
{
color: '#d4bb19',
title: '弹簧曲线',
// ohos.curve导入的弹簧曲线动画,以初速度为20的弹簧进行平移
curve: Curves.springCurve(20, 1, 1, 1.2),
},
]
build() {
Flex({
direction: FlexDirection.Column,
justifyContent: FlexAlign.Start,
alignItems: ItemAlign.Center
}) {
Column({ space: 50 }) {
ForEach(this.items, (item, index) => {
Text(item['title'])
.fontSize(20)
.fontColor('#FFFFFF')
// 当flag为true时,width为15%;当flag为false时,width为95%;
.width(this.flag ? '15%': '95%')
.height(80)
.textAlign(TextAlign.Start)
.backgroundColor(item['color'])
// 2s之内完成动画展示,每条动画曲线按照item['curve']去展示
.animation({ duration: 2000, curve: item['curve'] })
.borderRadius(20)
.padding({left:10})
}, item => JSON.stringify(item))
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding({ left: 12, right: 12, top: 22, bottom: 22 })
Button(){
Text("Go!")
.fontSize(20)
.fontColor('#fff')
}
.borderRadius(20)
.fontColor('#FFFFFF')
.fontSize('12fp')
.fontWeight(FontWeight.Bolder)
.backgroundColor('#15587c')
.padding(25)
.margin({top:50})
// 通过点击事件,反转flag的值
.onClick(() => {
this.flag = !this.flag
})
}
.height('100%')
.backgroundColor('#F1F3F5')
.padding({ left: '3%', right: '3%' })
}
}
```
## 参考
[属性动画](../application-dev/reference/arkui-ts/ts-animatorproperty.md#属性动画)
[Curve](../application-dev/reference/arkui-ts/ts-appendix-enums.md#curve)
[弹簧曲线动画](../application-dev/ui/arkts-spring-animation.md)
[Flex布局](../application-dev/reference/arkui-ts/ts-universal-attributes-flex-layout.md)
[Listitem](../application-dev/reference/arkui-ts/ts-container-listitem.md)
[List](../application-dev/reference/arkui-ts/ts-container-list.md)
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册