属性动画的使用
概述
属性动画,是最为基础的动画,其功能强大、使用场景多,应用范围较广。常用于如下场景中:
- 一、页面布局发生变化。例如添加、删除部分组件元素。
- 二、页面元素的可见性和位置发生变化。例如显示或者隐藏部分元素,或者将部分元素从一端移动到另外一端。
- 三、页面中图形图片元素动起来。例如使页面中的静态图片动起来。
简单来说,属性动画是组件的通用属性发生改变时而产生的属性渐变效果。如下图所示,其原理是,当组件的通用属性发生改变时,组件状态由初始状态逐渐变为结束状态的过程中,会创建多个连续的中间状态,逐帧播放后,就会形成属性渐变效果,从而形成动画。
属性动画的使用方式也非常简单,只需要给组件(包括基础组件和容器组件)添加animation属性,并设置好参数,如下代码所示:
Image($r('app.media.image1'))
.animation({
duration: 1000,
tempo: 1.0,
delay: 0,
curve: Curve.Linear,
playMode: PlayMode.Normal,
iterations: 1
})
2 创建属性动画页面
如下图所示,在该下拉刷新动画场景中,一共有6个属性动画。头部中的五个图标的移动放大动画中,每个图标都是单独的一个动画,其共同组合成一个刷新等待动画。最后是下方组件上移的一个移动动画。为方便理解,图中下方的内容将以图片来代替实际应用的功能页面。
图2-1 :示例动画
该6个属性动画创建方式类似,以五个图标放大移动动画的为例来讲解如何创建属性动画。
首先,创建一个头部刷新组件RefreshAnimHeader,在其中自定义一个图标组件AttrAnimIcons,用Image组件将资源图标引入,并设置好样式,如下所示:
@Component
export default struct RefreshAnimHeader {
...
@Builder AttrAnimIcons(iconItem) {
Image(iconItem.imgRes)
.width(this.iconWidth)
.position({ x: iconItem.posX })
.objectFit(ImageFit.Contain)
.animation({
duration: 2000,
tempo: 3.0,
delay: iconItem.delay,
curve: Curve.Linear,
playMode: PlayMode.Alternate,
iterations: -1
})
}
...
}
然后在build方法中使用Row容器组件,将自定义的图标组件引入,并设置好样式,同时定义组件状态iconWidth,添加onApper事件,修改iconWidth的值,使其从30变为100,触发UI状态更新。
@Component
export default struct RefreshAnimHeader {
...
@State iconWidth: number = 30;
private onStateCheck() {
if (this.state === RefreshState.REFRESHING) {
this.iconWidth = 100;
} else {
this.iconWidth = 30;
}
}
build() {
Row() {
ForEach(CommonConstants.REFRESH_HEADER_FEATURE, (iconItem) => {
this.AttrAnimIcons(iconItem)
}, item => item.toString())
}
.width("100%")
.height("100%")
.onAppear(() => {
this.onStateCheck();
})
}
}
运行代码,即可看到五个图标的移动放大动画效果。
1、animation属性作用域。animation自身也是组件的一个属性,其作用域为animation之前。即产生属性动画的属性须在animation之前声明,其后声明的将不会产生属性动画。以示例中的五个图标动画为例,我们期望产生动画的属性为Image组件的width属性,故该属性width需在animation属性之前声明。如果将该属性width在animation之后声明,则不会产生动画效果。
2、产生属性动画的属性变化时需触发UI状态更新。在本示例中,产生动画的属性width,其值是通过变量iconWidth从30变为100,故该变量iconWidth的改变需触发UI状态更新。
3、产生属性动画的属性本身需满足一定的要求,并非任何属性都可以产生属性动画。目前支持的属性包括width、height、position、opacity、backgroundColor、scale、rotate、translate等
3 属性动画参数调整
属性动画中animation的参数如下:
属性名称 | 属性类型 | 默认值 | 描述 |
duration | number | 1000 | 动画时长,单位为毫秒,默认时长为1000毫秒。 |
tempo | number | 1.0 | 动画的播放速度,值越大动画播放越快,值越小播放越慢,为0时无动画效果。 |
curve | Curve | Curve.Linear | 动画变化曲线,默认曲线为线性。 |
delay | number | 0 | 延时播放时间,单位为毫秒,默认不延时播放。 |
iterations | number | 1 | 播放次数,默认一次,设置为-1时表示无限次播放。 |
playMode | PlayMode | PlayMode.Normal | 设置动画播放模式,默认播放完成后重头开始播放。 |
onFinish | function | - | 动画播放结束时回调该函数。 |
其中变化曲线curve枚举值为:
名称 | 描述 |
Linear | 表示动画从头到尾的速度都是相同的。 |
Ease | 表示动画以低速开始,然后加快,在结束前变慢,CubicBezier(0.25, 0.1, 0.25, 1.0)。 |
EaseIn | 表示动画以低速开始,CubicBezier(0.42, 0.0, 1.0, 1.0)。 |
EaseOut | 表示动画以低速结束,CubicBezier(0.0, 0.0, 0.58, 1.0)。 |
EaseInOut | 表示动画以低速开始和结束,CubicBezier(0.42, 0.0, 0.58, 1.0)。 |
FastOutSlowIn | 标准曲线,cubic-bezier(0.4, 0.0, 0.2, 1.0)。 |
LinearOutSlowIn | 减速曲线,cubic-bezier(0.0, 0.0, 0.2, 1.0)。 |
FastOutLinearIn | 加速曲线,cubic-bezier(0.4, 0.0, 1.0, 1.0)。 |
ExtremeDeceleration | 急缓曲线,cubic-bezier(0.0, 0.0, 0.0, 1.0)。 |
Sharp | 锐利曲线,cubic-bezier(0.33, 0.0, 0.67, 1.0)。 |
Rhythm | 节奏曲线,cubic-bezier(0.7, 0.0, 0.2, 1.0)。 |
Smooth | 平滑曲线,cubic-bezier(0.4, 0.0, 0.4, 1.0)。 |
Friction | 阻尼曲线,CubicBezier(0.2, 0.0, 0.2, 1.0)。 |
播放模式playMode枚举值为:
名称 | 描述 |
Normal | 动画按正常播放。 |
Reverse | 动画反向播放。 |
Alternate | 动画在奇数次(1、3、5...)正向播放,在偶数次(2、4、6...)反向播放。 |
AlternateReverse | 动画在奇数次(1、3、5...)反向播放,在偶数次(2、4、6...)正向播放。 |
本文以参数delay和onFinish为例来演示和讲解属性动画的参数调整。其他参数的效果可自行尝试。
延时播放时间delay的设置
在单个的组件元素的属性动画中,一般不设置参数delay的值。而在需要设置时,往往是需要在动画开始前做一些准备工作,具体依场景而定,本文在此不讨论。
在由多个组件元素的属性动画组合的动画中,例如示例动画中的五个图标的属性动画组合而成的刷新等待动画,通过设置参数delay的值,可以使各个组件元素之间按照一定的秩序依次播放,形成跌宕起伏、鳞次栉比的动画效果。在此场景中,该值的大小又与duration相关联。
该如何设置各个图标的参数delay的值呢?
在设置delay值之前,我们先理解一个概念:延时间距。其意思是两个图标组件的延时参数delay的差值,即:delay2-delay1=延时间距。要想实现五个图标之间以良好的秩序先后移动放大,各个图标之间的延时间距是一样的,例如延时间距为100ms时,此五个图标的延时delay可以分别设置为100ms、200ms、300ms、400ms、500ms。
在实际开发场景中,我们该如何确定延时间距呢?
在此有个经验可以参考:在延时间距不超过动画时长duration时,总延时间距越接近duration,秩序性越好。其中,总延时间距为延时间距与图标数量的乘积,即:延时间距*图标数量=总延时间距。
故此,我们在设置参数delay时,需要确定动画时长duration的值。该值默认为1000ms,具体时长可依据具体的业务需要来决定。
在本示例动画中,图标动画时长duration为2000ms,故延时间距为2000ms/5=400ms,五个图标的延时参数delay可分别设置为400ms、800ms、1200ms、1600ms、2000ms。其效果如示例图所示,图标先后秩序明显,视觉效果良好。
onFinish回调函数的使用
参数onFinish与参数iterations有关。当参数iterations播放结束时,会调用onFinish函数来进行后续的业务处理。例如提示动画播放结束。
Image(iconItem.imgRes)
.width(this.iconWidth)
.position({ x: iconItem.posX })
.objectFit(ImageFit.Contain)
.animation({
duration: 2000,
tempo: 3.0,
delay: iconItem.delay,
curve: Curve.Linear,
playMode: PlayMode.Normal,
iterations: 1,
onFinish: () => {
prompt.showToast({ message:"动画播放结束!!!" })
}
})
当iterations设置为-1时,表示无限次播放,则onFinish回调函数不会被调用。
4 关闭属性动画页面
此处需要将关闭属性动画区别开来:
- 属性动画关闭,是指动画播放结束,但是动画组件依然存在并显示在页面上。
- 关闭属性动画页面,是指将动画的组件删除或者隐藏起来。
在本示例动画中,指将头部刷新组件RefreshAnimHeader隐藏起来。该如何实现呢?
首先,在组件RefreshAnimHeader中添加变量state,并用@Consume监听其值的变化,同时添加条件渲染逻辑,根据state的值来判断是否需要关闭。当state变为IDLE状态时,表示需要关闭属性动画页面。
@Component
export default struct RefreshAnimHeader {
@Consume(RefreshConstants.REFRESH_STATE_TAG) @Watch('onStateCheck') state: RefreshState;
build() {
Row() {
if (this.state !== RefreshState.IDLE) { // start or stop animation when idle state.
ForEach(CommonConstants.REFRESH_HEADER_FEATURE, (iconItem) => {
this.AttrAnimIcons(iconItem)
}, item => item.toString()}
}
}
.width(CommonConstants.FULL_LENGTH)
.height(CommonConstants.FULL_LENGTH)
.onAppear(() => {
this.onStateCheck();
})
}
}
其次,在本示例中,通过下方图片的上移属性动画来关闭刷新组件RefreshAnimHeader。在组件RefreshComponent中,通过@Consume与组件RefreshAnimHeader的@Consume进行间接绑定,修改state变量的值为IDLE状态即可关闭属性动画页面。
@Component
export default struct RefreshComponent {
@Consume(RefreshConstants.REFRESH_STATE_TAG) @Watch('onStateChanged') state: RefreshState;
build() {
List({ scroller: this.listController }) {
ListItem() {
...
}
}
.animation({
curve: Curve.Smooth,
duration: RefreshConstants.REFRESH_HEADER_ANIM_DURATION,
playMode: PlayMode.Normal,
onFinish: () => {
if (this.headerOffset === -RefreshConstants.REFRESH_HEADER_HEIGHT) {
this.state = RefreshState.IDLE;
}
}
})
}
5 参考
具体代码信息请参考Codelab:自定义下拉刷新动画(ArkTS)
案例:自定义下拉刷新动画(ArkTS)
介绍
介绍
本篇Codelab主要介绍组件动画animation属性设置。当组件的某些通用属性变化时,可以通过属性动画实现渐变效果,提升用户体验。
本Codelab使用的display接口处于mock阶段,在预览器上使用会显示白屏现象,可选择在真机或模拟器上运行。
相关概念
- 属性动画:组件的某些通用属性变化时,可以通过属性动画实现渐变效果,提升用户体验。支持的属性包括width、height、backgroundColor、opacity、scale、rotate、translate等。案例中自定义头部组件的属性动画设置主要涉及duration(动画时长)、tempo(动画速率)、delay(动画延时)、curve(动画曲线)、palyMode(动画模式)和iterations(动画播放次数)。
完整示例
gitee源码地址
源码下载
自定义下拉刷新动画(ArkTS).zip
环境搭建
我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
软件要求
- DevEco Studio版本:DevEco Studio 3.1 Release。
- HarmonyOS SDK版本:API version 9。
硬件要求
- 设备类型:华为手机或运行在DevEco Studio上的华为手机设备模拟器。
- HarmonyOS系统:3.1.0 Developer Release。
环境搭建
- 安装DevEco Studio,详情请参考下载和安装软件。
- 设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
- 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
- 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
- 开发者可以参考以下链接,完成设备调试的相关配置:
- 使用真机进行调试
- 使用模拟器进行调试
代码结构解读
本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在源码下载或gitee中提供。
No Preview
- ├──entry/src/main/ets // 代码区
- │ ├──common
- │ │ ├──constants
- │ │ │ ├──CommonConstants.ets // 公共常量类
- │ │ │ └──RefreshConstants.ets // 下拉刷新常量类
- │ │ └──utils
- │ │ ├──DimensionUtil.ets // 屏幕适配工具类
- │ │ └──GlobalContext.ets // 全局上下文工具类
- │ ├──entryability
- │ │ └──EntryAbility.ets // 程序入口类
- │ ├──pages
- │ │ ├──FileManagerIndex.ets // 文件管理Tab页
- │ │ └──TabIndex.ets // Tab管理页
- │ ├──view
- │ │ ├──RefreshAnimHeader.ets // 动画刷新组件
- │ │ ├──RefreshComponent.ets // 下拉刷新组件
- │ │ └──RefreshDefaultHeader.ets // 默认刷新组件
- │ └──viewmodel
- │ ├──AnimationModel.ets // 动画封装类
- │ └──CardModel.ets // 页签封装类
- └──entry/src/main/resources // 资源文件目录
自定义下拉组件
自定义下拉刷新通过自定义List组件RefreshComponent实现。在List容器中添加自定义刷新头部组件和其它的需要刷新部件,RefreshComponent提供了头部样式设置,刷新部件样式设置和刷新回调方法设置。
- 头部样式设置。本Codelab提供了DEFAULT默认刷新样式和CLOUD云朵动画刷新样式设置,在RefreshComponent组件初始化时,判断当前刷新样式进行渲染。
- 刷新部件样式。刷新部件样式itemLayout为嵌入RefreshComponent组件中的元素,通过@BuilderParam装饰符定义,可根据具体业务需求,当前为默认的Image组件样式。
- 刷新回调方法设置。在手指拖拽刷新头部滑出的过程中计算滑出的距离是否超出可刷新距离,松开手指时如果超出可刷新距离则调用“onRefresh”方法。
No Preview
// RefreshComponent.ets
// 设置RefreshComponent刷新组件state状态的更新。
@Consume(RefreshConstants.REFRESH_STATE_TAG) @Watch('onStateChanged') state: number;
private onStateChanged() {
switch (this.state) {
case RefreshState.REFRESHING:
if (this.onRefresh !== undefined) {
this.onRefresh();
}
break;
...
}
}
// 监听RefreshComponent中List组件的触摸事件。
List({ scroller: this.listController }) {
ListItem() {
Column() {
...
}
...
}
}
...
.onTouch((event?: TouchEvent) => {
if (!event) {
return;
}
switch (event.type) {
case TouchType.Down:
if (this.state === RefreshState.IDLE) {
this.state = RefreshState.DRAGGING;
}
break;
case TouchType.Move:
if (this.state === RefreshState.DRAGGING
&& this.listController.currentOffset().yOffset <= -RefreshConstants.REFRESH_EFFECTIVE_HEIGHT) {
this.state = RefreshState.DRAGGING_REFRESHABLE;
}
break;
case TouchType.Up:
if (this.state === RefreshState.DRAGGING_REFRESHABLE) {
this.headerOffset = 0;
this.state = RefreshState.REFRESHING;
}
break;
default:
break;
}
// FileManagerIndex.ets
//onRefresh事件没有做相关刷新,只做了模拟延时操作,开发者可以自行加入真实网络加载动作。
onRefresh: () => {
setTimeout(() => {
this.state = RefreshState.COMPLETE;
}, CommonConstants.REFRESH_DEFAULT_TIMEOUT);
}
自定义刷新动画
本Codelab中自定义刷新是由5个图片的组合动画效果。
- 每个Image通过iconItem参数分别设置各自的x轴偏移量和延时播放的属性动画效果。
- 监听RefreshComponent刷新组件state状态的变化,当前状态为REFRESHING状态时,改变@State修饰的图片宽度变量iconWidth来启动动画。
No Preview
// RefreshAnimHeader.ets
@Consume(RefreshConstants.REFRESH_STATE_TAG) @Watch('onStateCheck') state: number;
private onStateCheck() {
if (this.state === RefreshState.REFRESHING) {
this.iconWidth = CommonConstants.REFRESH_HEADER_ITEM_SCALE_WIDTH;
} else {
this.iconWidth = CommonConstants.REFRESH_HEADER_ITEM_DEFAULT_WIDTH;
}
}
总结
您已经完成了本次Codelab的学习,并了解到以下知识点:
- 使用属性动画实现自定义下拉组件。
- 使用属性动画实现自定义刷新组件。