XML
文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/black"
android:gravity="center">
<com.gallery20.app.MyLineSeekBar
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginStart="22dp"
android:layout_marginEnd="22dp"
android:layout_marginBottom="60dp" />
</LinearLayout>
自定义View
代码
class MyLineSeekBar(context: Context, attrs: AttributeSet) : View(context, attrs) {
private var mLineWidthArray = IntArray(5).apply {
this[0] = 40
this[1] = 60
this[2] = 80
this[3] = 100
this[4] = 120
}
private var mProgress: Float = 0f
private var mCurIndex: Int = 0
private var mAnimator: Animator? = null
private val mGreyCirclePaint = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.GRAY
}
private val mGreyLinePaint = Paint().apply {
isAntiAlias = true
style = Paint.Style.STROKE
strokeWidth = dpToPx(context, 2f)
color = Color.GRAY
}
private val mWhiteCirclePaint = Paint().apply {
isAntiAlias = true
style = Paint.Style.FILL
color = Color.WHITE
}
override fun onTouchEvent(event: MotionEvent): Boolean {
val lineWidthArray = mLineWidthArray
val curX = event.x
val minLineWidth = lineWidthArray[0] / 2f
val maxLineWidth = lineWidthArray[lineWidthArray.size - 1] / 2f
val left = minLineWidth
val right = measuredWidth - minLineWidth - maxLineWidth
mProgress = max(0f, min(1f, (curX - left) / (right - left)))
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mAnimator?.cancel()
}
MotionEvent.ACTION_MOVE -> {
notifyLineWidthChanged()
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
animateToClosestPoint()
}
}
invalidate()
return true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val lineWidthArray = mLineWidthArray
val minLineWidth = lineWidthArray[0] / 2f
val maxLineWidth = lineWidthArray[lineWidthArray.size - 1] / 2f
val left = minLineWidth
val right = measuredWidth - minLineWidth - maxLineWidth
canvas.drawLine(left, measuredHeight / 2f, right, measuredHeight / 2f, mGreyLinePaint)
val perPointDistance = (right - left) / (lineWidthArray.size - 1)
for (i in lineWidthArray.indices) {
val lineWidth = lineWidthArray[i]
val step = i * perPointDistance
canvas.drawCircle(
left + step, measuredHeight / 2f, lineWidth / 2f, mGreyCirclePaint
)
}
canvas.drawCircle(
left + mProgress * (right - left),
measuredHeight / 2f,
getCurrentCircleRadius() / 2f,
mWhiteCirclePaint
)
}
fun animateToClosestPoint() {
mAnimator?.cancel()
val animator = getAnimateToClosestPoint(mProgress)
animator.addUpdateListener {
mProgress = it.animatedValue as Float
invalidate()
}
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
notifyLineWidthChanged()
mAnimator = null
}
})
mAnimator = animator
animator.start()
}
fun notifyLineWidthChanged() {
val newIndex = getClosestPointIndex(mProgress)
if (newIndex != mCurIndex) {
mCurIndex = newIndex
}
}
fun getAnimateToClosestPoint(progress: Float): ValueAnimator {
val closestProgress = getClosestPointIndex(progress) * 1f / (mLineWidthArray.size - 1)
val animator = ValueAnimator()
animator.setFloatValues(progress, closestProgress)
animator.interpolator = DecelerateInterpolator()
animator.duration = 200
return animator
}
fun getClosestPointIndex(progress: Float): Int {
val perPointProgress = 1f / (mLineWidthArray.size - 1)
val minIndex = Math.floor((progress / perPointProgress).toDouble()).toInt()
return if (minIndex < mLineWidthArray.size - 1) {
val minIndexDistance = Math.abs(progress - minIndex * perPointProgress)
val maxIndexDistance = Math.abs(progress - (minIndex + 1) * perPointProgress)
if (minIndexDistance < maxIndexDistance) minIndex else minIndex + 1
} else {
minIndex
}
}
private fun getCurrentCircleRadius(): Float {
val lineWidthArray = mLineWidthArray
val perPointProgress = 1f / (lineWidthArray.size - 1)
val floorIndex = Math.floor((mProgress / perPointProgress).toDouble()).toInt()
return if (floorIndex == 0 || floorIndex == lineWidthArray.size - 1) {
lineWidthArray[floorIndex].toFloat()
} else {
lineWidthArray[floorIndex] + (lineWidthArray[floorIndex + 1] - lineWidthArray[floorIndex]) * (mProgress - floorIndex * perPointProgress) / perPointProgress
}
}
}
效果图