手把手带你实现 鸿蒙应用 键盘音乐
设置和不设置全局沉浸式的区别是这样的
src/main/ets/entryability/EntryAbility.ets
文件内进行编辑
loadContent
中进行设置
// 1 设置应用全屏
let windowClass: window.Window = windowStage.getMainWindowSync(); // 获取应用主窗口
// 2 设置沉浸式
windowClass.setWindowLayoutFullScreen(true)
此时效果是这样的 , 文字也会直接在状态栏上显示
此时,考虑到不同设备的状态栏高度可能不同,所以我们需要
AppStorageV2
// 1 获取应用窗口对象
const windowClass: window.Window = windowStage.getMainWindowSync(); // 获取应用主窗口
// 2 设置全屏
windowClass.setWindowLayoutFullScreen(true)
// 3 获取布局避让遮挡的区域
const type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR;
const avoidArea = windowClass.getWindowAvoidArea(type);
const bottomRectHeight = avoidArea.bottomRect.height; // 获取到导航条区域的高度
const vpHeight = px2vp(bottomRectHeight) // 转换成 vp单位的数值
// 4 把导航栏高度数据 存在全局
const appStatu = AppStorageV2.connect(AppStatu, "AppStatu", () => new AppStatu())
appStatu!.vpHeight = vpHeight
AppStatu
是自定义类,用来存储数据 状态栏高度数据的
src/main/ets/types/index.ets
@ObservedV2
export class AppStatu {
@Trace vpHeight: number =0
}
build() {
Column({ space: 30 }) {
}
.width("100%")
.height("100%")
.backgroundImage($r("app.media.startIcon"))
.backgroundImageSize(ImageSize.FILL)
.backdropBlur(1000) // 对背景进行模糊
}
使用背景图片+模糊搭建琴谱区域,高度由内容撑开
@Builder
MusicScore() {
// 琴谱
Column({ space: 3 }) {
Text("琴谱")
}
.width("100%")
.backgroundImage($r("app.media.startIcon"))
.backgroundImageSize(ImageSize.FILL)
.backdropBlur(500) // 对背景进行模糊
.padding({
top: this.appStatu!.vpHeight + 20
})
}
build() {
Column({ space: 30 }) {
// 1 琴谱
this.MusicScore()
}
.width("100%")
.height("100%")
.backgroundImage($r("app.media.startIcon"))
.backgroundImageSize(ImageSize.FILL)
.backdropBlur(1000) // 对背景进行模糊
}
琴谱只需要两个字段
title
content
src/main/ets/types/index.ets
@ObservedV2
export class Lyric {
@Trace title: string = ""
@Trace content: string[] = []
}
@ObservedV2
export class LyricStatu {
@Trace title: string = ""
@Trace isCorrect: boolean = false
}
为了方便页面的效果处理,我们需要将手上的数据,简单处理下,方便页面渲染
手上的数据
src/main/ets/mock/index.ets
import { Lyric } from '../types'
export const tonghua: Lyric = {
title: "童话",
content: ["LONOL", "LONOL", "OOMML", "LONOL", "LQPPO", "LONOM", "MMOTS", "PPRRQQ", "QQNPOONO", "ONOR", "LSRQPPPRRQQ",
"QQVUTUV", "VPOT", "TTSSSLSRQQRQ", "QRQ", "RQPOOQST", "TTSPPRQ", "OQST", "TTSPPRQRQPO", "PQMMOONO",]
}
处理后的数据结构
为什么要这样处理,因为让它方便渲染
如何处理呢 在页面打开的时候在aboutToAppear
中处理即可 lyricList
import { tonghua } from '../mock'
import { LyricStatu } from '../types'
@Entry
@ComponentV2
struct Index {
// 琴谱列表
@Local lyricList: LyricStatu[][] = []
aboutToAppear() {
this.lyricList = tonghua.content.map(row => {
const list = row.split('').map(v => {
const o = new LyricStatu()
o.title = v
return o
})
return list
})
}
}
### 渲染琴谱
```typescript
// 状态栏的高度
@Local appStatu: AppStatu | undefined = AppStorageV2.connect(AppStatu, "AppStatu", () => new AppStatu())
@Builder
MusicScore() {
// 琴谱
Column({ space: 3 }) {
// 标题
Text(tonghua.title)
.fontSize(30)
.fontColor("#fff")
ForEach(this.lyricList, (item1: LyricStatu[]) => {
Row({ space: 5 }) {
ForEach(item1, (item2: LyricStatu) => {
Text(item2.title)
.fontColor(item2.isCorrect ? "#23d96e" : "#ffcf49")
.fontSize(20)
})
}
})
}
.width("100%")
.backgroundImage($r("app.media.startIcon"))
.backgroundImageSize(ImageSize.FILL)
.backdropBlur(500) // 对背景进行模糊
.padding({ // 设置文字下移,否则被屏幕摄像头给挡住
top: this.appStatu!.vpHeight + 20
})
}
build() {
Column({ space: 30 }) {
// 1 琴谱
this.MusicScore()
// this.KeyBoard()
}
.width("100%")
.height("100%")
.backgroundImage($r("app.media.startIcon"))
.backgroundImageSize(ImageSize.FILL)
.backdropBlur(1000) // 对背景进行模糊
}
得到结果
键盘一个26个字母,对应边有26个声音。一一相对应
其中,我们的静态资源存放在 rawFile中,鸿蒙应用在打包时不会对里面的文件做任何的编译处理,然后在使用的时候需要搭配AVPlayer使用。如
const res = await getContext().resourceManager.getRawFd("paino1.mp3")
AVPlayer实例.fdSrc = res
src/main/ets/mock/index.ets
export const letters: LettemMusic[][] = [
[
{ name: "Q", src: "paino17.mp3" },
{ name: "W", src: "paino23.mp3" },
{ name: "E", src: "paino5.mp3" },
{ name: "R", src: "paino18.mp3" },
{ name: "T", src: "paino20.mp3" },
{ name: "Y", src: "paino25.mp3" },
{ name: "U", src: "paino21.mp3" },
{ name: "I", src: "paino9.mp3" },
{ name: "O", src: "paino15.mp3" },
{ name: "P", src: "paino16.mp3" },
],
[
{ name: "A", src: "paino1.mp3" },
{ name: "S", src: "paino19.mp3" },
{ name: "D", src: "paino4.mp3" },
{ name: "F", src: "paino6.mp3" },
{ name: "G", src: "paino7.mp3" },
{ name: "H", src: "paino8.mp3" },
{ name: "J", src: "paino10.mp3" },
{ name: "K", src: "paino11.mp3" },
{ name: "L", src: "paino12.mp3" },
],
[
{ name: "Z", src: "paino26.mp3" },
{ name: "X", src: "paino24.mp3" },
{ name: "C", src: "paino3.mp3" },
{ name: "V", src: "paino22.mp3" },
{ name: "B", src: "paino2.mp3" },
{ name: "N", src: "paino14.mp3" },
{ name: "M", src: "paino13.mp3" },
]
]
import { letters, tonghua } from '../mock'
...
// 键盘 和 对应的音乐按键
@Local letters: LettemMusic[][] = letters
// 键盘
@Builder
KeyBoard() {
Column({ space: 10 }) {
ForEach(this.letters, (items: LettemMusic[]) => {
Row({ space: 8 }) {
ForEach(items, (item: LettemMusic) => {
Text(item.name)
.backgroundColor("rgba(255,255,255,0.9)")
.padding(10)
.borderRadius(10)
.fontWeight(400)
.stateStyles({
clicked: {
.backgroundColor("#fff")
}
})
})
}
.width("100%")
.padding(2)
.justifyContent(FlexAlign.Center)
})
}
.layoutWeight(1)
}
build() {
Column({ space: 30 }) {
// 1 琴谱
this.MusicScore()
// 2 键盘
this.KeyBoard()
}
.width("100%")
.height("100%")
.backgroundImage($r("app.media.startIcon"))
.backgroundImageSize(ImageSize.FILL)
.backdropBlur(1000) // 对背景进行模糊
}
使用AVPlayer可以实现端到端播放原始媒体资源,本开发指导将以完整地播放一首音乐作为示例,向开发者讲解AVPlayer音频播放相关功能。 播放的全流程包含:创建AVPlayer,设置播放资源,设置播放参数(音量/倍速/焦点模式),播放控制(播放/暂停/跳转/停止),重置,销毁资源。 在进行应用开发的过程中,开发者可以通过AVPlayer的state属性主动获取当前状态或使用on('stateChange')方法监听状态变化。如果应用在音频播放器处于错误状态时执行操作,系统可能会抛出异常或生成其他未定义的行为。
使用流程基本围绕这一张图即可
创建 AVPlayer 实例 此时,avPlayer进入空闲状态 idle
const avPlayer = await media.createAVPlayer()
监听状态的改变 我们对播放器的每一个操作,都会影响到它状态发生改变
avPlayer.on("stateChange", (state) => {
switch (state) {
// 如果播放器初始化完毕,那么就让它开始状态
case "initialized":
avPlayer.prepare()
break;
case "prepared":
// 如果播放器准备完毕,就让它变成开始播放
avPlayer.play()
break;
default:
break;
}
})
设置播放音乐的URL
const res = await getContext().resourceManager.getRawFd(this.url)
avPlayer.fdSrc = res // 设置完播放器后,播放器会进入 initialized 状态
开始播放
我们已经在 prepared
状态中,设置了自动播放了 avPlayer.play()
src/main/ets/utils/AvPlayerManager.ets 实现了对 AVPlayer功能的基本封装
import { media } from '@kit.MediaKit'
class AVPlayerManager {
// 播放器实例
avPlayer: media.AVPlayer | null = null;
url: string = ""
// 播放完毕的回调事件
playComplete: () => void = () => {
}
// 构造函数
constructor(url: string) {
this.init()
this.url = url
}
// 初始化
async init() {
this.avPlayer = await media.createAVPlayer()
this.avPlayer.on("stateChange", (state) => {
switch (state) {
case "initialized":
this.avPlayer?.prepare()
break;
case "prepared":
this.avPlayer?.play()
break;
case "completed":
// 播放完毕,销毁实例
this.avPlayer?.release()
this.playComplete()
break;
default:
break;
}
})
this.avPlayer.on("error", (err) => {
console.log("err", err)
})
// 设置URL
const res = await getContext().resourceManager.getRawFd(this.url)
this.avPlayer!.fdSrc = res
}
}
export default AVPlayerManager
方便判断按下的键盘是否正确和播放正确的按键音乐
// 用来判断按下的按键和琴谱是否对应的
letterFlat: LettemMusic[] = []
aboutToAppear() {
this.letterFlat = this.letters.flat()
// ...
}
.onClick(() => this.playLetter(item))
// 用来管理正在播放的声音对应的AVPlayer实例 如按下了 Q W ,那么就会出生两个 AVPlayer实例
avPlayManagerList: AVPlayerManager[] = []
// 点击键盘播放音乐
playLetter(letter: LettemMusic) {
// 根据点击的键盘 找到琴谱音乐对象 如 { name :"A" ,src :"paino1.mp1"}
const item = this.letterFlat.find(v => v.name === letter.name)
// 根据播放的歌曲路径 判断当前音乐是否正在播放
const avIndex = this.avPlayManagerList.findIndex(v => v.url === item!.src)
if (avIndex !== -1) {
// 如果正在播放 马上销毁
this.avPlayManagerList[avIndex].avPlayer?.release()
// 并且从数组中移除
this.avPlayManagerList.splice(avIndex, 1)
}
// 根据当前点击的键盘创建对应的AVPlayer实例
const avplayManager = new AVPlayerManager(letter.src)
// 追加到数组中
this.avPlayManagerList.push(avplayManager)
// 添加一个播放完毕的回调,用来删除avPlayManagerList数组中的AvPlay
avplayManager.playComplete = () => {
const index = this.avPlayManagerList.findIndex(v => v.url === item!.src)
this.avPlayManagerList.splice(index, 1)
}
}
类似练习打字效果,按对按键了,就设置绿色,如:
因为我们的琴谱是个二维数组
因此,我们也是定义一个数组 [行的坐标,列的坐标],分别是二维数组相对应
// 用户弹的到琴谱坐标
nextRowColumn: number[] = [0, 0]
接着,也是在点击事件中,根据按下的按键和对应的琴谱是否相等,如果是,设置绿色
// 点击键盘播放音乐
playLetter(letter: LettemMusic) {
// ....
// 获取行坐标
const row = this.nextRowColumn[0]
// 获取列坐标
const column = this.nextRowColumn[1]
// 判断当前的坐标是否超出范围
if (this.lyricList[row] && this.lyricList[row][column]) {
// 获取坐标对应的琴谱
const item = this.lyricList[row][column]
// 判断按下的按键和对应的琴谱是否相等 如 L == L
if (item.title === letter.name) {
// 设置选中
item.isCorrect = true
// 以下代码是设置坐标递进
if (this.lyricList[row][column+1]) {
this.nextRowColumn[1] = column + 1
} else if (this.lyricList[row+1]) {
this.nextRowColumn[0] = row + 1
this.nextRowColumn[1] = 0
} else {
console.log("最后一个了")
}
}
}
}