714 lines
24 KiB
Kotlin
714 lines
24 KiB
Kotlin
/*
|
||
* Copyright (c) 2019 Hemanth Savarala.
|
||
*
|
||
* Licensed under the GNU General Public License v3
|
||
*
|
||
* This is free software: you can redistribute it and/or modify it under
|
||
* the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
|
||
*
|
||
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||
* See the GNU General Public License for more details.
|
||
*/
|
||
package code.name.monkey.retromusic.lyrics
|
||
|
||
|
||
import android.animation.ValueAnimator
|
||
import android.content.Context
|
||
import android.graphics.Canvas
|
||
import android.graphics.Color
|
||
import android.graphics.Paint
|
||
import android.graphics.Rect
|
||
import android.graphics.drawable.Drawable
|
||
import android.os.Looper
|
||
import android.text.Layout
|
||
import android.text.StaticLayout
|
||
import android.text.TextPaint
|
||
import android.util.AttributeSet
|
||
import android.util.TypedValue
|
||
import android.view.MotionEvent
|
||
import android.view.VelocityTracker
|
||
import android.view.View
|
||
import android.view.ViewConfiguration
|
||
import android.view.animation.DecelerateInterpolator
|
||
import android.widget.OverScroller
|
||
import androidx.annotation.ColorInt
|
||
import androidx.core.content.ContextCompat
|
||
import androidx.core.content.res.ResourcesCompat
|
||
import androidx.core.view.ViewCompat
|
||
import code.name.monkey.retromusic.BuildConfig
|
||
import code.name.monkey.retromusic.R
|
||
import java.util.*
|
||
|
||
/**
|
||
* Desc : 歌词
|
||
* Author : Lauzy
|
||
* Date : 2017/10/13
|
||
* Blog : http://www.jianshu.com/u/e76853f863a9
|
||
* Email : freedompaladin@gmail.com
|
||
*/
|
||
class LrcView @JvmOverloads constructor(
|
||
context: Context,
|
||
attrs: AttributeSet? = null,
|
||
defStyleAttr: Int = 0
|
||
) : View(context, attrs, defStyleAttr) {
|
||
private var mLrcData: MutableList<Lrc>? = null
|
||
private var mTextPaint: TextPaint? = null
|
||
private var mDefaultContent: String? = null
|
||
private var mCurrentLine: Int = 0
|
||
private var mOffset: Float = 0.toFloat()
|
||
private var mLastMotionX: Float = 0.toFloat()
|
||
private var mLastMotionY: Float = 0.toFloat()
|
||
private var mScaledTouchSlop: Int = 0
|
||
private var mOverScroller: OverScroller? = null
|
||
private var mVelocityTracker: VelocityTracker? = null
|
||
private var mMaximumFlingVelocity: Int = 0
|
||
private var mMinimumFlingVelocity: Int = 0
|
||
private var mLrcTextSize: Float = 0.toFloat()
|
||
private var mLrcLineSpaceHeight: Float = 0.toFloat()
|
||
private var mTouchDelay: Int = 0
|
||
private var mNormalColor: Int = 0
|
||
private var mCurrentPlayLineColor: Int = 0
|
||
private var mNoLrcTextSize: Float = 0.toFloat()
|
||
private var mNoLrcTextColor: Int = 0
|
||
|
||
//是否拖拽中,否的话响应onClick事件
|
||
private var isDragging: Boolean = false
|
||
|
||
//用户开始操作
|
||
private var isUserScroll: Boolean = false
|
||
private var isAutoAdjustPosition = true
|
||
private var mPlayDrawable: Drawable? = null
|
||
private var isShowTimeIndicator: Boolean = false
|
||
private var mPlayRect: Rect? = null
|
||
private var mIndicatorPaint: Paint? = null
|
||
private var mIndicatorLineWidth: Float = 0.toFloat()
|
||
private var mIndicatorTextSize: Float = 0.toFloat()
|
||
private var mCurrentIndicateLineTextColor: Int = 0
|
||
private var mIndicatorLineColor: Int = 0
|
||
private var mIndicatorMargin: Float = 0.toFloat()
|
||
private var mIconLineGap: Float = 0.toFloat()
|
||
private var mIconWidth: Float = 0.toFloat()
|
||
private var mIconHeight: Float = 0.toFloat()
|
||
private var isEnableShowIndicator = true
|
||
private var mIndicatorTextColor: Int = 0
|
||
private var mIndicatorTouchDelay: Int = 0
|
||
private val mLrcMap = HashMap<String, StaticLayout>()
|
||
private val mHideIndicatorRunnable = Runnable {
|
||
isShowTimeIndicator = false
|
||
invalidateView()
|
||
}
|
||
private val mStaticLayoutHashMap = HashMap<String, StaticLayout>()
|
||
private val mScrollRunnable = Runnable {
|
||
isUserScroll = false
|
||
scrollToPosition(mCurrentLine)
|
||
}
|
||
private var mOnPlayIndicatorLineListener: OnPlayIndicatorLineListener? = null
|
||
|
||
private val lrcWidth: Int
|
||
get() = width - paddingLeft - paddingRight
|
||
|
||
private val lrcHeight: Int
|
||
get() = height
|
||
|
||
private val isLrcEmpty: Boolean
|
||
get() = mLrcData == null || lrcCount == 0
|
||
|
||
private val lrcCount: Int
|
||
get() = mLrcData!!.size
|
||
|
||
//itemOffset 和 mOffset 最小即当前位置
|
||
val indicatePosition: Int
|
||
get() {
|
||
var pos = 0
|
||
var min = java.lang.Float.MAX_VALUE
|
||
for (i in mLrcData!!.indices) {
|
||
val offsetY = getItemOffsetY(i)
|
||
val abs = Math.abs(offsetY - mOffset)
|
||
if (abs < min) {
|
||
min = abs
|
||
pos = i
|
||
}
|
||
}
|
||
return pos
|
||
}
|
||
|
||
var playDrawable: Drawable?
|
||
get() = mPlayDrawable
|
||
set(playDrawable) {
|
||
mPlayDrawable = playDrawable
|
||
mPlayDrawable!!.bounds = mPlayRect!!
|
||
invalidateView()
|
||
}
|
||
|
||
init {
|
||
init(context, attrs)
|
||
}
|
||
|
||
private fun init(context: Context, attrs: AttributeSet?) {
|
||
|
||
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.LrcView)
|
||
mLrcTextSize =
|
||
typedArray.getDimension(R.styleable.LrcView_lrcTextSize, sp2px(context, 15f).toFloat())
|
||
mLrcLineSpaceHeight = typedArray.getDimension(
|
||
R.styleable.LrcView_lrcLineSpaceSize,
|
||
dp2px(context, 20f).toFloat()
|
||
)
|
||
mTouchDelay = typedArray.getInt(R.styleable.LrcView_lrcTouchDelay, 3500)
|
||
mIndicatorTouchDelay = typedArray.getInt(R.styleable.LrcView_indicatorTouchDelay, 2500)
|
||
mNormalColor = typedArray.getColor(R.styleable.LrcView_lrcNormalTextColor, Color.GRAY)
|
||
mCurrentPlayLineColor =
|
||
typedArray.getColor(R.styleable.LrcView_lrcCurrentTextColor, Color.BLUE)
|
||
mNoLrcTextSize = typedArray.getDimension(
|
||
R.styleable.LrcView_noLrcTextSize,
|
||
dp2px(context, 20f).toFloat()
|
||
)
|
||
mNoLrcTextColor = typedArray.getColor(R.styleable.LrcView_noLrcTextColor, Color.BLACK)
|
||
mIndicatorLineWidth = typedArray.getDimension(
|
||
R.styleable.LrcView_indicatorLineHeight,
|
||
dp2px(context, 0.5f).toFloat()
|
||
)
|
||
mIndicatorTextSize = typedArray.getDimension(
|
||
R.styleable.LrcView_indicatorTextSize,
|
||
sp2px(context, 13f).toFloat()
|
||
)
|
||
mIndicatorTextColor =
|
||
typedArray.getColor(R.styleable.LrcView_indicatorTextColor, Color.GRAY)
|
||
mCurrentIndicateLineTextColor =
|
||
typedArray.getColor(R.styleable.LrcView_currentIndicateLrcColor, Color.GRAY)
|
||
mIndicatorLineColor =
|
||
typedArray.getColor(R.styleable.LrcView_indicatorLineColor, Color.GRAY)
|
||
mIndicatorMargin = typedArray.getDimension(
|
||
R.styleable.LrcView_indicatorStartEndMargin,
|
||
dp2px(context, 5f).toFloat()
|
||
)
|
||
mIconLineGap =
|
||
typedArray.getDimension(R.styleable.LrcView_iconLineGap, dp2px(context, 3f).toFloat())
|
||
mIconWidth = typedArray.getDimension(
|
||
R.styleable.LrcView_playIconWidth,
|
||
dp2px(context, 20f).toFloat()
|
||
)
|
||
mIconHeight = typedArray.getDimension(
|
||
R.styleable.LrcView_playIconHeight,
|
||
dp2px(context, 20f).toFloat()
|
||
)
|
||
mPlayDrawable = typedArray.getDrawable(R.styleable.LrcView_playIcon)
|
||
mPlayDrawable = if (mPlayDrawable == null) ContextCompat.getDrawable(
|
||
context,
|
||
R.drawable.ic_play_arrow_white_24dp
|
||
) else mPlayDrawable
|
||
typedArray.recycle()
|
||
|
||
setupConfigs(context)
|
||
}
|
||
|
||
private fun setupConfigs(context: Context) {
|
||
mScaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||
mMaximumFlingVelocity = ViewConfiguration.get(context).scaledMaximumFlingVelocity
|
||
mMinimumFlingVelocity = ViewConfiguration.get(context).scaledMinimumFlingVelocity
|
||
mOverScroller = OverScroller(context, DecelerateInterpolator())
|
||
mOverScroller!!.setFriction(0.1f)
|
||
// ViewConfiguration.getScrollFriction(); 默认摩擦力 0.015f
|
||
|
||
mTextPaint = TextPaint()
|
||
mTextPaint!!.apply {
|
||
isAntiAlias = true
|
||
textAlign = Paint.Align.LEFT
|
||
textSize = mLrcTextSize
|
||
/* if (BuildConfig.FLAVOR != "nofont") {
|
||
typeface = ResourcesCompat.getFont(context, R.font.circular)
|
||
}*/
|
||
}
|
||
mDefaultContent = DEFAULT_CONTENT
|
||
|
||
mIndicatorPaint = Paint()
|
||
mIndicatorPaint!!.isAntiAlias = true
|
||
mIndicatorPaint!!.strokeWidth = mIndicatorLineWidth
|
||
mIndicatorPaint!!.color = mIndicatorLineColor
|
||
mPlayRect = Rect()
|
||
mIndicatorPaint!!.textSize = mIndicatorTextSize
|
||
}
|
||
|
||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||
super.onLayout(changed, left, top, right, bottom)
|
||
if (changed) {
|
||
mPlayRect!!.left = mIndicatorMargin.toInt()
|
||
mPlayRect!!.top = (height / 2 - mIconHeight / 2).toInt()
|
||
mPlayRect!!.right = (mPlayRect!!.left + mIconWidth).toInt()
|
||
mPlayRect!!.bottom = (mPlayRect!!.top + mIconHeight).toInt()
|
||
mPlayDrawable!!.bounds = mPlayRect!!
|
||
}
|
||
}
|
||
|
||
fun setLrcData(lrcData: MutableList<Lrc>) {
|
||
resetView(DEFAULT_CONTENT)
|
||
mLrcData = lrcData
|
||
invalidate()
|
||
}
|
||
|
||
override fun onDraw(canvas: Canvas) {
|
||
super.onDraw(canvas)
|
||
if (isLrcEmpty) {
|
||
drawEmptyText(canvas)
|
||
return
|
||
}
|
||
val indicatePosition = indicatePosition
|
||
mTextPaint!!.textSize = mLrcTextSize
|
||
mTextPaint!!.textAlign = Paint.Align.LEFT
|
||
var y = (lrcHeight / 2).toFloat()
|
||
val x = dip2px(context, 16f).toFloat()
|
||
for (i in 0 until lrcCount) {
|
||
if (i > 0) {
|
||
y += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight
|
||
}
|
||
if (mCurrentLine == i) {
|
||
mTextPaint!!.color = mCurrentPlayLineColor
|
||
} else if (indicatePosition == i && isShowTimeIndicator) {
|
||
mTextPaint!!.color = mCurrentIndicateLineTextColor
|
||
} else {
|
||
mTextPaint!!.color = mNormalColor
|
||
}
|
||
drawLrc(canvas, x, y, i)
|
||
}
|
||
|
||
if (isShowTimeIndicator) {
|
||
mPlayDrawable!!.draw(canvas)
|
||
val time = mLrcData!![indicatePosition].time
|
||
val timeWidth = mIndicatorPaint!!.measureText(LrcHelper.formatTime(time))
|
||
mIndicatorPaint!!.color = mIndicatorLineColor
|
||
canvas.drawLine(
|
||
mPlayRect!!.right + mIconLineGap, (height / 2).toFloat(),
|
||
width - timeWidth * 1.3f, (height / 2).toFloat(), mIndicatorPaint!!
|
||
)
|
||
val baseX = (width - timeWidth * 1.1f).toInt()
|
||
val baseline =
|
||
(height / 2).toFloat() - (mIndicatorPaint!!.descent() - mIndicatorPaint!!.ascent()) / 2 - mIndicatorPaint!!.ascent()
|
||
mIndicatorPaint!!.color = mIndicatorTextColor
|
||
canvas.drawText(
|
||
LrcHelper.formatTime(time),
|
||
baseX.toFloat(),
|
||
baseline,
|
||
mIndicatorPaint!!
|
||
)
|
||
}
|
||
}
|
||
|
||
private fun dip2px(context: Context, dpVale: Float): Int {
|
||
val scale = context.resources.displayMetrics.density
|
||
return (dpVale * scale + 0.5f).toInt()
|
||
}
|
||
|
||
private fun drawLrc(canvas: Canvas, x: Float, y: Float, i: Int) {
|
||
val text = mLrcData!![i].text
|
||
var staticLayout: StaticLayout? = mLrcMap[text]
|
||
if (staticLayout == null) {
|
||
mTextPaint!!.textSize = mLrcTextSize
|
||
staticLayout = StaticLayout(
|
||
text,
|
||
mTextPaint,
|
||
lrcWidth,
|
||
Layout.Alignment.ALIGN_NORMAL,
|
||
1f,
|
||
0f,
|
||
false
|
||
)
|
||
mLrcMap[text] = staticLayout
|
||
}
|
||
canvas.save()
|
||
canvas.translate(x, y - (staticLayout.height / 2).toFloat() - mOffset)
|
||
staticLayout.draw(canvas)
|
||
canvas.restore()
|
||
}
|
||
|
||
//中间空文字
|
||
private fun drawEmptyText(canvas: Canvas) {
|
||
mTextPaint!!.textAlign = Paint.Align.LEFT
|
||
mTextPaint!!.color = mNoLrcTextColor
|
||
mTextPaint!!.textSize = mNoLrcTextSize
|
||
canvas.save()
|
||
val staticLayout = StaticLayout(
|
||
mDefaultContent, mTextPaint,
|
||
lrcWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false
|
||
)
|
||
val margin = dip2px(context, 16f).toFloat();
|
||
canvas.translate(margin, margin)
|
||
staticLayout.draw(canvas)
|
||
canvas.restore()
|
||
}
|
||
|
||
fun updateTime(time: Long) {
|
||
if (isLrcEmpty) {
|
||
return
|
||
}
|
||
val linePosition = getUpdateTimeLinePosition(time)
|
||
if (mCurrentLine != linePosition) {
|
||
mCurrentLine = linePosition
|
||
if (isUserScroll) {
|
||
invalidateView()
|
||
return
|
||
}
|
||
ViewCompat.postOnAnimation(this@LrcView, mScrollRunnable)
|
||
}
|
||
}
|
||
|
||
private fun getUpdateTimeLinePosition(time: Long): Int {
|
||
var linePos = 0
|
||
for (i in 0 until lrcCount) {
|
||
val lrc = mLrcData!![i]
|
||
if (time >= lrc.time) {
|
||
if (i == lrcCount - 1) {
|
||
linePos = lrcCount - 1
|
||
} else if (time < mLrcData!![i + 1].time) {
|
||
linePos = i
|
||
break
|
||
}
|
||
}
|
||
}
|
||
return linePos
|
||
}
|
||
|
||
private fun scrollToPosition(linePosition: Int) {
|
||
val scrollY = getItemOffsetY(linePosition)
|
||
val animator = ValueAnimator.ofFloat(mOffset, scrollY)
|
||
animator.addUpdateListener { animation ->
|
||
mOffset = animation.animatedValue as Float
|
||
invalidateView()
|
||
}
|
||
animator.duration = 300
|
||
animator.start()
|
||
}
|
||
|
||
private fun getItemOffsetY(linePosition: Int): Float {
|
||
var tempY = 0f
|
||
for (i in 1..linePosition) {
|
||
tempY += (getTextHeight(i - 1) + getTextHeight(i)) / 2 + mLrcLineSpaceHeight
|
||
}
|
||
return tempY
|
||
}
|
||
|
||
private fun getTextHeight(linePosition: Int): Float {
|
||
val text = mLrcData!![linePosition].text
|
||
var staticLayout: StaticLayout? = mStaticLayoutHashMap[text]
|
||
if (staticLayout == null) {
|
||
mTextPaint!!.textSize = mLrcTextSize
|
||
staticLayout = StaticLayout(
|
||
text, mTextPaint,
|
||
lrcWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false
|
||
)
|
||
mStaticLayoutHashMap[text] = staticLayout
|
||
}
|
||
return staticLayout.height.toFloat()
|
||
}
|
||
|
||
private fun overScrolled(): Boolean {
|
||
return mOffset > getItemOffsetY(lrcCount - 1) || mOffset < 0
|
||
}
|
||
|
||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||
if (isLrcEmpty) {
|
||
return super.onTouchEvent(event)
|
||
}
|
||
if (mVelocityTracker == null) {
|
||
mVelocityTracker = VelocityTracker.obtain()
|
||
}
|
||
mVelocityTracker!!.addMovement(event)
|
||
when (event.action) {
|
||
MotionEvent.ACTION_DOWN -> {
|
||
removeCallbacks(mScrollRunnable)
|
||
removeCallbacks(mHideIndicatorRunnable)
|
||
if (!mOverScroller!!.isFinished) {
|
||
mOverScroller!!.abortAnimation()
|
||
}
|
||
mLastMotionX = event.x
|
||
mLastMotionY = event.y
|
||
isUserScroll = true
|
||
isDragging = false
|
||
}
|
||
|
||
MotionEvent.ACTION_MOVE -> {
|
||
var moveY = event.y - mLastMotionY
|
||
if (Math.abs(moveY) > mScaledTouchSlop) {
|
||
isDragging = true
|
||
isShowTimeIndicator = isEnableShowIndicator
|
||
}
|
||
if (isDragging) {
|
||
|
||
// if (mOffset < 0) {
|
||
// mOffset = Math.max(mOffset, -getTextHeight(0) - mLrcLineSpaceHeight);
|
||
// }
|
||
val maxHeight = getItemOffsetY(lrcCount - 1)
|
||
// if (mOffset > maxHeight) {
|
||
// mOffset = Math.min(mOffset, maxHeight + getTextHeight(getLrcCount() - 1) + mLrcLineSpaceHeight);
|
||
// }
|
||
if (mOffset < 0 || mOffset > maxHeight) {
|
||
moveY /= 3.5f
|
||
}
|
||
mOffset -= moveY
|
||
mLastMotionY = event.y
|
||
invalidateView()
|
||
}
|
||
}
|
||
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
|
||
if (!isDragging && (!isShowTimeIndicator || !onClickPlayButton(event))) {
|
||
isShowTimeIndicator = false
|
||
invalidateView()
|
||
performClick()
|
||
}
|
||
handleActionUp(event)
|
||
}
|
||
}
|
||
// return isDragging || super.onTouchEvent(event);
|
||
return true
|
||
}
|
||
|
||
private fun handleActionUp(event: MotionEvent) {
|
||
if (isEnableShowIndicator) {
|
||
ViewCompat.postOnAnimationDelayed(
|
||
this@LrcView,
|
||
mHideIndicatorRunnable,
|
||
mIndicatorTouchDelay.toLong()
|
||
)
|
||
}
|
||
if (isShowTimeIndicator && mPlayRect != null && onClickPlayButton(event)) {
|
||
isShowTimeIndicator = false
|
||
invalidateView()
|
||
if (mOnPlayIndicatorLineListener != null) {
|
||
mOnPlayIndicatorLineListener!!.onPlay(
|
||
mLrcData!![indicatePosition].time,
|
||
mLrcData!![indicatePosition].text
|
||
)
|
||
}
|
||
}
|
||
if (overScrolled() && mOffset < 0) {
|
||
scrollToPosition(0)
|
||
if (isAutoAdjustPosition) {
|
||
ViewCompat.postOnAnimationDelayed(
|
||
this@LrcView,
|
||
mScrollRunnable,
|
||
mTouchDelay.toLong()
|
||
)
|
||
}
|
||
return
|
||
}
|
||
|
||
if (overScrolled() && mOffset > getItemOffsetY(lrcCount - 1)) {
|
||
scrollToPosition(lrcCount - 1)
|
||
if (isAutoAdjustPosition) {
|
||
ViewCompat.postOnAnimationDelayed(
|
||
this@LrcView,
|
||
mScrollRunnable,
|
||
mTouchDelay.toLong()
|
||
)
|
||
}
|
||
return
|
||
}
|
||
|
||
mVelocityTracker!!.computeCurrentVelocity(1000, mMaximumFlingVelocity.toFloat())
|
||
val YVelocity = mVelocityTracker!!.yVelocity
|
||
val absYVelocity = Math.abs(YVelocity)
|
||
if (absYVelocity > mMinimumFlingVelocity) {
|
||
mOverScroller!!.fling(
|
||
0, mOffset.toInt(), 0, (-YVelocity).toInt(), 0,
|
||
0, 0, getItemOffsetY(lrcCount - 1).toInt(),
|
||
0, getTextHeight(0).toInt()
|
||
)
|
||
invalidateView()
|
||
}
|
||
releaseVelocityTracker()
|
||
if (isAutoAdjustPosition) {
|
||
ViewCompat.postOnAnimationDelayed(this@LrcView, mScrollRunnable, mTouchDelay.toLong())
|
||
}
|
||
}
|
||
|
||
private fun onClickPlayButton(event: MotionEvent): Boolean {
|
||
val left = mPlayRect!!.left.toFloat()
|
||
val right = mPlayRect!!.right.toFloat()
|
||
val top = mPlayRect!!.top.toFloat()
|
||
val bottom = mPlayRect!!.bottom.toFloat()
|
||
val x = event.x
|
||
val y = event.y
|
||
return (mLastMotionX > left && mLastMotionX < right && mLastMotionY > top
|
||
&& mLastMotionY < bottom && x > left && x < right && y > top && y < bottom)
|
||
}
|
||
|
||
override fun computeScroll() {
|
||
super.computeScroll()
|
||
if (mOverScroller!!.computeScrollOffset()) {
|
||
mOffset = mOverScroller!!.currY.toFloat()
|
||
invalidateView()
|
||
}
|
||
}
|
||
|
||
private fun releaseVelocityTracker() {
|
||
if (null != mVelocityTracker) {
|
||
mVelocityTracker!!.clear()
|
||
mVelocityTracker!!.recycle()
|
||
mVelocityTracker = null
|
||
}
|
||
}
|
||
|
||
fun resetView(defaultContent: String) {
|
||
if (mLrcData != null) {
|
||
mLrcData!!.clear()
|
||
}
|
||
mLrcMap.clear()
|
||
mStaticLayoutHashMap.clear()
|
||
mCurrentLine = 0
|
||
mOffset = 0f
|
||
isUserScroll = false
|
||
isDragging = false
|
||
mDefaultContent = defaultContent
|
||
removeCallbacks(mScrollRunnable)
|
||
invalidate()
|
||
}
|
||
|
||
override fun performClick(): Boolean {
|
||
return super.performClick()
|
||
}
|
||
|
||
fun dp2px(context: Context, dpVal: Float): Int {
|
||
return TypedValue.applyDimension(
|
||
TypedValue.COMPLEX_UNIT_DIP,
|
||
dpVal, context.resources.displayMetrics
|
||
).toInt()
|
||
}
|
||
|
||
fun sp2px(context: Context, spVal: Float): Int {
|
||
return TypedValue.applyDimension(
|
||
TypedValue.COMPLEX_UNIT_SP,
|
||
spVal, context.resources.displayMetrics
|
||
).toInt()
|
||
}
|
||
|
||
/**
|
||
* 暂停(手动滑动歌词后,不再自动回滚至当前播放位置)
|
||
*/
|
||
fun pause() {
|
||
isAutoAdjustPosition = false
|
||
invalidateView()
|
||
}
|
||
|
||
/**
|
||
* 恢复(继续自动回滚)
|
||
*/
|
||
fun resume() {
|
||
isAutoAdjustPosition = true
|
||
ViewCompat.postOnAnimationDelayed(this@LrcView, mScrollRunnable, mTouchDelay.toLong())
|
||
invalidateView()
|
||
}
|
||
|
||
|
||
/*------------------Config-------------------*/
|
||
|
||
private fun invalidateView() {
|
||
if (Looper.getMainLooper().thread === Thread.currentThread()) {
|
||
invalidate()
|
||
} else {
|
||
postInvalidate()
|
||
}
|
||
}
|
||
|
||
fun setOnPlayIndicatorLineListener(onPlayIndicatorLineListener: OnPlayIndicatorLineListener) {
|
||
mOnPlayIndicatorLineListener = onPlayIndicatorLineListener
|
||
}
|
||
|
||
fun setEmptyContent(defaultContent: String) {
|
||
mDefaultContent = defaultContent
|
||
invalidateView()
|
||
}
|
||
|
||
fun setLrcTextSize(lrcTextSize: Float) {
|
||
mLrcTextSize = lrcTextSize
|
||
invalidateView()
|
||
}
|
||
|
||
fun setLrcLineSpaceHeight(lrcLineSpaceHeight: Float) {
|
||
mLrcLineSpaceHeight = lrcLineSpaceHeight
|
||
invalidateView()
|
||
}
|
||
|
||
fun setTouchDelay(touchDelay: Int) {
|
||
mTouchDelay = touchDelay
|
||
invalidateView()
|
||
}
|
||
|
||
fun setNormalColor(@ColorInt normalColor: Int) {
|
||
mNormalColor = normalColor
|
||
invalidateView()
|
||
}
|
||
|
||
fun setCurrentPlayLineColor(@ColorInt currentPlayLineColor: Int) {
|
||
mCurrentPlayLineColor = currentPlayLineColor
|
||
invalidateView()
|
||
}
|
||
|
||
fun setNoLrcTextSize(noLrcTextSize: Float) {
|
||
mNoLrcTextSize = noLrcTextSize
|
||
invalidateView()
|
||
}
|
||
|
||
fun setNoLrcTextColor(@ColorInt noLrcTextColor: Int) {
|
||
mNoLrcTextColor = noLrcTextColor
|
||
invalidateView()
|
||
}
|
||
|
||
fun setIndicatorLineWidth(indicatorLineWidth: Float) {
|
||
mIndicatorLineWidth = indicatorLineWidth
|
||
invalidateView()
|
||
}
|
||
|
||
fun setIndicatorTextSize(indicatorTextSize: Float) {
|
||
// mIndicatorTextSize = indicatorTextSize;
|
||
mIndicatorPaint!!.textSize = indicatorTextSize
|
||
invalidateView()
|
||
}
|
||
|
||
fun setCurrentIndicateLineTextColor(currentIndicateLineTextColor: Int) {
|
||
mCurrentIndicateLineTextColor = currentIndicateLineTextColor
|
||
invalidateView()
|
||
}
|
||
|
||
fun setIndicatorLineColor(indicatorLineColor: Int) {
|
||
mIndicatorLineColor = indicatorLineColor
|
||
invalidateView()
|
||
}
|
||
|
||
fun setIndicatorMargin(indicatorMargin: Float) {
|
||
mIndicatorMargin = indicatorMargin
|
||
invalidateView()
|
||
}
|
||
|
||
fun setIconLineGap(iconLineGap: Float) {
|
||
mIconLineGap = iconLineGap
|
||
invalidateView()
|
||
}
|
||
|
||
fun setIconWidth(iconWidth: Float) {
|
||
mIconWidth = iconWidth
|
||
invalidateView()
|
||
}
|
||
|
||
fun setIconHeight(iconHeight: Float) {
|
||
mIconHeight = iconHeight
|
||
invalidateView()
|
||
}
|
||
|
||
fun setEnableShowIndicator(enableShowIndicator: Boolean) {
|
||
isEnableShowIndicator = enableShowIndicator
|
||
invalidateView()
|
||
}
|
||
|
||
fun setIndicatorTextColor(indicatorTextColor: Int) {
|
||
mIndicatorTextColor = indicatorTextColor
|
||
invalidateView()
|
||
}
|
||
|
||
interface OnPlayIndicatorLineListener {
|
||
fun onPlay(time: Long, content: String)
|
||
}
|
||
|
||
companion object {
|
||
private const val DEFAULT_CONTENT = "Empty"
|
||
}
|
||
} |