|
@@ -3,7 +3,6 @@ import { LyricController } from '../LyricController';
|
|
|
import { Lyric } from '../bean/Lyric';
|
|
import { Lyric } from '../bean/Lyric';
|
|
|
import { ListAdapter } from '../extensions/ListAdapter';
|
|
import { ListAdapter } from '../extensions/ListAdapter';
|
|
|
import { LyricLine } from '../bean/LyricLine';
|
|
import { LyricLine } from '../bean/LyricLine';
|
|
|
-import { LyricWord } from '../bean/LyricWord';
|
|
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* A component to display the lyric with scroll animation.
|
|
* A component to display the lyric with scroll animation.
|
|
@@ -49,7 +48,6 @@ export struct LyricView2 {
|
|
|
@State currentIndex: number = 0 // the focused index of the lyric line list.
|
|
@State currentIndex: number = 0 // the focused index of the lyric line list.
|
|
|
@State isLyricEmpty: boolean = true // is no lyric.
|
|
@State isLyricEmpty: boolean = true // is no lyric.
|
|
|
@State textSize: number = 16
|
|
@State textSize: number = 16
|
|
|
- @State isSingleLine: boolean = false
|
|
|
|
|
@State lineSpace: number = 16
|
|
@State lineSpace: number = 16
|
|
|
@State textWeight: number = FontWeight.Medium
|
|
@State textWeight: number = FontWeight.Medium
|
|
|
@State textHighlightSize: number = 18
|
|
@State textHighlightSize: number = 18
|
|
@@ -71,7 +69,6 @@ export struct LyricView2 {
|
|
|
@State isLoadingData: boolean = false
|
|
@State isLoadingData: boolean = false
|
|
|
private loadTimeout = -1
|
|
private loadTimeout = -1
|
|
|
@State isHightLightCenter: boolean = true
|
|
@State isHightLightCenter: boolean = true
|
|
|
- @State currentMediaPosition: number = 0
|
|
|
|
|
|
|
|
|
|
private onDataChangedListener = (lyric: Lyric | null) => {
|
|
private onDataChangedListener = (lyric: Lyric | null) => {
|
|
|
clearTimeout(this.loadTimeout)
|
|
clearTimeout(this.loadTimeout)
|
|
@@ -82,7 +79,6 @@ export struct LyricView2 {
|
|
|
}, 300)
|
|
}, 300)
|
|
|
}
|
|
}
|
|
|
private onPositionChangedListener = (mediaPosition: number) => {
|
|
private onPositionChangedListener = (mediaPosition: number) => {
|
|
|
- this.currentMediaPosition = mediaPosition
|
|
|
|
|
this.onPositionChanged(mediaPosition)
|
|
this.onPositionChanged(mediaPosition)
|
|
|
}
|
|
}
|
|
|
private onInvalidatedListener = (reLayout: boolean) => {
|
|
private onInvalidatedListener = (reLayout: boolean) => {
|
|
@@ -130,7 +126,6 @@ export struct LyricView2 {
|
|
|
private getAttrFromController() {
|
|
private getAttrFromController() {
|
|
|
this.currentLyric = this.controller.getLyric()
|
|
this.currentLyric = this.controller.getLyric()
|
|
|
this.textSize = this.controller.getTextSize()
|
|
this.textSize = this.controller.getTextSize()
|
|
|
- this.isSingleLine = this.controller.getSingleLine()
|
|
|
|
|
this.blurDegree = this.controller.getBlurDegree()
|
|
this.blurDegree = this.controller.getBlurDegree()
|
|
|
this.isHightLightCenter = this.controller.getHightLightCenter()
|
|
this.isHightLightCenter = this.controller.getHightLightCenter()
|
|
|
this.lineSpace = this.controller.getLineSpace()
|
|
this.lineSpace = this.controller.getLineSpace()
|
|
@@ -173,8 +168,7 @@ export struct LyricView2 {
|
|
|
|
|
|
|
|
private calculateOpacityFactor(index: number, currentIndex: number): number {
|
|
private calculateOpacityFactor(index: number, currentIndex: number): number {
|
|
|
const distance = Math.abs(index - currentIndex);
|
|
const distance = Math.abs(index - currentIndex);
|
|
|
- let maxOp = Math.max(0.3,1-this.blurDegree*0.2)
|
|
|
|
|
- return Math.max(maxOp, 1 - distance * 0.08); // 透明度随着距离增加而减小
|
|
|
|
|
|
|
+ return Math.max(0.5, 1 - distance * 0.18); // 透明度随着距离增加而减小
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -187,15 +181,32 @@ export struct LyricView2 {
|
|
|
LazyForEach(this.listAdapter, (item: LyricLine, index: number) => {
|
|
LazyForEach(this.listAdapter, (item: LyricLine, index: number) => {
|
|
|
ListItem() {
|
|
ListItem() {
|
|
|
Stack() {
|
|
Stack() {
|
|
|
- // 逐字歌词渲染
|
|
|
|
|
- if (item.hasWords() && item.words.length > 0) {
|
|
|
|
|
- this.WordByWordLyric(item, index)
|
|
|
|
|
- } else {
|
|
|
|
|
- // 普通歌词渲染(原有逻辑)
|
|
|
|
|
- this.NormalLyricLine(item, index)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
|
|
+ Text(item.text)
|
|
|
|
|
+ .fontSize(this.textSize)
|
|
|
|
|
+ .opacity(this.calculateOpacityFactor(index, this.currentIndex))
|
|
|
|
|
+ .blur( this.calculateBlurFactor(index, this.currentIndex))
|
|
|
|
|
+ .textAlign(this.alignMode == 'center' ? TextAlign.Center : TextAlign.Start)
|
|
|
|
|
+ .scale({
|
|
|
|
|
+ x: index == this.currentIndex ? this.controller.getHighlightScale() : 1,
|
|
|
|
|
+ y: index == this.currentIndex ? this.controller.getHighlightScale() : 1,
|
|
|
|
|
+ centerX: this.alignMode == 'center' ? '50%' : 0
|
|
|
|
|
+ })
|
|
|
|
|
+ .fontColor(index == this.currentIndex ? this.textHighlightColor : this.textColor)
|
|
|
|
|
+ // .fontWeight(this.textWeight)
|
|
|
|
|
+ .fontWeight(index == this.currentIndex && this.isHighlightBold ? FontWeight.Bold :
|
|
|
|
|
+ this.textWeight)
|
|
|
|
|
+ // .padding({
|
|
|
|
|
+ .animation({ duration: this.animDuration, curve: Curve.FastOutSlowIn })
|
|
|
|
|
+ .width(this.alignMode == 'center'?'100%':'76%')
|
|
|
|
|
+
|
|
|
|
|
+ // if (this.seekUIStyle == 'listItem' && index == this.seekIndex && item.text.length > 0
|
|
|
|
|
+ // && this.enableSeek && this.isUserTouching) {
|
|
|
|
|
+ // Row() {
|
|
|
|
|
+ // Image($r('app.media.cclyric_play'))
|
|
|
|
|
+ // .width(24)
|
|
|
|
|
+ // .height(24)
|
|
|
|
|
+ // .fillColor(this.seekUIColor)
|
|
|
|
|
+ // .objectFit(ImageFit.Fill)
|
|
|
Text(this.scrollDurationText)
|
|
Text(this.scrollDurationText)
|
|
|
.fontSize(this.textSize)
|
|
.fontSize(this.textSize)
|
|
|
.fontColor(this.seekUIColor)
|
|
.fontColor(this.seekUIColor)
|
|
@@ -203,16 +214,14 @@ export struct LyricView2 {
|
|
|
.width(100)
|
|
.width(100)
|
|
|
.visibility(this.seekUIStyle == 'listItem' && index == this.seekIndex && item.text.length > 0
|
|
.visibility(this.seekUIStyle == 'listItem' && index == this.seekIndex && item.text.length > 0
|
|
|
&& this.enableSeek && this.isUserTouching?Visibility.Visible:Visibility.Hidden)
|
|
&& this.enableSeek && this.isUserTouching?Visibility.Visible:Visibility.Hidden)
|
|
|
-
|
|
|
|
|
|
|
+ // }.width('100%')
|
|
|
|
|
+ // .justifyContent(FlexAlign.SpaceBetween)
|
|
|
|
|
+ // }
|
|
|
}
|
|
}
|
|
|
.align(Alignment.End)
|
|
.align(Alignment.End)
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
.padding(8)
|
|
.padding(8)
|
|
|
- .transition(TransitionEffect.asymmetric(TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 500 }),
|
|
|
|
|
- TransitionEffect.scale({ x: 0, y: 0 }) ))
|
|
|
|
|
- // .transition(TransitionEffect.asymmetric(TransitionEffect.move(TransitionEdge.BOTTOM).animation({ duration: 500,curve: Curve.Ease, delay: 100*index }),
|
|
|
|
|
- // TransitionEffect.scale({ x: 0, y: 0 }) ))
|
|
|
|
|
.border({ radius: 4 })
|
|
.border({ radius: 4 })
|
|
|
.backgroundColor(this.seekUIStyle == 'listItem' && index == this.seekIndex && item.text.length > 0
|
|
.backgroundColor(this.seekUIStyle == 'listItem' && index == this.seekIndex && item.text.length > 0
|
|
|
&& this.enableSeek && this.isUserTouching ? '#80a9a9a9' : '#00000000')
|
|
&& this.enableSeek && this.isUserTouching ? '#80a9a9a9' : '#00000000')
|
|
@@ -232,10 +241,6 @@ export struct LyricView2 {
|
|
|
.height('100%')
|
|
.height('100%')
|
|
|
.scrollBar(BarState.Off)
|
|
.scrollBar(BarState.Off)
|
|
|
.cachedCount(this.cacheSize)
|
|
.cachedCount(this.cacheSize)
|
|
|
- .transition(TransitionEffect.asymmetric(
|
|
|
|
|
- this.isSingleLine? TransitionEffect.scale({ x: 1.2, y: 1.2 }).animation({ duration: 500 }):
|
|
|
|
|
- TransitionEffect.move(TransitionEdge.BOTTOM).animation({ duration: 500 }),
|
|
|
|
|
- TransitionEffect.scale({ x: 0, y: 0 }) ))
|
|
|
|
|
.visibility(this.isLoadingData ? Visibility.Hidden : Visibility.Visible)
|
|
.visibility(this.isLoadingData ? Visibility.Hidden : Visibility.Visible)
|
|
|
.onScrollIndex((_, __, center) => {
|
|
.onScrollIndex((_, __, center) => {
|
|
|
const now = Date.now()
|
|
const now = Date.now()
|
|
@@ -275,82 +280,14 @@ export struct LyricView2 {
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
- // 普通歌词渲染(原有逻辑)
|
|
|
|
|
- @Builder
|
|
|
|
|
- NormalLyricLine(item: LyricLine, index: number) {
|
|
|
|
|
- Text(item.text)
|
|
|
|
|
- .fontSize(this.textSize)
|
|
|
|
|
- .opacity(this.calculateOpacityFactor(index, this.currentIndex))
|
|
|
|
|
- .blur(this.calculateBlurFactor(index, this.currentIndex))
|
|
|
|
|
- .textAlign(this.alignMode == 'center' ? TextAlign.Center : TextAlign.Start)
|
|
|
|
|
- .scale({
|
|
|
|
|
- x: index == this.currentIndex ? this.controller.getHighlightScale() : 1,
|
|
|
|
|
- y: index == this.currentIndex ? this.controller.getHighlightScale() : 1,
|
|
|
|
|
- centerX: this.alignMode == 'center' ? '50%' : 0
|
|
|
|
|
- })
|
|
|
|
|
- .fontColor(index == this.currentIndex ? this.textHighlightColor : this.textColor)
|
|
|
|
|
- .fontWeight(index == this.currentIndex && this.isHighlightBold ? FontWeight.Bold : this.textWeight)
|
|
|
|
|
- .animation({ duration: this.animDuration, curve: Curve.FastOutSlowIn })
|
|
|
|
|
- .visibility(this.isSingleLine?
|
|
|
|
|
- (index == this.currentIndex ?Visibility.Visible:Visibility.None)
|
|
|
|
|
- :Visibility.Visible)
|
|
|
|
|
- .width(this.alignMode == 'center' ? '100%' : '76%')
|
|
|
|
|
- }
|
|
|
|
|
- // 逐字歌词渲染
|
|
|
|
|
- @Builder
|
|
|
|
|
- WordByWordLyric(item: LyricLine, index: number) {
|
|
|
|
|
- Row({ space: 0 }) {
|
|
|
|
|
- ForEach(item.words, (word: LyricWord, wordIndex: number) => {
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- Text(word.word)
|
|
|
|
|
- .fontSize(index == this.currentIndex ?this.textSize*this.controller.getHighlightScale():this.textSize)
|
|
|
|
|
- .fontColor(this.currentMediaPosition >= word.startTime ?
|
|
|
|
|
- index == this.currentIndex ? this.textHighlightColor : this.textColor : this.textColor)
|
|
|
|
|
- .fontWeight(this.currentMediaPosition >= word.startTime && this.isHighlightBold ?
|
|
|
|
|
- index == this.currentIndex? FontWeight.Bold : this.textWeight: this.textWeight)
|
|
|
|
|
- .margin(0)
|
|
|
|
|
- .opacity(this.calculateOpacityFactor(index, this.currentIndex))
|
|
|
|
|
- .blur(this.calculateBlurFactor(index, this.currentIndex))
|
|
|
|
|
- .visibility(this.isSingleLine?
|
|
|
|
|
- (index == this.currentIndex ?Visibility.Visible:Visibility.None)
|
|
|
|
|
- :Visibility.Visible)
|
|
|
|
|
- .animation({
|
|
|
|
|
- // 动画播放速度
|
|
|
|
|
- tempo: 0.8,
|
|
|
|
|
- // 动画持续时间,单位是毫秒
|
|
|
|
|
- duration: 777,
|
|
|
|
|
- // 动画缓动函数
|
|
|
|
|
- curve: Curve.FastOutSlowIn
|
|
|
|
|
- })
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- .justifyContent(this.alignMode === 'center' ? FlexAlign.Center : FlexAlign.Start)
|
|
|
|
|
- .width(this.alignMode == 'center' ? '100%' :index == this.currentIndex ?'95%': '85%')
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- isTopBottomLine(index:number){
|
|
|
|
|
- return index === 0 || index === this.listAdapter.totalCount() - 1;
|
|
|
|
|
- }
|
|
|
|
|
- //修复get Property index out of bounds
|
|
|
|
|
private handleSeekAction() {
|
|
private handleSeekAction() {
|
|
|
clearTimeout(this.seekUiHideTimeout);
|
|
clearTimeout(this.seekUiHideTimeout);
|
|
|
-
|
|
|
|
|
- let itemCount = this.listAdapter.totalCount(); // 获取数据项的总数
|
|
|
|
|
-
|
|
|
|
|
- // 确保 seekIndex 在有效范围内
|
|
|
|
|
- if (this.seekIndex >= 0 && this.seekIndex < itemCount) {
|
|
|
|
|
- let targetPosition = this.listAdapter.getData(this.seekIndex).beginTime;
|
|
|
|
|
- let isPlayerHandled = this.onSeekAction(targetPosition);
|
|
|
|
|
- if (!isPlayerHandled) {
|
|
|
|
|
- this.animateToIndex(this.currentIndex);
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- console.error("Seek index out of bounds:", this.seekIndex);
|
|
|
|
|
- // 处理超出范围的情况,比如设定默认值或抛出错误
|
|
|
|
|
|
|
+ let targetPosition = this.listAdapter.getData(this.seekIndex).beginTime;
|
|
|
|
|
+ let isPlayerHandled = this.onSeekAction(targetPosition);
|
|
|
|
|
+ if (!isPlayerHandled) {
|
|
|
|
|
+ this.animateToIndex(this.currentIndex);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
this.isUserTouching = false;
|
|
this.isUserTouching = false;
|
|
|
}
|
|
}
|
|
|
|
|
|