在上一篇文章中,我们成功地让青蛙对我们的点击做出了视觉上的响应------一个生动的缩放动画。现在,我们的应用不再是静态的了,但似乎还缺点什么。
没错,就是声音!交互不仅仅是视觉上的,听觉上的反馈同样重要。如果每次点击青蛙,它都能发出一声清脆的"呱",那应用的趣味性和沉浸感无疑会更上一层楼。
今天,我们就来完成这个任务:为我们的"单身青蛙"应用加上音效,让它在被点击时,能真实地"叫"出来。同时,我们也会探讨在Kuikly中如何进行基础的音频资源管理。
初步计划
在动手之前,我们自然是先去翻了翻Kuikly的文档,想找找有没有现成的音频播放组件,比如一个叫<Audio>的家伙。结果发现,似乎并没有这样一个专门为播放短音效而生的轮子。
不过,没有"直路",我们可以"绕路"走嘛。Kuikly提供了一个功能强大的<Video>组件。既然视频能包含声音,我们是不是可以利用它来只播放声音呢?这个想法听起来不错。
我们的计划是:准备一个音频文件(就是我们之前准备好的frog_sound.mp3),然后把它"伪装"成一个视频源交给<Video>组件。为了不让它在界面上"碍眼",我们再把它设置成一个1x1像素的、完全透明的"隐形"播放器。
实战
好了,理论有了,开干!
我们最初的想法很简单直接:既然拿到了<Video>组件的引用(ViewRef),那在点击事件里直接调用它的play()方法不就行了?就像这样:
kotlin
// 理想中的代码...
ctx.soundPlayerRef.view?.play()
然而,现实很快给了我们一拳,编译器无情地报错:Unresolved reference: play。好吧,看来此路不通。这种命令式的、直接操作视图的方式,似乎并不符合Kuikly的"口味"。
这次碰壁让我们冷静下来,重新思考Kuikly的设计哲学------声明式UI。我们不应该去"命令"一个组件去播放,而应该"声明"它的状态是"正在播放"。
于是,新的方案诞生了:
- 定义一个
observable的状态变量soundPlayControl,用来控制播放行为(播放/暂停)。 - 将
<Video>组件的playControl属性绑定到这个状态上。 - 当用户点击青蛙时,我们不再调用方法,而是去更新
soundPlayControl的状态为VideoPlayControl.PLAY。 - 监听
<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
}
}
}
}
// ... 省略底部功能区 ...
}
}
// ... 省略生命周期方法 ...
}
代码解释:
soundPlayControl状态 :我们新增了一个observable变量,它的类型是VideoPlayControl,默认值为PAUSE。- 状态更新触发播放 :在青蛙的
click事件中,我们不再调用任何方法,而是简单地将soundPlayControl的值改为VideoPlayControl.PLAY。 - 属性绑定 :
<Video>组件的playControl属性直接绑定了soundPlayControl状态。当状态改变时,Kuikly框架会自动更新组件,从而控制视频(音频)的播放或暂停。 - 播放后重置状态 :我们监听
playStateDidChanged事件。在探索中我们发现,当播放完成时,状态会变为PlayState.PLAY_END。此时,我们把soundPlayControl重新设置为PAUSE,这样就完成了一个播放循环,并且可以让青蛙被连续点击发声。
结语
现在,重新编译运行,再次点击那只青蛙。伴随着熟悉的缩放动画,一声清脆的"呱"终于响了起来!虽然中间走了点小弯路,但我们最终还是用一种更"Kuikly"的方式,让我们的应用"活"了起来。这次的经历也让我们对Kuikly的声明式思想有了更深的理解:少一些命令,多一些声明,代码会变得更优雅、更可预测。