Android实现音乐歌词变色效果

龙旋

共 4648字,需浏览 10分钟

 · 2021-09-17

效果图


我们为这个自定义控件起名为:SongLyricTextView。要想实现这个效果唯一要解决的问题是如何计算播放文字变色的进度,我就直接抛砖引玉,说一下自己的实现方案。‍

  1. 指定播放开始索引和结束索引的坐标,例如从0到10;

  2. 截取子字符串从0到10,计算子字符串的宽度playWidth;

  3. 开启差值器,从0到playWidth,设置duration;

  4. 通过差值器不停返回的宽度,计算canvas中需要绘制变色区域;

  5. 在原本黑色的文字上,绘制一遍变色的字;


接下来就按照这个步骤实现demo中的效果,首先我们定义基本的属性:
    /**     * 文字未播放的颜色     * */    private val normalColor = Color.parseColor("#333333")
/** * 文字播放的颜色 * */ private val playColor = Color.parseColor("#fec403")
/** * 要变色的宽度 * */ private var consumeWidth: Float = 0f
/** * 是否正在播放中 * */ private var isPlaying = false
private val mPaint = paint
/** * 要变色的区域 * */ private val path = Path()
/** * 负责变色差值器 * */ private val animator by lazy { val animator = ValueAnimator.ofFloat(0f, 1f) animator.interpolator = LinearInterpolator() animator.addUpdateListener { // 开始重绘 consumeWidth = it.animatedValue as Float invalidate() } animator }

接下来,把刚才提到的前三步整合为一个开始播放的方法:

  1. 指定播放开始索引和结束索引的坐标,例如从0到10;

  2. 截取子字符串从0到10,计算子字符串的宽度playWidth;

  3. 开启差值器,从0到playWidth,设置duration;

/**     * 开始播放     *     * @param startIndex 开始的文字的索引     * @param endIndex 结束的文字的索引     * @param duration 播放时长     * */    fun startPlayLine(startIndex: Int, endIndex: Int, duration: Long) {        isPlaying = true        if (startIndex == -1) return        // 计算从开始位置到开始的文字宽度,可以理解为已经播放完毕的文字宽度        val startWidth = mPaint.measureText(this.text.substring(0, startIndex))        // 计算即将播放截止的文字的宽度,其实也可以直接this.text.substring(0, endIndex),问题不大        val endWidth = startWidth + mPaint.measureText(this.text.substring(startIndex, endIndex))        // 启动差值器        animator.setFloatValues(startWidth, endWidth)        animator.duration = if (duration > 0) duration else 1000        animator.start()    }
/** * 停止播放 * */ fun stopPlay() { isPlaying = false animator.cancel() invalidate() }

有开始播放的方法,那就肯定要有停止播放,代码也非常的简单。因为之前工作的需求是从第一句开始播放,如果你想要播放中间某一段文字,可以对demo中的代码自己做一些修改。

最后是计算canvas绘制的区域:
 override fun onDraw(canvas: Canvas) {        // 调用一遍super,把黑色的字写一遍        mPaint.color = normalColor        super.onDraw(canvas)
if (layout == null) { invalidate() return } // 是否是播放状态 if (isPlaying) { path.reset() // 因为需求是整行播放,所以可以通过行数来遍历 val lineCount = layout.lineCount val content = text.toString() for (i in 0 until lineCount) { // 计算一行文字的宽度 val lineWidth = mPaint.measureText( content.substring(layout.getLineStart(i), layout.getLineEnd(i)) ) // 如果已经播放过了 if (lineWidth <= consumeWidth) { // 如果是之前已经变色区域,直接添加到path中 // 减去这一行的宽度 consumeWidth -= lineWidth path.addRect( layout.getLineLeft(i), layout.getLineTop(i).toFloat(), layout.getLineRight(i), layout.getLineBottom(i).toFloat(), Path.Direction.CCW ) } else { // 如果该行正好是要变色的行,直接改变颜色 // 把需要的consumeWidth放入path中 path.addRect( layout.getLineLeft(i), layout.getLineTop(i).toFloat(), layout.getLineLeft(i) + consumeWidth, layout.getLineBottom(i).toFloat(), Path.Direction.CCW ) break } } // 设置需要绘制的区域,绘制一遍变色的字 canvas.clipPath(path) mPaint.color = playColor layout.draw(canvas) } }

最后看一下demo中具体使用SongLyricTextView的用法:
// 开始播放start.setOnClickListener {     val lyricList = lyric.split("\n")     var startIndex = 0     var delayTime = 0L     lyricList.forEach {         val startIndexTemp = startIndex         val duration = (500 + Math.random() * 500).toLong()         text.postDelayed(             {                 text.startPlayLine(                     startIndexTemp,                     min(lyric.length, startIndexTemp + it.length + 1), // 因为有个换行符,所以 + 1                     duration                 )             },             delayTime         )         delayTime += duration + 50         startIndex += it.length + 1     }}// 停止播放end.setOnClickListener {    text.stopPlay()}

源码地址:
https://github.com/li504799868/SongLyricTextView

到这里就结束啦。
浏览 88
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报