鸿蒙技术分享:敲鸿蒙木鱼,积____功德🐶🐶🐶——鸿蒙元服务开发:从入门到放弃(1)
本文是系列文章,其他文章见:敲鸿蒙木鱼,积____功德——鸿蒙元服务开发:从入门到放弃(2)敲鸿蒙木鱼,积____功德——鸿蒙元服务开发:从入门到放弃(3) 本文完整源码查看funny-widget 简介 因为工作需要,准备开发元服务,所以就想着搞一个电子木鱼的DEMO学习一下元服务以及桌面卡片的功能开发知识。 详细了解HarmonyOS的元服务,可查看官方介绍。 涉及知识点 元服务开
本文是系列文章,其他文章见:
敲鸿蒙木鱼,积____功德🐶🐶🐶——鸿蒙元服务开发:从入门到放弃(2)
敲鸿蒙木鱼,积____功德🐶🐶🐶——鸿蒙元服务开发:从入门到放弃(3)
本文完整源码查看funny-widget
简介
因为工作需要,准备开发元服务,所以就想着搞一个电子木鱼的DEMO学习一下元服务以及桌面卡片的功能开发知识。
详细了解HarmonyOS的元服务,可查看官方介绍。
涉及知识点
- 元服务开发流程
- 加载图片
- 播放音频
- 开发调试
- 组件代码在卡片和元服务间共享
- 数据在卡片和元服务间共享
元服务开发
创建项目
根据官方文档创建工程。
必须要注册华为开发者账号。
开发电子木鱼页面
新建ElectronicWoodenFishPage
页面。
为了方便后期共用布局和逻辑,将木鱼逻辑抽离出单独的Component,ElectronicWoodenFishComponent`。
import { ElectronicWoodenFishComponent } from '../components/ElectronicWoodenFishComponent';
import { DataManager } from '../utils/DataManager';
@Entry
@Component
struct ElectronicWoodenFishPage {
@State meritCount: number = 0;
build() {
RelativeContainer() {
ElectronicWoodenFishComponent({
meritCount: this.meritCount,
onClickWoodenFish: () => {
}
})
.id('ElectronicWoodenFishComponent')
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
}
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
.height('100%')
.width('100%')
}
}
添加布局和点击功能
在ElectronicWoodenFishComponent
组件中添加木鱼图片布局。
@Component
export struct ElectronicWoodenFishComponent {
@Prop meritCount: number = 0;
onClickWoodenFish: () => void = () => {}
build() {
Row() {
Column() {
Text(`功德:${this.meritCount}`)
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
Stack() {
Image($r('app.media.WoodenFish'))
.size({ width: '80%' })
.aspectRatio(1)
.onClick(() => {
if (this.onClickWoodenFish) {
this.onClickWoodenFish()
}
})
}
.align(Alignment.Top)
}
.width('100%')
}
.height('100%')
.backgroundColor(Color.Black)
}
}
获取图片资源
baidu木鱼图片,现在baidu搜索图片支持智能抠图,方便了很多。
有需要的同学直接取用:
添加缩放动画和文本动画
在ElectronicWoodenFishComponent
组件中添加“+1”上浮动画文字和木鱼放大动画。
@Component
export struct ElectronicWoodenFishComponent {
@Prop meritCount: number = 0;
@State imageScale: ScaleOptions = { x: 1, y: 1 }
@State plusOneOffsetY: number = 40;
@State plusOneOpacity: number = 0;
onClickWoodenFish: () => void = () => {}
build() {
Row() {
Column() {
Text(`功德:${this.meritCount}`)
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
Stack() {
Image($r('app.media.WoodenFish'))
.size({ width: '80%' })
.aspectRatio(1)
.scale(this.imageScale)
.animation({
curve: Curve.EaseInOut,
playMode: PlayMode.AlternateReverse,
duration: 500,
onFinish: () => {
this.imageScale = { x: 1, y: 1 }
}
})
.onClick(() => {
this.imageScale = { x: 1.2, y: 1.2 }
this.plusOneOpacity = 1
this.plusOneOffsetY = 0
if (this.onClickWoodenFish) {
this.onClickWoodenFish()
}
})
Text('+1')
.fontColor(Color.White)
.opacity(this.plusOneOpacity)
.offset({ y: this.plusOneOffsetY })
.animation({
curve: Curve.EaseInOut,
playMode: PlayMode.Normal,
duration: 960,
onFinish: () => {
this.plusOneOffsetY = 40
this.plusOneOpacity = 0
}
})
}
.align(Alignment.Top)
}
.width('100%')
}
.height('100%')
.backgroundColor(Color.Black)
}
}
简单的使用关键帧动画完成放大和平移动画,不过之前测试遇到过动画onFinish会触发多次的问题,感觉还是不太稳定。
添加音效
修改ElectronicWoodenFishPage
页面点击回调,增加音效播放。
@Entry
@Component
struct ElectronicWoodenFishPage {
build() {
RelativeContainer() {
ElectronicWoodenFishComponent({
meritCount: this.meritCount,
onClickWoodenFish: () => {
AudioManager.shared.playOnlineSound()
// ...
}
})
}
}
}
export class AudioManager {
static shared = new AudioManager()
isSeek: boolean = false
count: number = 0
setAVPlayerCallback(avPlayer: media.AVPlayer) {
// seek操作结果回调函数
avPlayer.on('seekDone', (seekDoneTime: number) => {
console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
})
// error回调监听函数,当avPlayer在操作过程中出现错误时调用 reset接口触发重置流程
avPlayer.on('error', (err: BusinessError) => {
hilog.info(0x0000, '音频', '%{public}s', `error回调:${err.message}`);
console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
avPlayer.reset(); // 调用reset重置资源,触发idle状态
})
// 状态机变化回调函数
avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
hilog.info(0x0000, '音频', '%{public}s', `stateChange回调:${state},${reason.toString()}`);
switch (state) {
case 'idle': // 成功调用reset接口后触发该状态机上报
console.info('AVPlayer state idle called.');
avPlayer.release(); // 调用release接口销毁实例对象
break;
case 'initialized': // avplayer 设置播放源后触发该状态上报
console.info('AVPlayer state initialized called.');
avPlayer.prepare();
break;
case 'prepared': // prepare调用成功后上报该状态机
console.info('AVPlayer state prepared called.');
avPlayer.play(); // 调用播放接口开始播放
break;
case 'playing': // play成功调用后触发该状态机上报
console.info('AVPlayer state playing called.');
if (this.count !== 0) {
if (this.isSeek) {
console.info('AVPlayer start to seek.');
avPlayer.seek(avPlayer.duration); //seek到音频末尾
} else {
// 当播放模式不支持seek操作时继续播放到结尾
console.info('AVPlayer wait to play end.');
}
} else {
avPlayer.pause(); // 调用暂停接口暂停播放
}
this.count++;
break;
case 'paused': // pause成功调用后触发该状态机上报
console.info('AVPlayer state paused called.');
avPlayer.play(); // 再次播放接口开始播放
break;
case 'completed': // 播放结束后触发该状态机上报
console.info('AVPlayer state completed called.');
avPlayer.stop(); //调用播放结束接口
break;
case 'stopped': // stop接口成功调用后触发该状态机上报
console.info('AVPlayer state stopped called.');
avPlayer.reset(); // 调用reset接口初始化avplayer状态
break;
case 'released':
console.info('AVPlayer state released called.');
break;
default:
console.info('AVPlayer state unknown called.');
break;
}
})
}
// 以下demo为通过url设置网络地址来实现播放直播码流的demo
async playOnlineSound() {
hilog.info(0x0000, '电子木鱼组件', '%{public}s', 'playOnlineSound');
console.debug(`[电子木鱼组件]playOnlineSound`)
// 创建avPlayer实例对象
let avPlayer: media.AVPlayer = await media.createAVPlayer();
// 创建状态机变化回调函数
this.setAVPlayerCallback(avPlayer);
avPlayer.url = 'https://clemmensen.top/static/muyu.mp3';
}
}
1.这里的在线mp3音频地址使用的掘金其他老哥的资源,感谢;码上掘金实现电子木鱼
2.在线播放音频肯定是有延迟;至于为什么不用本地的,因为元服务播放本地音频失败[捂脸];
数据存储
简单封装了下preferences。
import preferences from '@ohos.data.preferences';
import { AsyncCallback, Callback } from '@ohos.base';
export class DataManager {
static shared = new DataManager()
preferences: preferences.Preferences | null = null;
init(context: Context) {
this.preferences = preferences.getPreferencesSync(context, { name: 'myStore' });
}
get(key: string, defValue: preferences.ValueType, callback: AsyncCallback<preferences.ValueType>): void {
this.preferences?.get(key, defValue, callback)
}
getSync(key: string, defValue: preferences.ValueType): preferences.ValueType {
return this.preferences?.getSync(key, defValue) ?? defValue
}
put(key: string, value: preferences.ValueType, callback: AsyncCallback<void>): void {
this.preferences?.put(key, value, callback)
}
putSync(key: string, value: preferences.ValueType): void {
this.preferences?.putSync(key, value)
this.preferences?.flush()
}
}
修改ElectronicWoodenFishPage
页面,增加数据存取逻辑。
import { ElectronicWoodenFishComponent } from '../components/ElectronicWoodenFishComponent';
import { DataManager } from '../utils/DataManager';
@Entry
@Component
struct ElectronicWoodenFishPage {
@State meritCount: number = 0;
aboutToAppear(): void {
DataManager.shared.init(getContext(this))
this.meritCount = DataManager.shared.getSync('meritCount', 0) as number
}
build() {
RelativeContainer() {
ElectronicWoodenFishComponent({
meritCount: this.meritCount,
onClickWoodenFish: () => {
let meritCount: number = DataManager.shared.getSync('meritCount', 0) as number
DataManager.shared.putSync('meritCount', meritCount + 1)
this.meritCount = meritCount + 1
}
})
}
}
}
本文完整源码查看funny-widget
更多推荐
所有评论(0)