|
@@ -0,0 +1,137 @@
|
|
|
+package com.xunao.effectdemo.view
|
|
|
+
|
|
|
+import android.animation.ValueAnimator
|
|
|
+import android.content.Context
|
|
|
+import android.graphics.Canvas
|
|
|
+import android.graphics.Color
|
|
|
+import android.graphics.Path
|
|
|
+import android.util.AttributeSet
|
|
|
+import android.view.animation.LinearInterpolator
|
|
|
+
|
|
|
+/**
|
|
|
+ * author : 程中强
|
|
|
+ * e-mail : 740479946@qq.com
|
|
|
+ * date : 2022/9/613:46
|
|
|
+ * desc : 歌词变色textview
|
|
|
+ * version: 1.0
|
|
|
+ */
|
|
|
+class SongLyricTextView @JvmOverloads constructor(
|
|
|
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
|
|
+) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyleAttr) {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 文字为播放的颜色
|
|
|
+ * */
|
|
|
+ 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
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onDraw(canvas: Canvas) {
|
|
|
+ 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)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 开始播放
|
|
|
+ *
|
|
|
+ * @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))
|
|
|
+ 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()
|
|
|
+ }
|
|
|
+
|
|
|
+}
|