属性动画,是最为基础的动画,其功能强大、使用场景多,应用范围较广。常用于如下场景中:
简单来说,属性动画是组件的通用属性发生改变时而产生的属性渐变效果。如下图所示,其原理是,当组件的通用属性发生改变时,组件状态由初始状态逐渐变为结束状态的过程中,会创建多个连续的中间状态,逐帧播放后,就会形成属性渐变效果,从而形成动画。
属性动画的使用方式也非常简单,只需要给组件(包括基础组件和容器组件)添加animation属性,并设置好参数,如下代码所示:
Image($r('app.media.image1'))
.animation({
duration: 1000,
tempo: 1.0,
delay: 0,
curve: Curve.Linear,
playMode: PlayMode.Normal,
iterations: 1
})
如下图所示,在该下拉刷新动画场景中,一共有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等
属性动画中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的值,可以使各个组件元素之间按照一定的秩序依次播放,形成跌宕起伏、鳞次栉比的动画效果。在此场景中,该值的大小又与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与参数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回调函数不会被调用。
此处需要将关闭属性动画区别开来:
在本示例动画中,指将头部刷新组件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;
}
}
})
}
具体代码信息请参考Codelab:自定义下拉刷新动画(ArkTS)