【动图+代码】如何实现丝滑入场动效|鸿蒙动效开发笔记 02 原创
这篇笔记将介绍一种 HarmonyOS NEXT 开发里,让组件丝滑入场的动效实现方式!
ヽ( ̄ω ̄( ̄ω ̄〃)ゝ 前置知识:
GIF 案例,从 .animation() 到 Curves.springMotion|鸿蒙动效笔记 01
和一点点鸿蒙开发基础(工程结构、ArkTS 语法)。
参考代码:
整个项目已经开源:
Linys_Ani_Examples_NEXT on Github
Linys_Ani_Examples_NEXT on Github
// defaults/defaults.ets
// 使用这个文件存储诸多默认值,以供统一调用
export function fontSize_Extra_Large() {
return 36;
}
export function fontSize_Large() {
return 24;
}
export function fontSize_Normal() {
return 16;
}
export function fontSize_Icon_Button() {
return 25;
}
export function click_effect_default() {
let ce: ClickEffect = { level: ClickEffectLevel.LIGHT };
return
// pages/FadeInExamples.ets
// 展示的页面
import Curves from '@ohos.curves';
import { click_effect_default, fontSize_Extra_Large, fontSize_Large } from '../defaults/defaults';
@Entry
@Component
struct FadeInExample {
@State response: number = 0.55;
@State dampingFraction: number = 0.825;
@State labels: string[] = ["Meow", "¯\\_(ツ)_/¯", "( •̀ ω •́ )✧", "在一起", "(~o ̄3 ̄)~", "就可以!"]
build() {
Column({ space: 10 }) {
Row({ space: 15 }) {
Navigator({ target: 'pages/Index', type: NavigationType.Back }) {
Text('')
.fontWeight(FontWeight.Bold)
.fontSize(fontSize_Extra_Large())
.clickEffect(click_effect_default())
}
Text('浮现入场')
.fontWeight(FontWeight.Bold)
.fontSize(fontSize_Large())
}
.alignItems(VerticalAlign.Center)
Scroll() {
Column({ space: 10 }) {
ForEach(this.labels, (text: string, index: number) => {
item({ text: text, timeout: index })
})
}
}.edgeEffect(EdgeEffect.Spring)
}
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
.padding({ left: 20, right: 20, top: 10 })
}
}
@Component
struct item {
@State text: string = "";
@State offset_y: number = 100;
@State opa: number = 0;
@State timeout: number = 0;
build() {
Column() {
Text(this.text)
.fontColor($r('app.color.start_window_background'))
.fontWeight(FontWeight.Bold)
.fontSize(fontSize_Large())
Text(this.timeout.toString())
.fontWeight(FontWeight.Bold)
.fontSize(fontSize_Extra_Large())
.fontColor($r('app.color.start_window_background'))
.opacity(0.4)
.textAlign(TextAlign.End)
.width("100%")
}
// Basics
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Start)
.backgroundColor($r('app.color.color_accent'))
.width("100%")
.borderRadius(22)
.padding(16)
// Entry animation
.offset({ y: this.offset_y })
.opacity(this.opa)
.animation({ curve: Curves.springMotion(0.5, 0.7) })
.onAppear(() => {
setTimeout(() => {
this.opa = 1;
this.offset_y = 0;
}, (this.timeout) * 100)
})
}
}
实现效果:
整体思路:
为了实现浮现的效果,我们从两个方面入手:【浮】和【现】。
【浮】
即上升效果,在这里我们用组件的 .offset() 属性实现。
.offset({ y: this.offset_y
于是通过更改 offset_y,我们可以让组件“灵魂出窍”——组件在布局上占有位置不变,只是显示上产生偏移,这个特性能避免一些布局错乱的问题。
(注意!在设置了 .offset() 的时候,组件检测点击(onClick)的位置也是不变的,并不会随着显示偏移而判定偏移!)
【现】
即出现(淡入)效果,我们可以通过组件的 .opacity() 属性实现。
.opacity(this.opa)
这里的 opacity 指的是不透明度,1 为完全显示。0 为完全消失(但是占的空间还是占住的)。
只要我们让组件一开始 .opacity(0),一段时间后再 .opacity(1) 就能实现延迟显示的效果。这可以通过改变 opa 实现。
延迟变化
在每个组件被渲染出来的时候,它们都会执行一遍 .onAppear(()=>{}) 属性中所写的代码。
那么我们只需要在这个属性的回调中写一个延迟执行的函数,让它在被渲染出来(但是不透明度为0,并设置了位置偏移 )的时候就开始等,等一段时间后再真正地显示(不透明度设置为 1,位置偏移设置为 0)就能实现。
可以使用 .setTimeout(()=>{}, ms) 设置倒计时:
onAppear(() => {
setTimeout(() => {
this.opa = 1;
this.offset_y = 0;
}, (this.timeout) * 100)
})
(并且对于列表中的每一项,这个等待的时间应该要一个比一个长,来实现“依次浮现”的效果。只要在 ForEach(如果你用了 ForEach 的话)里将组件本身就依次变化的索引 index 赋值给 this.timeout 就可以了。)
ForEach(this.labels, (text: string, index: number) => {
item({ text: text, timeout: index })
})
过渡动画
然后便是过渡动画了。这个部分相对简单,只需要写一行 .animation() 就可以。
如果搭配上上一篇笔记所记述的 springMotion,我们就可以实现一个 Q 弹的动画效果。
.animation({ curve: Curves.springMotion(0.5, 0.7) })
更多后话
这个效果的实现还是相对比较简单,且基础的。
不过文中所写的这个版本尚有诸多不足,如:当列表超级超级长的时候,一个个弹出的动画就太长了,没准我都划到底了,还在等上面的组件一个个浮现出来。
解决起来倒也简单——只需要另外写一个判断,一开始渲染的时候,只对在屏幕内的列表项目进行延迟时间的累计,在看不到的地方就统一延迟相同的数值(反正也看不见),这样整个动画的时长不会超过屏幕内项目数*延迟间隔。这个的代码实现就交给诸位读者啦~
同理,这个效果的属性也不局限于位置偏移和不透明度,模糊、颜色等都可以按照这个思路进行渐变/渐显,加入这个入场动画。
最后的最后,感谢你读到这里!咱们下一篇鸿蒙动效开发笔记再见!!(~o ̄3 ̄)~