经过前段时间陆续的折腾,我们的小青蛙已经初具雏形:能看(界面还原)、能听(点击叫声)。但是,作为一名追求极致体验的开发者,我总觉得它还差点意思。
差在哪里?手感。
现在的青蛙,点击下去虽然有叫声,但视觉反馈非常生硬,要么不动,要么突变。今天,我们就利用 Kuikly 强大的动画能力,给这只青蛙注入"灵魂",让它不仅能叫,还能动得丝滑、动得有趣。
我们主要实现两个效果:
- 按压回弹:点击时青蛙会有丝滑的缩小和回弹效果,模拟真实的按压感。
- 动态气泡:点击时头顶冒出"孤寡 +1"的气泡,并向上飘动渐隐。
实战一:丝滑的按压回弹 (Animation)
在之前的代码中,我们可能尝试过用 Scale 来做缩放,但如果没有动画过渡,状态的改变是瞬间完成的,看起来就像画面"闪"了一下,体验很差。
Kuikly 提供了声明式的 animation 属性。它能将动画绑定到特定的状态变量上,当该状态发生变化时,自动应用预设的动画曲线。
1. 改造青蛙按钮
我们需要做两件事:
- 使用
animation属性,绑定isFrogAnimating状态,并设置动画曲线(如Animation.easeInOut)。 - 简化状态逻辑,只需要控制"按下"和"恢复"两个状态即可。
kotlin
// 引入动画类
import com.tencent.kuikly.core.base.Animation
// 青蛙按钮的 View
View {
attr {
// ... 其他属性保持不变 ...
// 【关键点1】绑定动画状态
// 当 isFrogAnimating 变化时,应用时长 0.1s 的 easeInOut 动画
animation(Animation.easeInOut(0.1f), this@FrogMainPage.isFrogAnimating)
// 【关键点2】根据状态应用缩放
if (this@FrogMainPage.isFrogAnimating) {
transform(Scale(0.9f, 0.9f)) // 按下状态:缩小到 90%
} else {
transform(Scale(1f, 1f)) // 正常状态:恢复原大小
}
}
// ...
}
2. 优化点击逻辑
有了 animation,我们的点击处理逻辑就可以变得非常简单。我们只需要把状态设为 true(按下),稍作延迟后再设回 false(恢复),Kuikly 会自动帮我们处理中间的动画过程。
kotlin
event {
click {
// ... 播放声音等逻辑 ...
// 触发按压动画
this@FrogMainPage.isFrogAnimating = true
// 100ms 后恢复状态
// 配合上面的 animation,会形成:缩小 -> (过渡) -> 恢复 的回弹效果
setTimeout(100) {
this@FrogMainPage.isFrogAnimating = false
}
}
}
就是这么简单!加上这一行 animation,青蛙的点击手感瞬间就从"PPT"变成了"果冻"。
实战二:冒出的"孤寡"气泡 (动态列表动画)
接下来我们要实现一个更有趣的效果:每点一次,青蛙头顶就冒出一个"孤寡 +1"的气泡,然后慢慢向上飘并消失。
这涉及到三个技术点:
- 动态数据:气泡是动态生成的,可能有多个。
- 列表渲染:需要根据数据动态渲染 UI。
- 组合动画:气泡需要同时进行位移(向上)和透明度(渐隐)变化。
1. 定义气泡数据
首先,我们需要一个数据类来描述气泡的状态。
kotlin
// 定义在 FrogMainPage 类内部或外部
data class Bubble(
val id: Long, // 唯一标识,用于列表更新优化
val x: Float, // X轴位置
val y: Float, // Y轴位置
val opacity: Float // 透明度
)
然后在页面中定义一个状态变量来存储当前所有的气泡:
kotlin
// 使用 observable 监听列表变化
private var bubbles: List<Bubble> by observable(emptyList())
// ID 生成器
private var bubbleIdCounter = 0L
2. 渲染气泡列表
在 body 中,我们使用 forEach 遍历 bubbles 列表,为每个气泡生成一个 Text 组件。
kotlin
// 动态气泡层(放在青蛙容器内)
this@FrogMainPage.bubbles.forEach { bubble ->
Text {
attr {
text("孤寡 +1")
fontSize(18f)
color(Color(0xFF2E7D32L))
fontWeightBold()
// 【关键点1】绝对定位
positionAbsolute()
top(bubble.y) // 绑定 Y 坐标
left(bubble.x) // 绑定 X 坐标
opacity(bubble.opacity) // 绑定透明度
// 【关键点2】为位置和透明度开启过渡动画
// 时长 0.8s,让它飘得慢一点
animation(Animation.easeOut(0.8f), bubble.y)
animation(Animation.easeIn(0.8f), bubble.opacity)
}
}
}
3. 生成与动画逻辑
这是最精彩的部分。在点击事件中,我们不仅要生成气泡,还要"导演"它的整个生命周期。
kotlin
click {
// ... 其他逻辑 ...
// 1. 生成新气泡(初始状态)
val newBubble = Bubble(
id = this@FrogMainPage.bubbleIdCounter++,
x = 100f, // 初始位置
y = 0f, // 初始位置
opacity = 1f // 初始不透明
)
// 加入列表(触发 UI 渲染出初始状态的气泡)
val currentList = this@FrogMainPage.bubbles.toMutableList()
currentList.add(newBubble)
this@FrogMainPage.bubbles = currentList
// 2. 触发动画(目标状态)
// 使用 setTimeout(16) 确保在下一帧执行,让初始状态先上屏
setTimeout(16) {
val animatedList = this@FrogMainPage.bubbles.map {
if (it.id == newBubble.id) {
// 修改 Y 坐标(向上飘)和透明度(消失)
it.copy(y = -100f, opacity = 0f)
} else it
}
this@FrogMainPage.bubbles = animatedList
}
// 3. 动画结束后清理垃圾
// 800ms 是我们设置的动画时长
setTimeout(800) {
val cleanedList = this@FrogMainPage.bubbles.filter { it.id != newBubble.id }
this@FrogMainPage.bubbles = cleanedList
}
}
这里用到了一个小技巧:两段式状态更新。
- 先添加气泡(初始位置),让它渲染出来。
- 下一帧修改气泡属性(目标位置),利用
animation自动生成向上飘动的动画。 - 最后移除数据,释放内存。
Kuikly 动画原理总结
通过这两个实战,我们可以总结出 Kuikly 开发动画的核心理念:状态驱动 (State-driven)。
在传统的 Android 开发中,我们可能习惯于使用 ObjectAnimator 去直接操作 View 对象。但在 Kuikly(以及 Flutter、Compose、React 等声明式框架)中,我们不直接操作 UI ,而是操作状态。
- Animation 的魔力 :我们只需要配置
animation属性将状态变量与动画曲线绑定,系统会自动处理状态变化时的插值计算。 - 数据即 UI :气泡的动画本质上就是
Bubble数据对象中y和opacity属性的变化。
掌握了这个理念,你就能用 Kuikly 实现出各种复杂炫酷的交互效果。
下篇预告
现在我们的青蛙已经很灵动了,但还有一个大问题:每次重启 App,孤寡次数都会归零!这怎么能行?这可是用户辛辛苦苦积攒的功德啊。
下一篇,我们将深入探讨 状态管理与数据持久化,给青蛙加上"记忆",让这份孤寡永流传。