动画实现方式:属性动画、关键帧动画、转场动画
效果
实现代码
@Entry
@Component
struct Index {
private tabsController: TabsController = new TabsController()
@State currentTabIndex: number = 0
@State iconScale: number = 1; // 默认不缩放,在关键帧中设置缩放大小
private uiContent: UIContext = this.getUIContext?.();
@Builder
tabBar(index: number, label: string, unSelectIcon: ResourceStr, selectIcon: ResourceStr) {
Column() {
if (this.currentTabIndex === index) { // 避免使用三目表达式控制当前index显示的icon,因为只需要为激活状态的图标设置动画
Image(selectIcon)
.width(30)
.height(30)
.scale({ x: this.iconScale, y: this.iconScale }) // 绑定属性
} else {
Image(unSelectIcon)
.width(30)
.height(30)
}
Text(label).fontSize(14).fontColor(this.currentTabIndex === index ? "#9c5ff3" : "#8a8a8a")
.padding({ top: 6 })
}
}
build() {
Column() {
Tabs({ controller: this.tabsController }) {
TabContent() {
Text("首页")
.fontSize(30)
.fontWeight(FontWeight.Bold)
}.tabBar(this.tabBar(0, "首页", $r("app.media.ic_home"), $r("app.media.ic_home_")))
TabContent() {
Text("VIP")
.fontSize(30)
.fontWeight(FontWeight.Bold)
}.tabBar(this.tabBar(1, "VIP", $r("app.media.ic_vip"), $r("app.media.ic_vip_")))
TabContent() {
Text("我的")
.fontSize(30)
.fontWeight(FontWeight.Bold)
}.tabBar(this.tabBar(2, "我的", $r("app.media.ic_mine"), $r("app.media.ic_mine_")))
}
.onContentWillChange((currentIndex: number, comeIndex: number) => {
this.currentTabIndex = comeIndex
if (!this.uiContent) {
this.iconScale = 1
} else {
this.uiContent.keyframeAnimateTo( //设置关键帧动画
{ iterations: 1 }, // 关键帧播放次数为一次
[{
duration: 200,
event: () => {
this.iconScale = 0.7 // 先缩放到0.7倍
}
}, {
duration: 150,
curve: Curve.EaseOut,
event: () => {
this.iconScale = 1 // 恢复正常大小
}
}])
}
return true
})
// .onChange((index:number)=>{
// this.currentTabIndex = index
// }) // 避免在onChange中切换index,onChange是切换tebContent切换完成后触发的回调
.barPosition(BarPosition.End)
.scrollable(false)
}
}
}
原理【消除延迟】
要实现动画对于点击没有延迟,需要先消除tab点击的延迟效果。Tabs的tabbar通过CustomBuilder自定义底部tabBar导航栏的时候,点击tab会有TabContent切换完成后才对tabbar切换,这是因为CustomBuilder中切换显示图标是根据tabbar的index改变来切换的,onchange里面对index进行修改导致切换状态的时间和点击tab的时间对应不上,所以看上去有延迟的效果,onchange回调是在tab已经切换之后才触发的,对于这个问题,可以将实现变更index的代码在onContentWillChange中实现即可做到点击和切换同时响应【官方建议在onAnimationStart中监听并刷新当前索引】。
onChange的效果(明显延迟)
onContentWillChange效果
另外,如果不使用CustomBuilder,在默认样式的tabar中存在的延迟效果,可以通SubTabBarStyle来实现消除点击后tabContent和tabBar之间切换动画的延迟
SubTabBarStyle效果
代码:
TabContent() {
Column() {
Text("我的").fontSize(30).fontWeight(FontWeight.Bold)
}
.height("100%").width("100%").justifyContent(FlexAlign.End).padding({ bottom: 40 })
}.tabBar(new SubTabBarStyle("我的"))
原理【关键帧动画】
同过关键帧动画实现tabbar切换后将图标的大小缩放到指定倍数(关键帧)又后回到原始大小,效果上呈现出点击回弹效果。
参考:鸿蒙关于关键帧动画的文档:【关键帧动画 (keyframeAnimateTo)】
扩展(实现更复杂的动画样式)
通过转场动画和其它动画接口实现更复杂的动画效果
添加转场动画
private appearEffect : TransitionEffect = TransitionEffect.OPACITY.animation({
curve: curves.springMotion(0.6, 0.8) // 默认透明度转场效果,指定springMotion(0.6, 0.8)曲线
})
.combine(TransitionEffect.rotate({ angle: 90 }))// 添加平移转场效果
.combine(TransitionEffect.translate({ y: 10 }).animation({ curve: curves.springMotion() }))// 添加move转场效果
为image组件绑定动画
if (this.currentTabIndex === index) { // 避免使用三目表达式控制当前index显示的icon,因为只需要为激活状态的图标设置动画
Image(selectIcon)
.width(30)
.height(30)
.transition(this.appearEffect) // 添加转场效果
.scale({ x: this.iconScale, y: this.iconScale }) // 绑定属性
} else {
Image(unSelectIcon)
.width(30)
.transition(this.appearEffect) // 同时为未激活icon添加转场效果
.height(30)
}