Use Coroutines in LrcView
This commit is contained in:
parent
0f66d005c7
commit
dd59459786
4 changed files with 574 additions and 811 deletions
|
@ -19,7 +19,6 @@ import android.content.Context
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.AsyncTask
|
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.Layout
|
import android.text.Layout
|
||||||
import android.text.StaticLayout
|
import android.text.StaticLayout
|
||||||
|
@ -35,14 +34,15 @@ import android.widget.Scroller
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.withSave
|
import androidx.core.graphics.withSave
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.lang.Runnable
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 歌词 Created by wcy on 2015/11/9.
|
* 歌词 Created by wcy on 2015/11/9.
|
||||||
*/
|
*/
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
@Suppress("deprecation")
|
|
||||||
class CoverLrcView @JvmOverloads constructor(
|
class CoverLrcView @JvmOverloads constructor(
|
||||||
context: Context?,
|
context: Context?,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
|
@ -72,7 +72,6 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
private var mScroller: Scroller? = null
|
private var mScroller: Scroller? = null
|
||||||
private var mOffset = 0f
|
private var mOffset = 0f
|
||||||
private var mCurrentLine = 0
|
private var mCurrentLine = 0
|
||||||
private var flag: Any? = null
|
|
||||||
private var isShowTimeline = false
|
private var isShowTimeline = false
|
||||||
private var isTouching = false
|
private var isTouching = false
|
||||||
private var isFling = false
|
private var isFling = false
|
||||||
|
@ -85,9 +84,8 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private val viewScope = CoroutineScope(Dispatchers.Main + Job())
|
||||||
* 手势监听器
|
|
||||||
*/
|
|
||||||
private val mSimpleOnGestureListener: SimpleOnGestureListener =
|
private val mSimpleOnGestureListener: SimpleOnGestureListener =
|
||||||
object : SimpleOnGestureListener() {
|
object : SimpleOnGestureListener() {
|
||||||
override fun onDown(e: MotionEvent): Boolean {
|
override fun onDown(e: MotionEvent): Boolean {
|
||||||
|
@ -251,42 +249,31 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
mScroller = Scroller(context)
|
mScroller = Scroller(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置非当前行歌词字体颜色 */
|
|
||||||
fun setNormalColor(normalColor: Int) {
|
fun setNormalColor(normalColor: Int) {
|
||||||
mNormalTextColor = normalColor
|
mNormalTextColor = normalColor
|
||||||
postInvalidate()
|
postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置当前行歌词的字体颜色 */
|
|
||||||
fun setCurrentColor(currentColor: Int) {
|
fun setCurrentColor(currentColor: Int) {
|
||||||
mCurrentTextColor = currentColor
|
mCurrentTextColor = currentColor
|
||||||
postInvalidate()
|
postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置拖动歌词时选中歌词的字体颜色 */
|
|
||||||
fun setTimelineTextColor(timelineTextColor: Int) {
|
fun setTimelineTextColor(timelineTextColor: Int) {
|
||||||
mTimelineTextColor = timelineTextColor
|
mTimelineTextColor = timelineTextColor
|
||||||
postInvalidate()
|
postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置拖动歌词时时间线的颜色 */
|
|
||||||
fun setTimelineColor(timelineColor: Int) {
|
fun setTimelineColor(timelineColor: Int) {
|
||||||
mTimelineColor = timelineColor
|
mTimelineColor = timelineColor
|
||||||
postInvalidate()
|
postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置拖动歌词时右侧时间字体颜色 */
|
|
||||||
fun setTimeTextColor(timeTextColor: Int) {
|
fun setTimeTextColor(timeTextColor: Int) {
|
||||||
mTimeTextColor = timeTextColor
|
mTimeTextColor = timeTextColor
|
||||||
postInvalidate()
|
postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置歌词是否允许拖动
|
|
||||||
*
|
|
||||||
* @param draggable 是否允许拖动
|
|
||||||
* @param onPlayClickListener 设置歌词拖动后播放按钮点击监听器,如果允许拖动,则不能为 null
|
|
||||||
*/
|
|
||||||
fun setDraggable(draggable: Boolean, onPlayClickListener: OnPlayClickListener?) {
|
fun setDraggable(draggable: Boolean, onPlayClickListener: OnPlayClickListener?) {
|
||||||
mOnPlayClickListener = if (draggable) {
|
mOnPlayClickListener = if (draggable) {
|
||||||
requireNotNull(onPlayClickListener) { "if draggable == true, onPlayClickListener must not be null" }
|
requireNotNull(onPlayClickListener) { "if draggable == true, onPlayClickListener must not be null" }
|
||||||
|
@ -296,17 +283,6 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置播放按钮点击监听器
|
|
||||||
*
|
|
||||||
* @param onPlayClickListener 如果为非 null ,则激活歌词拖动功能,否则将将禁用歌词拖动功能
|
|
||||||
*/
|
|
||||||
@Deprecated("use {@link #setDraggable(boolean, OnPlayClickListener)} instead")
|
|
||||||
fun setOnPlayClickListener(onPlayClickListener: OnPlayClickListener?) {
|
|
||||||
mOnPlayClickListener = onPlayClickListener
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 设置歌词为空时屏幕中央显示的文字,如“暂无歌词” */
|
|
||||||
fun setLabel(label: String?) {
|
fun setLabel(label: String?) {
|
||||||
runOnUi {
|
runOnUi {
|
||||||
mDefaultLabel = label
|
mDefaultLabel = label
|
||||||
|
@ -314,106 +290,40 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载歌词文件
|
|
||||||
*
|
|
||||||
* @param lrcFile 歌词文件
|
|
||||||
*/
|
|
||||||
fun loadLrc(lrcFile: File) {
|
fun loadLrc(lrcFile: File) {
|
||||||
loadLrc(lrcFile, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载双语歌词文件,两种语言的歌词时间戳需要一致
|
|
||||||
*
|
|
||||||
* @param mainLrcFile 第一种语言歌词文件
|
|
||||||
* @param secondLrcFile 第二种语言歌词文件
|
|
||||||
*/
|
|
||||||
private fun loadLrc(mainLrcFile: File, secondLrcFile: File?) {
|
|
||||||
runOnUi {
|
runOnUi {
|
||||||
reset()
|
reset()
|
||||||
val sb = StringBuilder("file://")
|
viewScope.launch(Dispatchers.IO) {
|
||||||
sb.append(mainLrcFile.path)
|
val lrcEntries = LrcUtils.parseLrc(arrayOf(lrcFile, null))
|
||||||
if (secondLrcFile != null) {
|
withContext(Dispatchers.Main) {
|
||||||
sb.append("#").append(secondLrcFile.path)
|
|
||||||
}
|
|
||||||
val flag = sb.toString()
|
|
||||||
this.flag = flag
|
|
||||||
object : AsyncTask<File?, Int?, List<LrcEntry>>() {
|
|
||||||
override fun doInBackground(vararg params: File?): List<LrcEntry>? {
|
|
||||||
return LrcUtils.parseLrc(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPostExecute(lrcEntries: List<LrcEntry>) {
|
|
||||||
if (flag == flag) {
|
|
||||||
onLrcLoaded(lrcEntries)
|
onLrcLoaded(lrcEntries)
|
||||||
this@CoverLrcView.flag = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.execute(mainLrcFile, secondLrcFile)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载歌词文本
|
|
||||||
*
|
|
||||||
* @param lrcText 歌词文本
|
|
||||||
*/
|
|
||||||
fun loadLrc(lrcText: String?) {
|
fun loadLrc(lrcText: String?) {
|
||||||
loadLrc(lrcText, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载双语歌词文本,两种语言的歌词时间戳需要一致
|
|
||||||
*
|
|
||||||
* @param mainLrcText 第一种语言歌词文本
|
|
||||||
* @param secondLrcText 第二种语言歌词文本
|
|
||||||
*/
|
|
||||||
private fun loadLrc(mainLrcText: String?, secondLrcText: String?) {
|
|
||||||
runOnUi {
|
runOnUi {
|
||||||
reset()
|
reset()
|
||||||
val sb = StringBuilder("file://")
|
viewScope.launch(Dispatchers.IO) {
|
||||||
sb.append(mainLrcText)
|
val lrcEntries = LrcUtils.parseLrc(arrayOf(lrcText, null))
|
||||||
if (secondLrcText != null) {
|
withContext(Dispatchers.Main) {
|
||||||
sb.append("#").append(secondLrcText)
|
|
||||||
}
|
|
||||||
val flag = sb.toString()
|
|
||||||
this.flag = flag
|
|
||||||
object : AsyncTask<String?, Int?, List<LrcEntry>>() {
|
|
||||||
override fun doInBackground(vararg params: String?): List<LrcEntry>? {
|
|
||||||
return LrcUtils.parseLrc(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPostExecute(lrcEntries: List<LrcEntry>) {
|
|
||||||
if (flag == flag) {
|
|
||||||
onLrcLoaded(lrcEntries)
|
onLrcLoaded(lrcEntries)
|
||||||
this@CoverLrcView.flag = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.execute(mainLrcText, secondLrcText)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 歌词是否有效
|
|
||||||
*
|
|
||||||
* @return true,如果歌词有效,否则false
|
|
||||||
*/
|
|
||||||
fun hasLrc(): Boolean {
|
fun hasLrc(): Boolean {
|
||||||
return mLrcEntryList.isNotEmpty()
|
return mLrcEntryList.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新歌词
|
|
||||||
*
|
|
||||||
* @param time 当前播放时间
|
|
||||||
*/
|
|
||||||
fun updateTime(time: Long) {
|
fun updateTime(time: Long) {
|
||||||
runOnUi {
|
runOnUi {
|
||||||
if (!hasLrc()) {
|
if (!hasLrc()) {
|
||||||
return@runOnUi
|
return@runOnUi
|
||||||
}
|
}
|
||||||
val line = findShowLine(time)
|
val line = findShowLine(time - 300L)
|
||||||
if (line != mCurrentLine) {
|
if (line != mCurrentLine) {
|
||||||
mCurrentLine = line
|
mCurrentLine = line
|
||||||
if (!isShowTimeline) {
|
if (!isShowTimeline) {
|
||||||
|
@ -441,9 +351,9 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
super.onDraw(canvas)
|
super.onDraw(canvas)
|
||||||
val centerY = height / 2
|
val centerY = height / 2
|
||||||
|
|
||||||
// 无歌词文件
|
|
||||||
if (!hasLrc()) {
|
if (!hasLrc()) {
|
||||||
mLrcPaint.color = mCurrentTextColor
|
mLrcPaint.color = mCurrentTextColor
|
||||||
|
@Suppress("Deprecation")
|
||||||
@SuppressLint("DrawAllocation") val staticLayout = StaticLayout(
|
@SuppressLint("DrawAllocation") val staticLayout = StaticLayout(
|
||||||
mDefaultLabel,
|
mDefaultLabel,
|
||||||
mLrcPaint,
|
mLrcPaint,
|
||||||
|
@ -485,11 +395,6 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 画一行歌词
|
|
||||||
*
|
|
||||||
* @param y 歌词中心 Y 坐标
|
|
||||||
*/
|
|
||||||
private fun drawText(canvas: Canvas, staticLayout: StaticLayout, y: Float) {
|
private fun drawText(canvas: Canvas, staticLayout: StaticLayout, y: Float) {
|
||||||
canvas.withSave {
|
canvas.withSave {
|
||||||
translate(mLrcPadding, y - (staticLayout.height shr 1))
|
translate(mLrcPadding, y - (staticLayout.height shr 1))
|
||||||
|
@ -539,6 +444,7 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
|
|
||||||
override fun onDetachedFromWindow() {
|
override fun onDetachedFromWindow() {
|
||||||
removeCallbacks(hideTimelineRunnable)
|
removeCallbacks(hideTimelineRunnable)
|
||||||
|
viewScope.cancel()
|
||||||
super.onDetachedFromWindow()
|
super.onDetachedFromWindow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,12 +488,10 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 将中心行微调至正中心 */
|
|
||||||
private fun adjustCenter() {
|
private fun adjustCenter() {
|
||||||
smoothScrollTo(centerLine, ADJUST_DURATION)
|
smoothScrollTo(centerLine, ADJUST_DURATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 滚动到某一行 */
|
|
||||||
private fun smoothScrollTo(line: Int, duration: Long = mAnimationDuration) {
|
private fun smoothScrollTo(line: Int, duration: Long = mAnimationDuration) {
|
||||||
val offset = getOffset(line)
|
val offset = getOffset(line)
|
||||||
endAnimation()
|
endAnimation()
|
||||||
|
@ -602,14 +506,12 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 结束滚动动画 */
|
|
||||||
private fun endAnimation() {
|
private fun endAnimation() {
|
||||||
if (mAnimator != null && mAnimator!!.isRunning) {
|
if (mAnimator != null && mAnimator!!.isRunning) {
|
||||||
mAnimator!!.end()
|
mAnimator!!.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 二分法查找当前时间应该显示的行数(最后一个 <= time 的行数) */
|
|
||||||
private fun findShowLine(time: Long): Int {
|
private fun findShowLine(time: Long): Int {
|
||||||
var left = 0
|
var left = 0
|
||||||
var right = mLrcEntryList.size
|
var right = mLrcEntryList.size
|
||||||
|
@ -628,7 +530,6 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取当前在视图中央的行数 */
|
|
||||||
private val centerLine: Int
|
private val centerLine: Int
|
||||||
get() {
|
get() {
|
||||||
var centerLine = 0
|
var centerLine = 0
|
||||||
|
@ -642,7 +543,6 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
return centerLine
|
return centerLine
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取歌词距离视图顶部的距离 采用懒加载方式 */
|
|
||||||
private fun getOffset(line: Int): Float {
|
private fun getOffset(line: Int): Float {
|
||||||
if (mLrcEntryList.isEmpty()) return 0F
|
if (mLrcEntryList.isEmpty()) return 0F
|
||||||
if (mLrcEntryList[line].offset == Float.MIN_VALUE) {
|
if (mLrcEntryList[line].offset == Float.MIN_VALUE) {
|
||||||
|
@ -656,11 +556,9 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
return mLrcEntryList[line].offset
|
return mLrcEntryList[line].offset
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取歌词宽度 */
|
|
||||||
private val lrcWidth: Float
|
private val lrcWidth: Float
|
||||||
get() = width - mLrcPadding * 2
|
get() = width - mLrcPadding * 2
|
||||||
|
|
||||||
/** 在主线程中运行 */
|
|
||||||
private fun runOnUi(r: Runnable) {
|
private fun runOnUi(r: Runnable) {
|
||||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||||
r.run()
|
r.run()
|
||||||
|
@ -669,13 +567,7 @@ class CoverLrcView @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 播放按钮点击监听器,点击后应该跳转到指定播放位置 */
|
|
||||||
fun interface OnPlayClickListener {
|
fun interface OnPlayClickListener {
|
||||||
/**
|
|
||||||
* 播放按钮被点击,应该跳转到指定播放位置
|
|
||||||
*
|
|
||||||
* @return 是否成功消费该事件,如果成功消费,则会更新UI
|
|
||||||
*/
|
|
||||||
fun onPlayClick(time: Long): Boolean
|
fun onPlayClick(time: Long): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@ import android.view.View;
|
||||||
import android.view.animation.LinearInterpolator;
|
import android.view.animation.LinearInterpolator;
|
||||||
import android.widget.Scroller;
|
import android.widget.Scroller;
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -90,9 +92,7 @@ public class LrcView extends View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* 手势监听器
|
|
||||||
*/
|
|
||||||
private final GestureDetector.SimpleOnGestureListener mSimpleOnGestureListener =
|
private final GestureDetector.SimpleOnGestureListener mSimpleOnGestureListener =
|
||||||
new GestureDetector.SimpleOnGestureListener() {
|
new GestureDetector.SimpleOnGestureListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -111,7 +111,7 @@ public class LrcView extends View {
|
||||||
@Override
|
@Override
|
||||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||||
if (hasLrc()) {
|
if (hasLrc()) {
|
||||||
mOffset += -distanceY;
|
mOffset -= distanceY;
|
||||||
mOffset = Math.min(mOffset, getOffset(0));
|
mOffset = Math.min(mOffset, getOffset(0));
|
||||||
mOffset = Math.max(mOffset, getOffset(mLrcEntryList.size() - 1));
|
mOffset = Math.max(mOffset, getOffset(mLrcEntryList.size() - 1));
|
||||||
invalidate();
|
invalidate();
|
||||||
|
@ -219,7 +219,7 @@ public class LrcView extends View {
|
||||||
mPlayDrawable = ta.getDrawable(R.styleable.LrcView_lrcPlayDrawable);
|
mPlayDrawable = ta.getDrawable(R.styleable.LrcView_lrcPlayDrawable);
|
||||||
mPlayDrawable =
|
mPlayDrawable =
|
||||||
(mPlayDrawable == null)
|
(mPlayDrawable == null)
|
||||||
? getResources().getDrawable(R.drawable.ic_play_arrow)
|
? ContextCompat.getDrawable(getContext(), R.drawable.ic_play_arrow)
|
||||||
: mPlayDrawable;
|
: mPlayDrawable;
|
||||||
mTimeTextColor =
|
mTimeTextColor =
|
||||||
ta.getColor(
|
ta.getColor(
|
||||||
|
@ -252,52 +252,28 @@ public class LrcView extends View {
|
||||||
mScroller = new Scroller(getContext());
|
mScroller = new Scroller(getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置非当前行歌词字体颜色 */
|
|
||||||
public void setNormalColor(int normalColor) {
|
|
||||||
mNormalTextColor = normalColor;
|
|
||||||
postInvalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 普通歌词文本字体大小 */
|
|
||||||
public void setNormalTextSize(float size) {
|
|
||||||
mNormalTextSize = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 当前歌词文本字体大小 */
|
|
||||||
public void setCurrentTextSize(float size) {
|
|
||||||
mCurrentTextSize = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 设置当前行歌词的字体颜色 */
|
|
||||||
public void setCurrentColor(int currentColor) {
|
public void setCurrentColor(int currentColor) {
|
||||||
mCurrentTextColor = currentColor;
|
mCurrentTextColor = currentColor;
|
||||||
postInvalidate();
|
postInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置拖动歌词时选中歌词的字体颜色 */
|
|
||||||
public void setTimelineTextColor(int timelineTextColor) {
|
public void setTimelineTextColor(int timelineTextColor) {
|
||||||
mTimelineTextColor = timelineTextColor;
|
mTimelineTextColor = timelineTextColor;
|
||||||
postInvalidate();
|
postInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置拖动歌词时时间线的颜色 */
|
|
||||||
public void setTimelineColor(int timelineColor) {
|
public void setTimelineColor(int timelineColor) {
|
||||||
mTimelineColor = timelineColor;
|
mTimelineColor = timelineColor;
|
||||||
postInvalidate();
|
postInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置拖动歌词时右侧时间字体颜色 */
|
|
||||||
public void setTimeTextColor(int timeTextColor) {
|
public void setTimeTextColor(int timeTextColor) {
|
||||||
mTimeTextColor = timeTextColor;
|
mTimeTextColor = timeTextColor;
|
||||||
postInvalidate();
|
postInvalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置歌词是否允许拖动
|
|
||||||
*
|
|
||||||
* @param draggable 是否允许拖动
|
|
||||||
* @param onPlayClickListener 设置歌词拖动后播放按钮点击监听器,如果允许拖动,则不能为 null
|
|
||||||
*/
|
|
||||||
public void setDraggable(boolean draggable, OnPlayClickListener onPlayClickListener) {
|
public void setDraggable(boolean draggable, OnPlayClickListener onPlayClickListener) {
|
||||||
if (draggable) {
|
if (draggable) {
|
||||||
if (onPlayClickListener == null) {
|
if (onPlayClickListener == null) {
|
||||||
|
@ -310,18 +286,6 @@ public class LrcView extends View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置播放按钮点击监听器
|
|
||||||
*
|
|
||||||
* @param onPlayClickListener 如果为非 null ,则激活歌词拖动功能,否则将将禁用歌词拖动功能
|
|
||||||
* @deprecated use {@link #setDraggable(boolean, OnPlayClickListener)} instead
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void setOnPlayClickListener(OnPlayClickListener onPlayClickListener) {
|
|
||||||
mOnPlayClickListener = onPlayClickListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 设置歌词为空时屏幕中央显示的文字,如“暂无歌词” */
|
|
||||||
public void setLabel(String label) {
|
public void setLabel(String label) {
|
||||||
runOnUi(
|
runOnUi(
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -330,24 +294,12 @@ public class LrcView extends View {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载歌词文件
|
|
||||||
*
|
|
||||||
* @param lrcFile 歌词文件
|
|
||||||
*/
|
|
||||||
public void loadLrc(File lrcFile) {
|
public void loadLrc(File lrcFile) {
|
||||||
loadLrc(lrcFile, null);
|
loadLrc(lrcFile, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载双语歌词文件,两种语言的歌词时间戳需要一致
|
|
||||||
*
|
|
||||||
* @param mainLrcFile 第一种语言歌词文件
|
|
||||||
* @param secondLrcFile 第二种语言歌词文件
|
|
||||||
*/
|
|
||||||
public void loadLrc(File mainLrcFile, File secondLrcFile) {
|
public void loadLrc(File mainLrcFile, File secondLrcFile) {
|
||||||
runOnUi(
|
runOnUi(() -> {
|
||||||
() -> {
|
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder("file://");
|
StringBuilder sb = new StringBuilder("file://");
|
||||||
|
@ -374,21 +326,10 @@ public class LrcView extends View {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载歌词文本
|
|
||||||
*
|
|
||||||
* @param lrcText 歌词文本
|
|
||||||
*/
|
|
||||||
public void loadLrc(String lrcText) {
|
public void loadLrc(String lrcText) {
|
||||||
loadLrc(lrcText, null);
|
loadLrc(lrcText, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载双语歌词文本,两种语言的歌词时间戳需要一致
|
|
||||||
*
|
|
||||||
* @param mainLrcText 第一种语言歌词文本
|
|
||||||
* @param secondLrcText 第二种语言歌词文本
|
|
||||||
*/
|
|
||||||
public void loadLrc(String mainLrcText, String secondLrcText) {
|
public void loadLrc(String mainLrcText, String secondLrcText) {
|
||||||
runOnUi(
|
runOnUi(
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -418,53 +359,10 @@ public class LrcView extends View {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载在线歌词,默认使用 utf-8 编码
|
|
||||||
*
|
|
||||||
* @param lrcUrl 歌词文件的网络地址
|
|
||||||
*/
|
|
||||||
public void loadLrcByUrl(String lrcUrl) {
|
|
||||||
loadLrcByUrl(lrcUrl, "utf-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载在线歌词
|
|
||||||
*
|
|
||||||
* @param lrcUrl 歌词文件的网络地址
|
|
||||||
* @param charset 编码格式
|
|
||||||
*/
|
|
||||||
public void loadLrcByUrl(String lrcUrl, String charset) {
|
|
||||||
String flag = "url://" + lrcUrl;
|
|
||||||
setFlag(flag);
|
|
||||||
new AsyncTask<String, Integer, String>() {
|
|
||||||
@Override
|
|
||||||
protected String doInBackground(String... params) {
|
|
||||||
return LrcUtils.getContentFromNetwork(params[0], params[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(String lrcText) {
|
|
||||||
if (getFlag() == flag) {
|
|
||||||
loadLrc(lrcText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.execute(lrcUrl, charset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 歌词是否有效
|
|
||||||
*
|
|
||||||
* @return true,如果歌词有效,否则false
|
|
||||||
*/
|
|
||||||
public boolean hasLrc() {
|
public boolean hasLrc() {
|
||||||
return !mLrcEntryList.isEmpty();
|
return !mLrcEntryList.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新歌词
|
|
||||||
*
|
|
||||||
* @param time 当前播放时间
|
|
||||||
*/
|
|
||||||
public void updateTime(long time) {
|
public void updateTime(long time) {
|
||||||
runOnUi(
|
runOnUi(
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -484,12 +382,6 @@ public class LrcView extends View {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 将歌词滚动到指定时间
|
|
||||||
*
|
|
||||||
* @param time 指定的时间
|
|
||||||
* @deprecated 请使用 {@link #updateTime(long)} 代替
|
|
||||||
*/
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public void onDrag(long time) {
|
public void onDrag(long time) {
|
||||||
updateTime(time);
|
updateTime(time);
|
||||||
|
@ -513,7 +405,6 @@ public class LrcView extends View {
|
||||||
|
|
||||||
int centerY = getHeight() / 2;
|
int centerY = getHeight() / 2;
|
||||||
|
|
||||||
// 无歌词文件
|
|
||||||
if (!hasLrc()) {
|
if (!hasLrc()) {
|
||||||
mLrcPaint.setColor(mCurrentTextColor);
|
mLrcPaint.setColor(mCurrentTextColor);
|
||||||
@SuppressLint("DrawAllocation")
|
@SuppressLint("DrawAllocation")
|
||||||
|
@ -570,11 +461,6 @@ public class LrcView extends View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 画一行歌词
|
|
||||||
*
|
|
||||||
* @param y 歌词中心 Y 坐标
|
|
||||||
*/
|
|
||||||
private void drawText(Canvas canvas, StaticLayout staticLayout, float y) {
|
private void drawText(Canvas canvas, StaticLayout staticLayout, float y) {
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.translate(mLrcPadding, y - (staticLayout.getHeight() >> 1));
|
canvas.translate(mLrcPadding, y - (staticLayout.getHeight() >> 1));
|
||||||
|
@ -661,17 +547,14 @@ public class LrcView extends View {
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 将中心行微调至正中心 */
|
|
||||||
private void adjustCenter() {
|
private void adjustCenter() {
|
||||||
smoothScrollTo(getCenterLine(), ADJUST_DURATION);
|
smoothScrollTo(getCenterLine(), ADJUST_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 滚动到某一行 */
|
|
||||||
private void smoothScrollTo(int line) {
|
private void smoothScrollTo(int line) {
|
||||||
smoothScrollTo(line, mAnimationDuration);
|
smoothScrollTo(line, mAnimationDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 滚动到某一行 */
|
|
||||||
private void smoothScrollTo(int line, long duration) {
|
private void smoothScrollTo(int line, long duration) {
|
||||||
float offset = getOffset(line);
|
float offset = getOffset(line);
|
||||||
endAnimation();
|
endAnimation();
|
||||||
|
@ -687,14 +570,12 @@ public class LrcView extends View {
|
||||||
mAnimator.start();
|
mAnimator.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 结束滚动动画 */
|
|
||||||
private void endAnimation() {
|
private void endAnimation() {
|
||||||
if (mAnimator != null && mAnimator.isRunning()) {
|
if (mAnimator != null && mAnimator.isRunning()) {
|
||||||
mAnimator.end();
|
mAnimator.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 二分法查找当前时间应该显示的行数(最后一个 <= time 的行数) */
|
|
||||||
private int findShowLine(long time) {
|
private int findShowLine(long time) {
|
||||||
int left = 0;
|
int left = 0;
|
||||||
int right = mLrcEntryList.size();
|
int right = mLrcEntryList.size();
|
||||||
|
@ -716,7 +597,6 @@ public class LrcView extends View {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取当前在视图中央的行数 */
|
|
||||||
private int getCenterLine() {
|
private int getCenterLine() {
|
||||||
int centerLine = 0;
|
int centerLine = 0;
|
||||||
float minDistance = Float.MAX_VALUE;
|
float minDistance = Float.MAX_VALUE;
|
||||||
|
@ -729,7 +609,6 @@ public class LrcView extends View {
|
||||||
return centerLine;
|
return centerLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取歌词距离视图顶部的距离 采用懒加载方式 */
|
|
||||||
private float getOffset(int line) {
|
private float getOffset(int line) {
|
||||||
if (mLrcEntryList.get(line).getOffset() == Float.MIN_VALUE) {
|
if (mLrcEntryList.get(line).getOffset() == Float.MIN_VALUE) {
|
||||||
float offset = getHeight() / 2;
|
float offset = getHeight() / 2;
|
||||||
|
@ -744,12 +623,10 @@ public class LrcView extends View {
|
||||||
return mLrcEntryList.get(line).getOffset();
|
return mLrcEntryList.get(line).getOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取歌词宽度 */
|
|
||||||
private float getLrcWidth() {
|
private float getLrcWidth() {
|
||||||
return getWidth() - mLrcPadding * 2;
|
return getWidth() - mLrcPadding * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 在主线程中运行 */
|
|
||||||
private void runOnUi(Runnable r) {
|
private void runOnUi(Runnable r) {
|
||||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||||
r.run();
|
r.run();
|
||||||
|
@ -766,13 +643,7 @@ public class LrcView extends View {
|
||||||
this.mFlag = flag;
|
this.mFlag = flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 播放按钮点击监听器,点击后应该跳转到指定播放位置 */
|
|
||||||
public interface OnPlayClickListener {
|
public interface OnPlayClickListener {
|
||||||
/**
|
|
||||||
* 播放按钮被点击,应该跳转到指定播放位置
|
|
||||||
*
|
|
||||||
* @return 是否成功消费该事件,如果成功消费,则会更新UI
|
|
||||||
*/
|
|
||||||
boolean onPlayClick(long time);
|
boolean onPlayClick(long time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,7 +117,7 @@ object LyricUtil {
|
||||||
return "$lrcRootPath$title - $artist.lrc"
|
return "$lrcRootPath$title - $artist.lrc"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLrcOriginalPath(filePath: String): String {
|
private fun getLrcOriginalPath(filePath: String): String {
|
||||||
return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc")
|
return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue