
鸿蒙Next实现歌词字体颜色渐变 原创 精华
实现一个音乐播放器中,歌词颜色渐变的效果。简单点说,就是解析歌词内容,拿到每个词,或者每句话的开始时间、结束时间,执行颜色渐变的动画,然后依次执行。今天这篇先实现一个简单的一句话字体颜色渐变效果。
实现演示:
实现思路:
1.实现字体颜色渐变可以使用之前介绍过的Canvas绘制字体和线性渐变色实现,这种方案需要大量的绘制,相对复杂。
2.如果让字体镂空,底部背景使用animateTo实现颜色渐变,这样也可以实现字体颜色渐变。这样就需要用到一个重要属性blendMode
**图像混合模式:**blendMode
这是一个通用属性,是将当前控件的内容(包含子节点内容)与下方画布(可能为离屏画布)已有内容进行混合。
blendMode(value: BlendMode, type?: BlendApplyType)
BlendMode:混合模式。 这里主要介绍需要用到的两种模式,之后出一期总结一下这个属性的各种效果。
DST_IN:只显示目标像素中与源像素重叠的部分。
SRC_OVER:将源像素按照透明度进行混合,覆盖在目标像素上
BlendApplyType:是否离屏
离屏时会创建当前组件大小的离屏画布,再将当前组件(含子组件)的内容绘制到离屏画布上,再用指定的混合模式与下方画布已有内容进行混合。
3.每个歌词,按照动画时常,依次执行动画,因此可以分为执行过、正在执行、未执行三种状态,根据这三种状态分别设置歌词的渐变色。
避坑:
由于当前animateTo与V2状态装饰器的刷新机制不兼容,如果使用V2需要animateToImmediately先执行依次,再执行animateTo,见下面代码,如果使用V1则正常,不需要加。
源码
@Entry
@ComponentV2
struct LyricTest {
private uiContext = this.getUIContext();
lineLrc: string[] = 'HamonyOS学习笔记'.split('')
@Local playState: boolean = false
@Local currentIndex: number = 0; //每词在每句中索引
@Local colorStopValue: number = 0;
@Local lrcDuration: number = 1000; // 歌词持续时间
timer: number | null = null
playLrc() {
//由于当前animateTo与V2的刷新机制不兼容
//将额外的修改先刷新,再执行原来的动画达成预期的效果
animateToImmediately({
duration: 0
}, () => {
})
this.uiContext.animateTo({
duration: this.lrcDuration,
finishCallbackType: FinishCallbackType.LOGICALLY,
curve: Curve.Linear,
iterations: 1,
onFinish: () => {
this.colorStopValue = 0
this.currentIndex++;
if (this.currentIndex < this.lineLrc.length) {
this.playLrc();
}else {
this.currentIndex=0
this.playState = false
}
}
}, () => {
this.colorStopValue = 1
})
}
build() {
Column({space:20}) {
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) {
ForEach(this.lineLrc, (text: string, wordIndex: number) => {
Row() {
Text(text)
.fontSize(30)
.blendMode(BlendMode.DST_IN, BlendApplyType.OFFSCREEN)
}
.margin({right:5})
.blendMode(BlendMode.SRC_OVER, BlendApplyType.OFFSCREEN)
.linearGradient({
direction: GradientDirection.Right,
colors: (() => {
if (wordIndex < this.currentIndex) {
// 已播放的字:全红
return [['#FF0000', 0.0], ['#FF0000', 1.0]];
} else if (wordIndex === this.currentIndex) {
// 正在播放的字:红色渐变(从 0 到 this.colorStopValue)
return [
['#FF0000', 0.0],
['#FF0000', this.colorStopValue],
['#FFFFFF', this.colorStopValue],
['#FFFFFF', 1.0]
];
} else {
// 未播放的字:全白
return [['#FFFFFF', 0], ['#FFFFFF', 1.0]];
}
})()
})
})
}
.width('90%')
.margin({top:30})
Button(this.playState?'暂停':'开始').fontSize(24).onClick(()=>{
this.playState=!this.playState
if (this.playState)this.playLrc();
})
}
.width('100%')
.height('100%')
.linearGradient({
// 渐变方向
direction: GradientDirection.Bottom,
// 渐变颜色是否重复
repeating: true,
// 数组末尾元素占比小于1时满足重复着色效果
colors: [['#17233c', 0.0], ['#2b3755', 0.6], ['#181e34', 1]]
})
}
}
