Android實(shí)現(xiàn)音樂歌詞變色效果

指定播放開始索引和結(jié)束索引的坐標(biāo),例如從0到10;
截取子字符串從0到10,計(jì)算子字符串的寬度playWidth;
開啟差值器,從0到playWidth,設(shè)置duration;
通過差值器不停返回的寬度,計(jì)算canvas中需要繪制變色區(qū)域;
在原本黑色的文字上,繪制一遍變色的字;
/*** 文字未播放的顏色* */private val normalColor = Color.parseColor("#333333")/*** 文字播放的顏色* */private val playColor = Color.parseColor("#fec403")/*** 要變色的寬度* */private var consumeWidth: Float = 0f/*** 是否正在播放中* */private var isPlaying = falseprivate val mPaint = paint/*** 要變色的區(qū)域* */private val path = Path()/*** 負(fù)責(zé)變色差值器* */private val animator by lazy {val animator = ValueAnimator.ofFloat(0f, 1f)animator.interpolator = LinearInterpolator()animator.addUpdateListener {// 開始重繪consumeWidth = it.animatedValue as Floatinvalidate()}animator}
指定播放開始索引和結(jié)束索引的坐標(biāo),例如從0到10;
截取子字符串從0到10,計(jì)算子字符串的寬度playWidth;
開啟差值器,從0到playWidth,設(shè)置duration;
/*** 開始播放** @param startIndex 開始的文字的索引* @param endIndex 結(jié)束的文字的索引* @param duration 播放時(shí)長* */fun startPlayLine(startIndex: Int, endIndex: Int, duration: Long) {isPlaying = trueif (startIndex == -1) return// 計(jì)算從開始位置到開始的文字寬度,可以理解為已經(jīng)播放完畢的文字寬度val startWidth = mPaint.measureText(this.text.substring(0, startIndex))// 計(jì)算即將播放截止的文字的寬度,其實(shí)也可以直接this.text.substring(0, endIndex),問題不大val endWidth = startWidth + mPaint.measureText(this.text.substring(startIndex, endIndex))// 啟動(dòng)差值器animator.setFloatValues(startWidth, endWidth)animator.duration = if (duration > 0) duration else 1000animator.start()}/*** 停止播放* */fun stopPlay() {isPlaying = falseanimator.cancel()invalidate()}
override fun onDraw(canvas: Canvas) {// 調(diào)用一遍super,把黑色的字寫一遍mPaint.color = normalColorsuper.onDraw(canvas)if (layout == null) {invalidate()return}// 是否是播放狀態(tài)if (isPlaying) {path.reset()// 因?yàn)樾枨笫钦胁シ牛钥梢酝ㄟ^行數(shù)來遍歷val lineCount = layout.lineCountval content = text.toString()for (i in 0 until lineCount) {// 計(jì)算一行文字的寬度val lineWidth = mPaint.measureText(content.substring(layout.getLineStart(i), layout.getLineEnd(i)))// 如果已經(jīng)播放過了if (lineWidth <= consumeWidth) {// 如果是之前已經(jīng)變色區(qū)域,直接添加到path中// 減去這一行的寬度consumeWidth -= lineWidthpath.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}}// 設(shè)置需要繪制的區(qū)域,繪制一遍變色的字canvas.clipPath(path)mPaint.color = playColorlayout.draw(canvas)}}
// 開始播放start.setOnClickListener {val lyricList = lyric.split("\n")var startIndex = 0var delayTime = 0LlyricList.forEach {val startIndexTemp = startIndexval duration = (500 + Math.random() * 500).toLong()text.postDelayed({text.startPlayLine(startIndexTemp,min(lyric.length, startIndexTemp + it.length + 1), // 因?yàn)橛袀€(gè)換行符,所以 + 1duration)},delayTime)delayTime += duration + 50startIndex += it.length + 1}}// 停止播放end.setOnClickListener {text.stopPlay()}
評(píng)論
圖片
表情
