系列文章目录
HarmonyOS应用开发02-程序框架UIAbility、启动模式与路由跳转
HarmonyOS应用开发03-基础组件-让我们来码出复杂酷炫的UI
HarmonyOS应用开发05-让我们的界面动起来1!-显示动画与属性动画
上周没有更新博客(嗯,我是不会按时更新的,bushi,😎),一周都在各种Conference中度过,太忙了,忙的我都不知道干了啥 😭。 可能是梦里忙于new object
吧,(当然也可能是在嗑CP)哈哈哈。
对了,喜欢我的可以点点赞,加个收藏,最好能添加个关注,你的喜欢是我写作(胡说)的动力(好youni,一点都不像我这个i人本人的亚子⚡)
前言
本节记录学习下HamronyOS中的动画,运用到组件动画、转场动画等场景,让我们的UI界面炫酷起来~
先看下实现效果:
一、动画
动画的原理是在一段时间内,多次改变UI的外观,由于人眼会产生视觉停留,所以最终看到的是一个"连续"的动画。UI的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率FPS(Frame Per Second),即每秒的动画帧数,帧率越高则动画就会越流畅。
ArkUI中,产生动画的方式是改变属性值且指定动画参数。动画参数包含了如动画时长、变化规律(即曲线)等参数。当属性值发生变化后,按照动画参数,从原来的状态过渡到新的状态,即形成一个动画。
1、页面分类
ArkUI提供的动画能力按照页面
的分类方式,可分为页面内的动画
和页面间的动画
。页面内的动画指在一个页面内即可发生的动画,页面间的动画指两个页面跳转时才会发生的动画。
2、基础能力分类
按照基础能力分,可分为属性动画、显式动画、转场动画三部分。
二、属性动画
在HarmonyOS-ArkTS中,显示动画和属性动画是两种不同的动画类型,它们的主要区别在于动画的驱动方式和目标对象。
1、属性动画:
属性动画
则关注于对页面元素的属性
进行改变,例如元素的尺寸、颜色、透明度等。比如在FullCustomTransition.ets的Column组件中添加TransitionElement组件,并定义pageTransition方法。然后给Clomn组件添加opacity、scale、rotate
属性,定义变量animValue用来控制Clomn组件的动效,在PageTransitionEnter和PageTransitionExit组件中动态改变myProgress的值,从而控制动画效果。
属性动画是对页面元素的属性进行改变,这种动画的目的是通过改变元素的属性来提供更丰富的视觉效果和动态交互。 通过属性动画实现一个元素的逐渐放大或缩小、颜色的渐变等效果。
注意: 属性动画animation属性作用域:animation自身也是组件的一个属性,其作用域为animation之前。 即产生属性动画的属性须在animation之前声明,其后声明的将不会产生属性动画。 显式动画把要执行动画的属性的修改放在闭包函数中触发动画,而属性动画则无需使用闭包,把animation属性加在要做属性动画的组件的属性后即可
2、参数分析:
typescript
declare class CommonMethod<T> {
animation(value: AnimateParam): T;
}
animation :开启组件的属性动画,该方法在 CommonMethod
中定义,这就说明组件只要继承了 CommonMethod
,那么它将具有属性动画的能力,该方法一个 AnimateParam
参数:
- duration:动画的执行时间,单位为毫秒,默认1000毫秒。
- curve:动画插值器,默认曲线为线性。
- delay:动画延迟执行的时长,单位为毫秒,默认为0毫秒。
- iterations:动画的执行次数,默认为1次,当设置为-1时表示循环执行。
- playMode:动画的播放模式,默认播放完成后重头开始播放。
3、示例:
旋转动画示例:
效果图:
代码:
typescript
@Component
export default struct StudentListItem {
// 是否选中当前Item
@State isChecked: boolean = false;
// 列表Item子项Image点击事件
onItemChildImageClick: () => void
...
// Image旋转角度
@State rotateAngle: number = 0
build() {
Column() {
...
if (this.isChecked) {
Row() {
Text(this.studentData.title + " is very handsome!")
.fontSize('18fp')
.fontColor(Color.Blue)
Blank()
Image(this.studentData.image)
.objectFit(ImageFit.Cover)
.width('50vp')
.height('50vp')
.borderRadius(100)
.onClick(() => {
this.rotateAngle = 360
})
// 属性动画旋转Image
.rotate({ x: 0, y: 0, z: 1, angle: this.rotateAngle })
.animation({
duration: 5000,
tempo: 1.0,
delay: 0,
curve: Curve.Linear,
playMode: PlayMode.Normal,
iterations: -1 // 设、/置-1表示动画无限循环
})
}
.width('100%')
.padding({ left: 10, right: 10 })
.justifyContent(FlexAlign.SpaceAround)
}
}
.borderRadius(22)
.backgroundColor($r('app.color.start_window_background'))
.width('100%')
.height(this.isChecked ? $r('app.float.list_item_expand_height') : $r('app.float.list_item_height'))
.onClick(() => {
this.isChecked = !this.isChecked;
// 改变旋转角度
this.rotateAngle = 0
// 收起非点击Item
this.clickIndex = this.index;
})
}
}
二、显示动画
1、显示动画:
显示动画
主要关注在UI界面之间的切换效果
,页面转场时的自定义转场动效,例如在全局pageTransition
方法内配置页面入场(PageTransitionEnter)
和页面退场(PageTransitionExit)
时的自定义转场动效。在鸿蒙ArkTS中,可以通过PageTransitionEnter
和PageTransitionExit
组件来实现显示动画。这种动画的目的是在两个界面之间建立一个平滑的过渡,使用户体验更加流畅。例如,在页面切换时,可以通过显示动画实现一个类似于卡片翻转或淡入淡出的效果。
显示动画由全局方法 animateTo
实现,它主要是解决由于闭包代码导致的状态变化而插入的动画效果。
2、参数分析:
typescript
declare function animateTo(value: AnimateParam, event: () => void): void;
-
value :设置动画的具体配置参数,
AnimateParam
:- duration:设置动画的执行时长,单位为毫秒,默认为1000毫秒。
- tempo:设置动画的播放速度,值越大动画播放越快,值越小播放越慢,默认值为1,为0时无动画效果。
- curve :设置动画曲线,默认值为
Linear
。 - delay:设置动画的延迟执行时间,单位为毫秒,默认值为0不延迟。
- iterations:设置动画的执行次数,默认值为1次,设置为-1时无限循环。
- playMode:设置动画播放模式,默认播放完成后重头开始播放。
- onFinish:动画播放完成的回调。
-
event :指定显示动效的闭包函数,在闭包函数中导致的状态变化系统会自动插入过渡动画。
在使用ArkTS框架时,显示动画和属性动画的实现方式也有所不同。通常,显示动画可以通过使用ArkTS提供的转场动画组件或API来实现,而属性动画则需要通过编写相应的代码来设置元素的属性并使用ArkTS的动画系统来驱动。
总结来说,显示动画主要关注页面转场的视觉效果,而属性动画则更侧重于改变页面元素的属性。
3、示例:
效果图:
图一,点击卡片Item,展开卡片内容,是不是感觉效果很生硬,不平滑;第二张图加了动画效果后是不是看上去就显得很丝滑了。
代码实现:
不加动画:
typescript
import router from '@ohos.router';
import CommonConstants from '../common/constants/CommonConstants';
import { DataItemBean } from '../viewmodel/DataItemBean';
import prompt from '@system.prompt';
// import DataItemBean from '../viewmodel/DataItemBean';
@Component
export default struct StudentListItem {
...
// Image旋转角度
@State rotateAngle: number = 0
...
build() {
if (this.isListModel) {
Column() {
if (this.isChecked) {
Row() {
Text(this.studentData.title + " is very handsome!")
.fontSize('18fp')
.fontColor(Color.Blue)
Blank()
Image(this.studentData.image)
.objectFit(ImageFit.Cover)
.width('50vp')
.height('50vp')
.borderRadius(100)
.onClick(() => {
this.rotateAngle = 360
})
// 属性动画旋转Image
.rotate({ x: 0, y: 0, z: 1, angle: this.rotateAngle })
.animation({
duration: 5000,
tempo: 1.0,
delay: 0,
curve: Curve.Linear,
playMode: PlayMode.Normal,
iterations: -1 // 设、/置-1表示动画无限循环
})
}
.width('100%')
.padding({ left: 10, right: 10 })
.justifyContent(FlexAlign.SpaceAround)
}
}
.borderRadius(22)
.backgroundColor($r('app.color.start_window_background'))
.width('100%')
.height(this.isChecked ? $r('app.float.list_item_expand_height') : $r('app.float.list_item_height'))
// 属性动画
// animation属性作用域:animation自身也是组件的一个属性,其作用域为animation之前。
// 即产生属性动画的属性须在animation之前声明,其后声明的将不会产生属性动画
// 显式动画把要执行动画的属性的修改放在闭包函数中触发动画,而属性动画则无需使用闭包,把animation属性加在要做属性动画的组件的属性后即可
.animation({ duration: 600 })
.onClick(() => {
this.isChecked = !this.isChecked;
this.rotateAngle = 0
// 收起非点击Item
this.clickIndex = this.index;
})
} else {
...
}
}
加显示动画:
typescript
import router from '@ohos.router';
import CommonConstants from '../common/constants/CommonConstants';
import { DataItemBean } from '../viewmodel/DataItemBean';
import prompt from '@system.prompt';
// import DataItemBean from '../viewmodel/DataItemBean';
@Component
export default struct StudentListItem {
...
// Image旋转角度
@State rotateAngle: number = 0
...
build() {
if (this.isListModel) {
Column() {
if (this.isChecked) {
Row() {
Text(this.studentData.title + " is very handsome!")
.fontSize('18fp')
.fontColor(Color.Blue)
Blank()
Image(this.studentData.image)
.objectFit(ImageFit.Cover)
.width('50vp')
.height('50vp')
.borderRadius(100)
.onClick(() => {
this.rotateAngle = 360
})
// 属性动画旋转Image
.rotate({ x: 0, y: 0, z: 1, angle: this.rotateAngle })
.animation({
duration: 5000,
tempo: 1.0,
delay: 0,
curve: Curve.Linear,
playMode: PlayMode.Normal,
iterations: -1 // 设、/置-1表示动画无限循环
})
}
.width('100%')
.padding({ left: 10, right: 10 })
.justifyContent(FlexAlign.SpaceAround)
}
}
.borderRadius(22)
.backgroundColor($r('app.color.start_window_background'))
.width('100%')
.height(this.isChecked ? $r('app.float.list_item_expand_height') : $r('app.float.list_item_height'))
// 属性动画
// animation属性作用域:animation自身也是组件的一个属性,其作用域为animation之前。
// 即产生属性动画的属性须在animation之前声明,其后声明的将不会产生属性动画
// 显式动画把要执行动画的属性的修改放在闭包函数中触发动画,而属性动画则无需使用闭包,把animation属性加在要做属性动画的组件的属性后即可
.animation({ duration: 600 })
.onClick(() => {
// this.isChecked = !this.isChecked;
// 组件内转场动画需要配合 animateTo 才能生效,动效时长、曲线、延时跟随 animateTo 中的配置
// 📢:组件内的转场动画时长,动画曲线等动画参数以 animationTo 方法设置的为基准
// 显示动画-添加展开动画
animateTo({ duration: 1000 }, () => {
this.isChecked = !this.isChecked;
})
this.rotateAngle = 0
// this.rotateAngle = 360
// 收起非点击Item
this.clickIndex = this.index;
})
} else {
...
}
}
第三张图可以看到点击其他卡片Item的时候,已展开的卡片会自动伸缩回原始状态,核心代码是在点击事件里添加了这行代码实现的:
this.clickIndex = this.index;
总结
以上展示了HarmonyOS-ArkUI中的显示动画与属性动画使使用动画可以让我们的UI界面以及交互效果更加丰富、平滑,还可以使用动画实现更多更复杂的交互、UI效果,就看产品和U及设计怎么chao了(bushi,你俩别看,这是恶评 😜)
接下来会继续学习掌握界面转场动画,实现界面跳转自定义动画效果,使我们的页面间的跳转不那么枯燥。 继续加油! 👊