HarmonyOS鸿蒙Next中RichEditor实现类似歌词滚动,当前歌词高亮的效果
HarmonyOS鸿蒙Next中RichEditor实现类似歌词滚动,当前歌词高亮的效果 我想用RichEditor实现歌词滚动播放的效果,里面有每一句歌词的开始时间和结束时间,然后根据计时器的走向,找到高亮的那句话进行高亮,持续的播放一点点滚动到底部。有什么实现建议?
3 回复
可以尝试这个demo
在定时器执行过程中进行计算当前所在行数
Bean.ets:
import measure from '@ohos.measure';
import { display } from '@kit.ArkUI';
export class Bean {
static readonly textArray: Array<TextBean> = [];
static init() {
let widthComp = px2vp(display.getDefaultDisplaySync().width); // 组件的宽度,范例是全屏
console.log('lcs --- widthComp', widthComp)
for (let i = 0; i < 100; i++) {
let text = `我是第 ${i} 句话,`;
Bean.textArray.push({
label: text,
startTime: 1000 * i,
endTime: 1000 * i + 1000,
width: -1,
line: -1 // 默认是 -1标识不折行,其他值标识折行在几行
})
}
console.log('lcs ---', JSON.stringify(Bean.textArray));
}
}
export interface TextBean {
label: string;
startTime: number;
endTime: number;
width: number;
line?: number;
}
export function calcWidth(text: string): number {
let textWidth: number = measure.measureText({
textContent: text,
fontSize: 16
});
return px2vp(textWidth);
}
export function find(arr: Array<TextBean>, target: number, left: number, right: number): number {
if (left > right) {
return -1;
}
let mid:number = Math.floor((left + right) / 2);
if (target >= arr[mid].startTime && target < arr[mid].endTime) {
return arr[mid].line ?? 0;
} else if(arr[mid].startTime > target){
return find(arr, target, left, mid - 1);
}else{
return find(arr, target, mid + 1, right);
}
}
Index.ets:
import { Bean, calcWidth, find, TextBean } from './Bean';
import { Player } from './Player';
import { display, LengthMetrics } from '@kit.ArkUI';
@Entry
@Component
struct main {
scroller: Scroller = new Scroller();
duration: number = 99000;
@State currentTime: number = 0;
player?: Player
textHeight: number = 16; // 文字高度
columHeight: number = 0;
lines: number = 0;
widthComp: number = 0;
// controller: TextController = new TextController();
@Watch('lineChanged') @State centerLine: number = 0;
controller: RichEditorController = new RichEditorController();
private start: number = 0;
private end: number = 5;
@State currentTabIndex: number = 0
aboutToAppear(): void {
Bean.init(); // 初始化数据
this.widthComp = px2vp(display.getDefaultDisplaySync().width);
}
onPageShow(): void {
this.start = 0
this.end = 0
let w: number = calcWidth(Bean.textArray[0].label)
let line = 1
this.player = new Player((time: number) => {
this.currentTime = time;
if ((Bean.textArray[this.currentTabIndex]) &&
this.currentTime >= Bean.textArray[this.currentTabIndex].startTime &&
this.currentTime < Bean.textArray[this.currentTabIndex].endTime) {
this.controller.updateSpanStyle({
start: this.start,
end: this.end,
textStyle:
{
fontColor: Color.Black
}
})
this.start = this.end
this.end += Bean.textArray[this.currentTabIndex].label.length
let width: number = 0
this.controller.updateSpanStyle({
start: this.start,
end: this.end,
textStyle:
{
fontColor: Color.Blue
}
})
this.controller.getSpans({
start: this.start,
end: this.end
}).forEach(item => {
console.info("text span: " + (item as RichEditorTextSpanResult).value)
width = calcWidth((item as RichEditorTextSpanResult).value)
})
this.currentTabIndex += 1
w = w + width
if (w > this.widthComp) {
w = w - this.widthComp; // 多出的部分设为初始值
line += 1;
}
}
if (line >= Math.floor(this.lines / 2)) {
// 大于一半就开始滚动
this.centerLine = line;
}
})
this.player.setTimer();
}
lineChanged() {
this.scroller.scrollBy(0, (this.textHeight + 5)); // 滚动行高 + 间距lineSpacing
}
build() {
Column() {
Row() {
Text('文字高亮实例')
.fontColor(Color.White)
.fontSize(16)
}
.height(44)
.width('100%')
.backgroundColor(Color.Green)
.padding({
left: 16
})
Scroll(this.scroller) {
Stack() {
Column() {
RichEditor({ controller: this.controller })
.onReady(() => {
for (let i = 0; i <= Bean.textArray.length; i++) {
this.controller.addTextSpan(Bean.textArray[i]?.label || '', {
style: {
fontSize: 16
}
})
}
})
.hitTestBehavior(HitTestMode.None)
.onAreaChange(() => {
let layoutManager = this.controller.getLayoutManager();
let lineCount = "layoutManager LineCount: " + layoutManager.getLineCount()
this.lines = layoutManager.getLineCount()
})
.padding(0)
.width('100%')
}
.width('100%')
.justifyContent(FlexAlign.Start)
}
}
.scrollBar(BarState.Off)
.height('50%')
.backgroundColor(Color.White)
.onAreaChange((oldValue: Area, newValue: Area) => {
this.columHeight = Number(newValue.height);
this.lines = this.columHeight / (this.textHeight + 5);
console.log('lcs --- lines', this.lines);
})
Row() {
Text('查看原文')
Text('复制金句')
Text('智能字幕')
}
.height(50)
.backgroundColor(Color.Green)
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
Slider({
direction: Axis.Horizontal,
value: this.currentTime,
min: 0,
max: this.duration,
style: SliderStyle.OutSet
})
.blockColor(Color.White)
.trackColor(Color.Gray)
.selectedColor(Color.White)
.showTips(false)
.trackThickness(2)
.blockSize({
width: 14,
height: 14
})
.onChange((value: number, mode: SliderChangeMode) => {
this.currentTime = value;
if (mode == SliderChangeMode.Begin || mode === SliderChangeMode.Moving) {
}
if (mode == SliderChangeMode.End) {
this.currentTime = value;
}
})
}
}
}
更多关于HarmonyOS鸿蒙Next中RichEditor实现类似歌词滚动,当前歌词高亮的效果的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS鸿蒙Next中,使用RichEditor实现歌词滚动和高亮效果可以这样做:
-
滚动控制:通过ScrollerController控制滚动位置,结合当前行索引计算偏移量,调用scrollTo方法自动滚动。
-
高亮样式:使用RichEditor的spanStyle对当前行文字设置不同颜色(foregroundColor)和字体大小(fontSize),非当前行使用默认样式。
-
定时刷新:利用定时器或播放进度回调更新currentLineIndex,触发UI刷新。
关键代码片段:
[@State](/user/State) currentLine: number = 0;
scrollerController.scrollTo({xOffset: 0, yOffset: lineHeight * currentLine})
在HarmonyOS Next中实现RichEditor的歌词滚动效果,可以通过以下方案实现:
- 数据结构设计:
- 使用List<LyricLine>存储歌词数据,每个LyricLine包含:
- 开始时间(startTime)
- 结束时间(endTime)
- 歌词文本(content)
- 是否高亮标志(isHighlight)
- 核心实现步骤:
// 1. 初始化RichEditor
let richEditor = new RichEditor(context);
richEditor.setContent(lyricsToHtml(lyricLines));
// 2. 计时器逻辑
let timer = setInterval(() => {
const currentTime = getCurrentPlayerTime();
updateHighlight(currentTime);
}, 100);
// 3. 高亮更新函数
function updateHighlight(currentTime) {
lyricLines.forEach(line => {
line.isHighlight = (currentTime >= line.startTime && currentTime < line.endTime);
});
richEditor.setContent(lyricsToHtml(lyricLines));
scrollToHighlightedLine();
}
// 4. 歌词转HTML
function lyricsToHtml(lines) {
return lines.map(line =>
`<p style="color:${line.isHighlight?'red':'black'}">${line.content}</p>`
).join('');
}
- 滚动控制:
- 使用RichEditor的scrollTo方法定位到当前高亮行
- 可以计算高亮行位置后调用:
richEditor.scrollTo({y: targetYPosition});
- 性能优化:
- 避免频繁重绘,只在时间区间切换时更新UI
- 使用requestAnimationFrame优化滚动动画
- 对长歌词列表进行分页加载
注意:实际实现时需要根据HarmonyOS Next的API调整,特别是RichEditor的具体用法可能会有所不同。