Kuikly基础之动画实战:让孤寡青蛙“活”过来

经过前段时间陆续的折腾,我们的小青蛙已经初具雏形:能看(界面还原)、能听(点击叫声)。但是,作为一名追求极致体验的开发者,我总觉得它还差点意思。

差在哪里?手感。

现在的青蛙,点击下去虽然有叫声,但视觉反馈非常生硬,要么不动,要么突变。今天,我们就利用 Kuikly 强大的动画能力,给这只青蛙注入"灵魂",让它不仅能叫,还能动得丝滑、动得有趣。

我们主要实现两个效果:

  1. 按压回弹:点击时青蛙会有丝滑的缩小和回弹效果,模拟真实的按压感。
  2. 动态气泡:点击时头顶冒出"孤寡 +1"的气泡,并向上飘动渐隐。

实战一:丝滑的按压回弹 (Animation)

在之前的代码中,我们可能尝试过用 Scale 来做缩放,但如果没有动画过渡,状态的改变是瞬间完成的,看起来就像画面"闪"了一下,体验很差。

Kuikly 提供了声明式的 animation 属性。它能将动画绑定到特定的状态变量上,当该状态发生变化时,自动应用预设的动画曲线。

1. 改造青蛙按钮

我们需要做两件事:

  1. 使用 animation 属性,绑定 isFrogAnimating 状态,并设置动画曲线(如 Animation.easeInOut)。
  2. 简化状态逻辑,只需要控制"按下"和"恢复"两个状态即可。
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"的气泡,然后慢慢向上飘并消失。

这涉及到三个技术点:

  1. 动态数据:气泡是动态生成的,可能有多个。
  2. 列表渲染:需要根据数据动态渲染 UI。
  3. 组合动画:气泡需要同时进行位移(向上)和透明度(渐隐)变化。

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
    }
}

这里用到了一个小技巧:两段式状态更新

  1. 先添加气泡(初始位置),让它渲染出来。
  2. 下一帧修改气泡属性(目标位置),利用 animation 自动生成向上飘动的动画。
  3. 最后移除数据,释放内存。

Kuikly 动画原理总结

通过这两个实战,我们可以总结出 Kuikly 开发动画的核心理念:状态驱动 (State-driven)

在传统的 Android 开发中,我们可能习惯于使用 ObjectAnimator 去直接操作 View 对象。但在 Kuikly(以及 Flutter、Compose、React 等声明式框架)中,我们不直接操作 UI ,而是操作状态

  • Animation 的魔力 :我们只需要配置 animation 属性将状态变量与动画曲线绑定,系统会自动处理状态变化时的插值计算。
  • 数据即 UI :气泡的动画本质上就是 Bubble 数据对象中 yopacity 属性的变化。

掌握了这个理念,你就能用 Kuikly 实现出各种复杂炫酷的交互效果。

下篇预告

现在我们的青蛙已经很灵动了,但还有一个大问题:每次重启 App,孤寡次数都会归零!这怎么能行?这可是用户辛辛苦苦积攒的功德啊。

下一篇,我们将深入探讨 状态管理与数据持久化,给青蛙加上"记忆",让这份孤寡永流传。

相关推荐
小帆聊前端1 小时前
深度解读虚拟列表:从原理到实战,解决长列表渲染性能难题
前端·javascript
2***c4351 小时前
nginx服务器实现上传文件功能_使用nginx-upload-module模块
服务器·前端·nginx
p***93031 小时前
Java进阶之泛型
android·前端·后端
木易 士心1 小时前
Element UI 多级菜单缩进的动态控制:从原理到工程化实践
前端·vue.js·ui
狮子座的男孩1 小时前
js函数高级:03、详解原型与原型链(原型、显式原型与隐式原型、原型链、原型链属性、探索instanceof、案例图解)及相关面试题
前端·javascript·经验分享·显示原型与隐式原型·原型链及属性·探索instanceof·原型与原型链图解
烛阴1 小时前
C#继承与多态全解析,让你的对象“活”起来
前端·c#
狗哥哥1 小时前
Swagger对接MCP服务:赋能AI编码的高效落地指南
前端·后端
zl_vslam1 小时前
SLAM中的非线性优-3D图优化之相对位姿Between Factor(六)
前端·人工智能·算法·计算机视觉·slam se2 非线性优化
申阳1 小时前
Day 18:01. 基于 SpringBoot4 开发后台管理系统-快速了解一下 SpringBoot4 新特性
前端·后端·程序员