diff --git a/zh-cn/third-party-cases/Readme-CN.md b/zh-cn/third-party-cases/Readme-CN.md index 928a7d1d27be9ccc2513d34f718b2a63d4c48a62..a340c39d6361bb1d6710b64844d5f68a211b450f 100644 --- a/zh-cn/third-party-cases/Readme-CN.md +++ b/zh-cn/third-party-cases/Readme-CN.md @@ -20,6 +20,7 @@ - [如何实现列表的二级联动](interact-lists.md) - [时钟开发示例](time-styles-shift.md) - [弹簧动画开发](how-to-develop-spring-animation.md) +- [水波纹动画开发](water-wave-animation.md) ### 网络管理 - [如何请求并加载网络图片](how-to-load-images-from-internet.md) diff --git a/zh-cn/third-party-cases/figures/waterwaves.gif b/zh-cn/third-party-cases/figures/waterwaves.gif new file mode 100644 index 0000000000000000000000000000000000000000..b285e8e5590fb883eb7036196a8604af9aa67827 Binary files /dev/null and b/zh-cn/third-party-cases/figures/waterwaves.gif differ diff --git a/zh-cn/third-party-cases/water-wave-animation.md b/zh-cn/third-party-cases/water-wave-animation.md new file mode 100644 index 0000000000000000000000000000000000000000..305ffd1756b21efe65b5e3ca1457fd8fe50018b2 --- /dev/null +++ b/zh-cn/third-party-cases/water-wave-animation.md @@ -0,0 +1,302 @@ +# 趣味动画-水波纹动画 + +## 场景介绍 +在日常应用中,水波纹的效果比较常见,例如听曲识歌,附近搜索等等,本文就以数字按键为例介绍水波纹动效的实现。 + +## 效果呈现 +本例最终效果图如下: + +![waterwaves](figures/waterwaves.gif) + +## 环境要求 +本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发: +- IDE: DevEco Studio 3.1 Beta2 +- SDK: Ohos_sdk_public 3.2.11.9(API Version 9 Release) +## 实现思路 +本实例涉及到的主要特性及其实现方案如下: +* UI框架:使用Grid,GuidItem等容器组件组建UI框架。 +* 按钮渲染:通过自定义pwdBtn组件(含Column、Button、Stack、Text等关键组件以及visibility属性),进行数字按钮的渲染。 +* 按钮状态变化:设置状态变量unPressed,控制按钮的当前状态,向Column组件添加onTouch事件,监听按钮的当前状态。 + * 默认状态为按钮放开状态(unPressed为true)。 + * 当按钮按下时,更新按钮的状态(unPressed:true -> false)。 + * 当按钮放开时,更新按钮的状态(unPressed:false -> true)。 +* 按钮动画展示:使用 属性动画以及组件内转场动画 绘制按钮不同状态下的动画。 + * 当按钮按下时, 使用显式动画(animateTo)加载动画:渲染按下时的Row组件,同时加载水波的聚拢效果。 + * 当按钮放开时, 使用组件内转场加载动画:渲染放开时的Row组件,同时加载水波的扩散效果。 +## 开发步骤 +针对实现思路中所提到的内容,具体关键开发步骤如下: +1. 先通过Grid,GuidItem等容器组件将UI框架搭建起来,在GuidItem中引用步骤2中的自定义数字按钮pwdBtn构建出数字栅格。 + + 具体代码如下: + + ```ts + private pwdGrid: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, -1, 0, -1] + ... + Column() { + Grid() { + ForEach(this.pwdGrid, (item: number, index: number) => { + GridItem() { + ... + } + }, item => item) + } + .columnsTemplate('1fr 1fr 1fr') + .rowsTemplate('1fr 1fr 1fr 1fr') + .columnsGap(10) + .rowsGap(10) + .width(330) + .height(440) + }.width('100%').height('100%') + ``` + +2. 通过Column、Button、Stack、Text等关键组件以及visibility属性构建自定义数字按钮pwdBtn。 + + 具体代码如下: + + ```ts + @Component + struct pwdBtn { + ··· + build() { + Column() { + Button() { + stack(){ + ... + Text(`${this.item}`).fontSize(30) + } + ... + } + .backgroundColor('#ccc') + .type(ButtonType.Circle) + .borderRadius(100) + .width(100) + .height(100) + } + .visibility(this.item == -1 ? Visibility.Hidden : Visibility.Visible) + .borderRadius(100) + } + } + ``` + +3. 设置状态变量unPressed,监听当前数字按钮的状态,同时向Column组件添加onTouch事件,获取并更新按钮的当前状态,从而可以根据监听到的按钮状态加载对应的动画效果。 + + 具体代码块如下: + + ```ts + //状态变量unPressed,用于监听按钮按下和放开的状态 + @State unPressed: boolean = true + ... + // 添加onTouch事件,监听状态 + .onTouch((event: TouchEvent) => { + // 当按钮按下时,更新按钮的状态(unPressed:true -> false) + if (event.type == TouchType.Down) { + animateTo({ duration: 400 }, () => { + this.unPressed = !this.unPressed + this.currIndex = this.index + }) + } + // 当按钮放开时,更新按钮的状态(unPressed:false -> true) + if (event.type == TouchType.Up) { + animateTo({ duration: 400 }, () => { + this.unPressed = !this.unPressed + }) + } + }) + ``` + +4. 根据按钮组件的按下/放开状态,通过if-else语句选择插入的Row组件,并随之呈现不同的水波动画效果(按下时水波聚拢,放开时水波扩散)。 + + 具体代码块如下: + + ```ts + Stack() { + Row() { + // 判断当前按钮组件为放开状态 + if (this.unPressed && this.currIndex == this.index) { + // 插入Row组件,配置过渡效果 + Row() + .customStyle() + .backgroundColor('#fff') + // 水波纹扩散动画:从Row组件的中心点开始放大,scale{0,0}变更scale{1,1}(完整显示) + .transition({ + type: TransitionType.Insert, + opacity: 0, + scale: { x: 0, y: 0, centerY: '50%', centerX: '50%' } + }) + } + // 判断当前按钮组件为按下状态 + else if (!this.unPressed && this.currIndex == this.index) { + // 插入Row组件,配置过渡效果 + Row() + .customStyle() + .backgroundColor(this.btnColor) + .scale(this.btnScale) + .onAppear(() => { + // 水波纹聚拢动画:Row组件backgroundColor属性变更(#ccc -> #fff),插入动画过渡效果,scale{1,1}(完整显示)变化为scale{0,0} + animateTo({ duration: 300, + // 聚拢动画播放完成后,需要衔接扩散动画,Row组件backgroundColor属性变更(#fff -> #ccc),插入动画过渡效果,scale{0,0}变化为scale{1,1}(完整显示) + onFinish: () => { + this.btnColor = '#ccc' + this.btnScale = { x: 1, y: 1 } + } }, + () => { + this.btnColor = '#fff' + this.btnScale = { x: 0, y: 0 } + }) + }) + } + // 其他状态 + else { + Row() + .customStyle() + .backgroundColor('#fff') + } + } + .justifyContent(FlexAlign.Center) + .alignItems(VerticalAlign.Center) + .borderRadius(100) + Text(`${this.item}`).fontSize(30) + } + .customStyle() + ``` + +## 完整代码 +示例代码如下: + +```ts +@Entry +@Component +export default struct dragFire { + private pwdGrid: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, -1, 0, -1] + + build() { + Column() { + Grid() { + ForEach(this.pwdGrid, (item: number, index: number) => { + GridItem() { + pwdBtn({ item: item, index: index }) + } + }, item => item) + } + .columnsTemplate('1fr 1fr 1fr') + .rowsTemplate('1fr 1fr 1fr 1fr') + .columnsGap(10) + .rowsGap(10) + .width(330) + .height(440) + }.width('100%').height('100%') + } +} + + +@Component +struct pwdBtn { + private currIndex: number = -1 + //状态变量unPressed,用于控制按钮的状态 + @State unPressed: boolean = true + @State btnColor: string = '#ccc' + index: number + item: number + @State btnScale: { + x: number, + y: number + } = { x: 1, y: 1 } + + @Styles customStyle(){ + .width('100%') + .height('100%') + .borderRadius(100) + } + + build() { + Column() { + Button() { + Stack() { + Row() { + // 判断当前组件为放开状态 + if (this.unPressed && this.currIndex == this.index) { + // 插入Row组件,配置过渡效果 + Row() + .customStyle() + .backgroundColor('#fff') + // 水波纹扩散动画:Row组件backgroundColor属性变更(#fff -> #ccc),系统插入动画过渡效果,从组建的中心点开始放大,scale{0,0}变更scale{1,1} + .transition({ + type: TransitionType.Insert, + opacity: 0, + scale: { x: 0, y: 0, centerY: '50%', centerX: '50%' } + }) + } + // 判断当前组件为按下状态 + else if (!this.unPressed && this.currIndex == this.index) { + // 插入Row组件,配置过渡效果 + Row() + .customStyle() + .backgroundColor(this.btnColor) + .scale(this.btnScale) + .onAppear(() => { + // 水波纹聚拢动画:Row组件backgroundColor属性变更(#ccc -> #fff),插入动画过渡效果,scale{1,1}变化为scale{0,0} + animateTo({ duration: 300, + // 聚拢动画播放完成后,需要衔接扩散动画,此时Row组件backgroundColor属性变更(#fff -> #ccc),插入动画过渡效果,scale{0,0}变化为scale{1,1} + onFinish: () => { + this.btnColor = '#ccc' + this.btnScale = { x: 1, y: 1 } + } }, + () => { + this.btnColor = '#fff' + this.btnScale = { x: 0, y: 0 } + }) + }) + } + // 其他状态 + else { + Row() + .customStyle() + .backgroundColor('#fff') + } + } + .justifyContent(FlexAlign.Center) + .alignItems(VerticalAlign.Center) + .borderRadius(100) + + Text(`${this.item}`).fontSize(30) + } + .customStyle() + } + .stateEffect(false) + .backgroundColor('#ccc') + .type(ButtonType.Circle) + .borderRadius(100) + .width(100) + .height(100) + } + .visibility(this.item == -1 ? Visibility.Hidden : Visibility.Visible) + .borderRadius(100) + // onTouch事件,监听状态 + .onTouch((event: TouchEvent) => { + // 当按钮按下时,更新按钮的状态(unPressed:true -> false) + if (event.type == TouchType.Down) { + animateTo({ duration: 400 }, () => { + this.unPressed = !this.unPressed + this.currIndex = this.index + }) + } + // 当按钮放开时,更新按钮的状态(unPressed:false -> true) + if (event.type == TouchType.Up) { + animateTo({ duration: 400 }, () => { + this.unPressed = !this.unPressed + }) + } + }) + } +} +``` +## 参考 +[Guid](../application-dev/reference/arkui-ts/ts-container-grid.md) + +[GridItem](../application-dev/reference/arkui-ts/ts-container-griditem.md) + +[显式动画](../application-dev/reference/arkui-ts/ts-explicit-animation.md) + +[组件内转场](../application-dev/reference/arkui-ts/ts-transition-animation-component.md) + +[Stack](../application-dev/reference/arkui-ts/ts-container-stack.md) \ No newline at end of file