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
        }
    }
}
 
效果图
 
