首页 > 应用开发 > IT综合 > HarmonyOS 属性动画开发示例(ArkTS)
利用ArkUI组件不仅可以实现属性变化引起的属性动画,也可以实现父组件状态变化引起子组件产生动画效果,这种动画为显式动画。效果如图所示:
显式动画 :提供全局animateTo显式动画接口来指定由于闭包代码导致的状态变化插入过渡动效。
属性动画 :组件的某些通用属性变化时,可以通过属性动画实现渐变过渡效果,提升用户体验。支持的属性包括width、height、backgroundColor、opacity、scale、rotate、translate等。
Slider :滑动条组件,通常用于快速调节设置值,如音量调节、亮度调节等应用场景。
源码下载
我们首先需要完成HarmonyOS开发环境搭建,可参照如图步骤进行。
DevEco Studio 版本:DevEco Studio 3.1 Release。
HarmonyOS SDK 版本:API version 9。
设备类型:华为手机或运行在DevEco Studio上的华为手机设备模拟器。
HarmonyOS系统:3.1.0 Developer Release。
1. 安装DevEco Studio,详情请参考 下载和安装软件 。
2. 设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
● 如果可以直接访问Internet,只需进行 下载HarmonyOS SDK 操作。
● 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考 配置开发环境 。
3. 开发者可以参考以下链接,完成设备调试的相关配置:
本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在源码下载或gitee中提供。
├──entry /src/ main /ets / / 代码区
│ ├──common
│ │ └──constants
│ │ └──Const . ets // 常量类
│ ├──entryability
│ │ └──EntryAbility . ts // 程序入口类
│ ├──pages
│ │ └──Index . ets // 动效页面入口
│ ├──view
│ │ ├──AnimationWidgets . ets // 动画组件
│ │ ├──CountController . ets // 图标数量控制组件
│ │ └──IconAnimation . ets // 图标属性动画组件
│ └──viewmodel
│ ├──IconItem . ets // 图标类
│ ├──Point . ets // 图标坐标类
│ └──IconsModel . ets // 图标数据模型
└──entry /src/ main /resources / / 资源文件
页面入口由AnimationWidgets(动效组件)、CountController(动效图标数量控制组件)组成。
其中CountController通过Slider滑动控制quantity(动效图标数量);AnimationWidgets根据quantity展示相应数量的图标,点击组件按钮后通过在animateTo的event闭包函数中改变mainFlag状态,跟mainFlag相关的样式属性的变化都会产生动画效果。
// Index.ets
@Entry
@Component
struct Index {
@State quantity : number = Common . IMAGES_MIN ;
@Provide iconModel : IconsModel = new IconsModel ( this . quantity , Common . OFFSET_RADIUS );
build () {
Column () {
// 动画组件
AnimationWidgets ({
quantity : $ quantity
})
// 图标数量控制组件
CountController ({
quantity : $ quantity
})
}
...
}
}
CountController组件通过Slilder滑动控制动效图标的数量,最少3个图标,最多6个图标。
// CountController.ets
@Component
export struct CountController {
@Link quantity : number ;
build () {
Column () {
Row () {
Text ($ r ( 'app.string.count' ))
. textStyle ()
Text ( this . quantity )
. textStyle ()
}
...
Slider ({
value : this . quantity ,
min : Common . IMAGES_MIN ,
max : Common . IMAGES_TOTAL ,
step : 1 ,
style : SliderStyle . InSet
})
. blockColor ( Color . White )
. selectedColor ($ r ( 'app.color.SliderSelectColor' ))
. showSteps ( true )
. trackThickness ($ r ( 'app.float.size_20' ))
. onChange (( value : number ) => {
this . quantity = value ;
})
...
}
}
}
点击AnimationWidgets组件的中心图标,调用animateTo方法,在event回调方法中改变状态,从而对组件本身产生缩放动画,和图标位置变化的动画效果,效果如图所示:
在animationTo的回调中修改mainFlag状态,所有跟mainFlag状态相关的属性变化都会产生过渡动画效果。
// AnimationWidgets.ets
export struct AnimationWidgets {
@State mainFlag : boolean = false ;
@Link @Watch ( 'onQuantityChange' ) quantity : number ;
@Consume iconModel : IconsModel ;
onQuantityChange () { // 监听图标数量的变化,并修改图标数据以及对应的坐标位置
this . iconModel . addImage ( this . quantity );
}
aboutToAppear () {
this . onQuantityChange ();
}
animate () {
animateTo (
{
delay : Common . DELAY_10 ,
tempo : Common . TEMPO ,
iterations : 1 ,
duration : Common . DURATION_500 ,
curve : Curve . Smooth ,
playMode : PlayMode . Normal
}, () => {
this . mainFlag = ! this . mainFlag ;
})
}
build () {
Stack () {
Stack () {
ForEach ( this . iconModel . imagerArr , ( item : IconItem ) => {
IconAnimation ({
item : item ,
mainFlag : $ mainFlag
})
}, ( item : IconItem ) => JSON . stringify ( item . index ))
}
. rotate ({
x : 0 ,
y : 0 ,
z : 1 ,
angle : this . mainFlag ? Common . ROTATE_ANGLE_360 : 0
})
...
Image (
this . mainFlag
? $ r ( "app.media.imgActive" )
: $ r ( "app.media.imgInit" )
)
. scale ({
x : this . mainFlag ? Common . INIT_SCALE : 1 ,
y : this . mainFlag ? Common . INIT_SCALE : 1
})
. onClick (() => {
this . iconModel . reset (); // 重置图标激活状态
this . animate (); // 启动显式动画
})
...
}
}
}
组件的通用属性发生变化时,可以创建属性动画进行渐变,提升用户体验。示例效果如图所示:
当组件由animation动画属性修饰时,如果自身属性发生变化会产生过渡动画效果。本示例中当点击小图标时会触发自身clicked状态的变化,所有跟clicked相关的属性变化(如translate、rotate、scale、opacity)都会被增加动画效果。
// IconAnimation.ets
export struct IconAnimation {
@Link mainFlag : boolean ;
@ObjectLink item : IconItem ;
build () {
Image ( this . item . image )
. width ( Common . ICON_WIDTH )
. height ( Common . ICON_HEIGHT )
. objectFit ( ImageFit . Contain )
. translate (
this . mainFlag
? { x : this . item . point . x , y : this . item . point . y }
: { x : 0 , y : 0 }
)
. rotate ({
x : 0 ,
y : 1 ,
z : 0 ,
angle : this . item . clicked ? Common . ROTATE_ANGLE_360 : 0
})
. scale (
this . item . clicked
? { x : Common . SCALE_RATIO , y : Common . SCALE_RATIO }
: { x : 1 , y : 1 }
)
. opacity ( this . item . clicked ? Common . OPACITY_06 : 1 )
. onClick (() => {
this . item . clicked = ! this . item . clicked ;
})
. animation (
{
delay : Common . DELAY_10 ,
duration : Common . DURATION_1000 ,
iterations : 1 ,
curve : Curve . Smooth ,
playMode : PlayMode . Normal
}
)
}
}
根据图标数量计算图标位置。
// IconsModel.ets
import Common from '../common/constants/Const' ;
import IconItem from './IconItem' ;
import Point from './Point' ;
const TWO_PI : number = 2 * Math . PI ;
@Observed
export class IconsModel {
public imagerArr : Array < IconItem > = [];
private num : number = Common . IMAGES_MIN ;
private radius : number ;
constructor ( num : number , radius : number ) {
this . radius = radius ;
this . addImage ( num );
}
public addImage ( num : number ) {
this . num = num ;
if ( this . imagerArr . length === num ) {
return ;
}
if ( this . imagerArr . length > num ) {
this . imagerArr . splice ( num , this . imagerArr . length - num );
} else {
for ( let i = this . imagerArr . length ; i < num ; i ++ ) {
const point = this . genPointByIndex ( i );
this . imagerArr . push ( new IconItem ( i , Common . IMAGE_RESOURCE [ i ], false , point ));
}
}
this . refreshPoint ( num );
}
public refreshPoint ( num : number ) {
for ( let i = 0 ; i < num ; i ++ ) {
this . imagerArr [ i ]. point = this . genPointByIndex ( i );
}
}
public genPointByIndex ( index : number ): Point {
const x = this . radius * Math . cos ( TWO_PI * index / this . num );
const y = this . radius * Math . sin ( TWO_PI * index / this . num );
return new Point ( x , y );
}
public reset () {
for ( let i = 0 ; i < this . num ; i ++ ) {
if ( this . imagerArr [ i ]. clicked ) {
this . imagerArr [ i ]. clicked = false ;
}
}
}
}
您已经完成了本次Codelab的学习,并了解到以下知识点:
1. 如何使用animateTo实现显式动画。
2. 如何使用animation为组件添加属性动画。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70047792/viewspace-2996849/ ,如需转载,请注明出处,否则将追究法律责任。