Kuikly基础之音频播放与资源管理:青蛙叫声实现

在上一篇文章中,我们成功地让青蛙对我们的点击做出了视觉上的响应------一个生动的缩放动画。现在,我们的应用不再是静态的了,但似乎还缺点什么。

没错,就是声音!交互不仅仅是视觉上的,听觉上的反馈同样重要。如果每次点击青蛙,它都能发出一声清脆的"呱",那应用的趣味性和沉浸感无疑会更上一层楼。

今天,我们就来完成这个任务:为我们的"单身青蛙"应用加上音效,让它在被点击时,能真实地"叫"出来。同时,我们也会探讨在Kuikly中如何进行基础的音频资源管理。

初步计划

在动手之前,我们自然是先去翻了翻Kuikly的文档,想找找有没有现成的音频播放组件,比如一个叫<Audio>的家伙。结果发现,似乎并没有这样一个专门为播放短音效而生的轮子。

不过,没有"直路",我们可以"绕路"走嘛。Kuikly提供了一个功能强大的<Video>组件。既然视频能包含声音,我们是不是可以利用它来只播放声音呢?这个想法听起来不错。

我们的计划是:准备一个音频文件(就是我们之前准备好的frog_sound.mp3),然后把它"伪装"成一个视频源交给<Video>组件。为了不让它在界面上"碍眼",我们再把它设置成一个1x1像素的、完全透明的"隐形"播放器。

实战

好了,理论有了,开干!

我们最初的想法很简单直接:既然拿到了<Video>组件的引用(ViewRef),那在点击事件里直接调用它的play()方法不就行了?就像这样:

kotlin 复制代码
// 理想中的代码...
ctx.soundPlayerRef.view?.play() 

然而,现实很快给了我们一拳,编译器无情地报错:Unresolved reference: play。好吧,看来此路不通。这种命令式的、直接操作视图的方式,似乎并不符合Kuikly的"口味"。

这次碰壁让我们冷静下来,重新思考Kuikly的设计哲学------声明式UI。我们不应该去"命令"一个组件去播放,而应该"声明"它的状态是"正在播放"。

于是,新的方案诞生了:

  1. 定义一个observable的状态变量 soundPlayControl,用来控制播放行为(播放/暂停)。
  2. <Video>组件的playControl属性绑定到这个状态上。
  3. 当用户点击青蛙时,我们不再调用方法,而是去更新soundPlayControl的状态为VideoPlayControl.PLAY
  4. 监听<Video>playStateDidChanged事件,当播放结束(状态变为PlayState.PLAY_END)时,再将soundPlayControl的状态改回VideoPlayControl.PAUSE,为下一次播放做准备。

这个思路听起来就"地道"多了!它完美地融入了整个应用的响应式数据流。

最终代码

说干就干,我们立刻对FrogMainPage.kt进行了改造,最终的代码如下:

kotlin 复制代码
// ... 省略部分import ...
import com.tencent.kuikly.core.base.ViewRef
import com.tencent.kuikly.core.views.PlayState
import com.tencent.kuikly.core.views.Video
import com.tencent.kuikly.core.views.VideoPlayControl
import com.tencent.kuikly.core.views.VideoView

// ...

internal class FrogMainPage : BasePager() {

    // ... 省略其他状态变量 ...
    lateinit var soundPlayerRef: ViewRef<VideoView>
    // 新增一个控制音频播放的状态
    private var soundPlayControl: VideoPlayControl by observable(VideoPlayControl.PAUSE)

    override fun body(): ViewBuilder {
        val ctx = this
        return {
            // ... 省略大部分UI布局 ...
            
                        // 主青蛙按钮
                        View {
                            // ... 省略部分 attr ...
                            event {
                                 click {
                                     // ... 省略其他点击事件逻辑 ...
                                     
                                     // 更新状态,触发音频播放
                                     this@FrogMainPage.soundPlayControl = VideoPlayControl.PLAY
                                 }
                             }
                        }
            
            // ... 省略部分UI布局 ...

                // "隐形"的音频播放器
                Video {
                    ref {
                        ctx.soundPlayerRef = it
                    }
                    attr {
                        src("media/frog_sound.mp3")
                        width(1f)
                        height(1f)
                        opacity(0f)
                        // 将播放行为和我们的状态绑定
                        playControl(this@FrogMainPage.soundPlayControl)
                    }
                    event {
                        // 监听播放状态
                        playStateDidChanged { state, _ ->
                            // 播放结束后,重置状态以便下次播放
                            if (state == PlayState.PLAY_END) {
                                this@FrogMainPage.soundPlayControl = VideoPlayControl.PAUSE
                            }
                        }
                    }
                }
                
            // ... 省略底部功能区 ...
        }
    }
    
    // ... 省略生命周期方法 ...
}

代码解释:

  1. soundPlayControl状态 :我们新增了一个observable变量,它的类型是VideoPlayControl,默认值为PAUSE
  2. 状态更新触发播放 :在青蛙的click事件中,我们不再调用任何方法,而是简单地将soundPlayControl的值改为VideoPlayControl.PLAY
  3. 属性绑定<Video>组件的playControl属性直接绑定了soundPlayControl状态。当状态改变时,Kuikly框架会自动更新组件,从而控制视频(音频)的播放或暂停。
  4. 播放后重置状态 :我们监听playStateDidChanged事件。在探索中我们发现,当播放完成时,状态会变为PlayState.PLAY_END。此时,我们把soundPlayControl重新设置为PAUSE,这样就完成了一个播放循环,并且可以让青蛙被连续点击发声。

结语

现在,重新编译运行,再次点击那只青蛙。伴随着熟悉的缩放动画,一声清脆的"呱"终于响了起来!虽然中间走了点小弯路,但我们最终还是用一种更"Kuikly"的方式,让我们的应用"活"了起来。这次的经历也让我们对Kuikly的声明式思想有了更深的理解:少一些命令,多一些声明,代码会变得更优雅、更可预测。

相关推荐
CaspianSea21 小时前
编译Android 16 TV模拟器(一)
android
廋到被风吹走1 天前
【数据库】【MySQL】InnoDB外键解析:约束机制、性能影响与最佳实践
android·数据库·mysql
威哥爱编程1 天前
【鸿蒙开发案例篇】定点出击!鸿蒙6.0视频碰一碰流转+实时进度同步案例
harmonyos·arkts·arkui
峥嵘life1 天前
Android16 EDLA 认证测试CTS问题分析解决
android·java·服务器
嗝o゚1 天前
鱼与熊掌可兼得?用Flutter+鸿蒙的混合架构破解性能与UI的世纪难题
flutter·架构·harmonyos
惟恋惜1 天前
Jetpack Compose 的状态使用之“界面状态”
android·android jetpack
_李小白1 天前
【Android FrameWork】第二十六天:BroadcastReceiver
android
遇到困难睡大觉哈哈1 天前
HarmonyOS 应用数据持久化概述:Preferences、KV-Store、RelationalStore 到底怎么选?
笔记·华为·harmonyos
宇擎智脑科技1 天前
Flutter 对接高德地图 SDK 适配鸿蒙踩坑记录与通信架构解析
flutter·架构·harmonyos
@#---1 天前
如何准确判断json文件并且拿到我想要的信息
android·python·json