前言
正所谓:Hello Word 是程序员学任何一门语言的第一个程序实践。这其实也是一个不错的正反馈,那如何让学习鸿蒙 Next 更有成就感呢?下面就演示一下从零开发一个鸿蒙 Next 版的电子木鱼,主打就是一个抽象!

实现要点
- 页面布局
- 木鱼点击
- 木鱼音效
- 动画特效
- 自定义弹窗

开始实践
页面布局
ArkTS 定义了声明式 UI 描述、自定义组件和动态扩展 UI 元素的能力,配合 ArkUI 开发框架中的系统组件及其相关的事件方法、属性方法等共同构成 UI 开发的主体。我们下面要完成的主要是一个木鱼和设置按钮、自动按钮。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 
 | build() {
 Column() {
 HdNav({ title: '电子木鱼', showRightIcon: false, iconColor: $r('app.color.white'), titleColor: '#ffffff' })
 
 Row() {
 Text(this.woodenType[this.type] + ':'+ this.score).fontSize(22).fontColor("#ffffff").width('100%').textAlign(TextAlign.Center)
 }.width("100%").height("8%")
 
 Row() {
 Image($r('app.media.setting')).width(25).height(25).margin(16).onClick(() => {
 if (this.dialogController != null) {
 this.dialogController.open()
 }
 })
 }.width('100%')
 
 Row() {
 Image($r('app.media.foreground')).width(40).height(40).margin({left:8,top:5})
 }.width('100%')
 .onClick(() => {
 this.handlePopup = !this.handlePopup
 })
 .bindPopup(this.handlePopup, {
 message: '数据统计功能,正在完善中~',
 })
 
 Row() {
 if (this.isPresent) {
 Text(this.woodenType[this.type] + ': ' + this.woodenFishNum).fontSize(16).fontColor("#ffffff").width('100%').textAlign(TextAlign.Center)
 .transition(this.effect)
 }
 }.width('100%').height('25%')
 .alignItems(VerticalAlign.Top)
 
 Row() {
 Image($r('app.media.muyu'))
 .width(this.isZoomed == true ? this.targetWidth * 1.2 : this.targetWidth * 1)
 .height(this.isZoomed == true ? this.targetHeight * 1.2 : this.targetHeight * 1)
 }
 .width('100%')
 .height('25%')
 .alignItems(VerticalAlign.Center)
 .justifyContent(FlexAlign.Center)
 
 Row() {
 Toggle({ type: ToggleType.Switch })
 .onChange((isOn: boolean) => {
 if(isOn) {
 promptAction.showToast({ message: 'auto is on.' })
 } else {
 promptAction.showToast({ message: 'auto is off.' })
 }
 })
 
 Text('自动' + this.woodenType[this.type]).fontSize(18).fontColor('#ffffff').height(40).margin({left: 10})
 
 }.width('100%').height('10%').justifyContent(FlexAlign.Center)
 
 }
 .height("100%")
 .backgroundColor('rgba(0, 0, 0, 1.00)')
 
 }
 
 | 
木鱼点击
木鱼是一张图片,也就是给该图绑定一个点击事件,点击一次有三个动作需要执行:
- 木鱼有放大的效果
- 有类似功德文字的飘动
- 功德数值的累加
而点击的时候要看到实时的效果,所以可以声明三个状态,通过 State 的修改,从而驱动 UI 更新,以下的 animateTo 是给域名的放大添加的一个平滑效果。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 
 | // 积分@State score: number = 0
 // 积分文字
 @State isPresent: boolean = false
 // 木鱼是否放大
 @State isZoomed: boolean = false
 
 
 // 木鱼UI
 Image($r('app.media.muyu'))
 .width(this.isZoomed == true ? this.targetWidth * 1.2 : this.targetWidth * 1)
 .height(this.isZoomed == true ? this.targetHeight * 1.2 : this.targetHeight * 1)
 .onClick((event) => {
 animateTo({ curve: curves.springMotion() }, () => {
 this.isZoomed = !this.isZoomed;
 
 if (this.isZoomed == true) {
 this.isPresent = true;
 this.score += this.woodenFishNum;
 this.onClickPlay();
 }
 })
 
 // 定时缩小/定时文字消失
 setTimeout(() => {this.isZoomed = false;}, 50);
 setTimeout(() => {this.isPresent = false}, 600);
 })
 
 | 
木鱼音效
木鱼音效是点击时的咚咚的声音,这里就要使用到 HarmonyOS Next 的音频服务。这里需要注意一点,项目运行预览无法播放,一定要模拟器或真机才可以调试音频的播放效果。

| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | // 销毁音效工具onClickDestroy= ()=>{
 AudioMgr.Ins().destroy();
 console.log('audio', 'destroy');
 }
 
 // 初始化音效工具
 onClickInit = ()=>{
 AudioMgr.Ins().init();
 console.log('audio', 'init');
 }
 
 // 播放指定音效
 onClickPlay = ()=>{
 AudioMgr.Ins().play();
 console.log('audio', 'playing');
 }
 
 | 

动画特效
这里的动画效果主要是点击木鱼,从下网上飘出一个文字然后消失的特效。在鸿蒙中可以通过 TransitionEffect 方法添加效果,首先创建特效,然后再文字上挂载。
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | // 上移入场特效private effect: object =
 TransitionEffect.OPACITY
 // 初始正常大小// 假设动画持续时间为500ms
 .combine(TransitionEffect.scale({ x: 1, y: 1 }).animation({ curve: curves.springMotion(0.6, 1.2), duration: 0 }))
 // 向上平移150单位// 与上一步同时开始
 .combine(TransitionEffect.translate({ x: 0, y: 400 }).animation({ curve: curves.springMotion(0.6, 1.2), duration: 10000, delay: 50 }))
 // 淡出至完全透明// 在平移结束后开始淡出
 .combine(TransitionEffect.opacity(0).animation({ curve: curves.springMotion(0.6, 1.2), duration: 1000, delay: 0 }));
 
 | 

自定义弹窗
经过前面布局,事件绑定,音效播放,一个简单的电子木鱼其实已经完成了。但是为了增添趣味和后期扩展,这里再加一个设置功能,通过按钮打开配置项弹窗,设置包括:
- 类型选项 (功德、财运、桃花运等)
- 音效选项 (各种解压的音效素材)
- 皮肤管理 (木鱼的 UI 界面设置)
- 数值修改 (对展示的累加数值做任意修改)
- 其他 (是否关闭音效,是否自动点击等)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 
 | // 弹窗层(UI开发-组件-自定义弹窗)@CustomDialog
 struct SettingDialog {
 controller?: CustomDialogController
 
 // 父子组件双向同步,文档见 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-link-V5
 @Link woodenFishType: number
 
 // 木鱼敲击的数值
 @Link woodenFishNum: number
 
 build() {
 Column() {
 
 Row() {
 Text('愿望:').fontSize(17).fontWeight(600)
 Radio({ value: '功德', group: 'word' }).checked(true).onChange((isChecked: boolean) => {
 if(isChecked) {
 this.woodenFishType = 0
 }
 })
 Text('功德').fontSize(15)
 Radio({ value: '财富', group: 'word' }).onChange((isChecked: boolean) => {
 if(isChecked) {
 this.woodenFishType = 1
 }
 })
 Text('财富').fontSize(15)
 Radio({ value: '桃花运', group: 'word' }).onChange((isChecked: boolean) => {
 if(isChecked) {
 this.woodenFishType = 2
 }
 })
 Text('桃花运').fontSize(15)
 }
 .width('100%')
 .margin({bottom: 12})
 .justifyContent(FlexAlign.Start)
 
 Row() {
 Text('数值:').fontSize(16).fontWeight(600)
 TextInput({text:'1'}).type(InputType.Number).width(180).onChange((value: string) => {
 this.woodenFishNum = parseInt(value)
 })
 }
 .width('100%')
 .margin({bottom: 12})
 .justifyContent(FlexAlign.Start)
 
 Row() {
 Text('音效:').fontSize(16).fontWeight(600)
 Toggle({ type: ToggleType.Switch })
 }
 .width('100%')
 .margin({bottom: 12})
 .justifyContent(FlexAlign.Start)
 
 Row() {
 Text('皮肤:').fontSize(16).fontWeight(600)
 Radio({ value: '默认', group: 'skin' }).checked(true)
 Text('木鱼').fontSize(15)
 Radio({ value: '悟空', group: 'skin' })
 Text('黑悟空').fontSize(15)
 Radio({ value: '典韦', group: 'skin' })
 Text('典韦').fontSize(15)
 }
 .width('100%')
 .margin({bottom: 12})
 .justifyContent(FlexAlign.Start)
 
 }.padding({top: 28, left: 15})
 
 }
 }
 
 | 
这里需要注意的是:父子组件的数据传递。因为自定义弹窗和木鱼是两个不同的组件,而点击弹窗中的比如类型切换或修改的数值,全部要更新到木鱼组件的展示当中。
当然鸿蒙也提供了 @Link 装饰器,用于与其父组件中的数据源共享相同的值,可以结合上面代码和下方截图参考其用法。

写在后面
到这里,一个通用型的鸿蒙 Next 版电子木鱼就完成了。不管是组件交互还是布局都还好,唯一让我觉得不适应的是动画特效。
如果用这种方式实现电子烟花肯定不行,所以下次将换一种方法快速实现烟花秀,以及页面间的跳转,待更新~
