0
点赞
收藏
分享

微信扫一扫

实现SeekBar的自定义View

桑二小姐 2022-02-10 阅读 45

前言

自己来实现一个SeekBar,其实不难。主要就是点击事件的处理,还有就是跟随手指滑动来放置View。

Code


/**
 * @author: guoxuan
 * @version: 1.0
 * @date:2022/02/07
 * class desc:滑动选择控件 - 只负责滑动,回调进度
 */
class SlidingControlView : RelativeLayout {

    private lateinit var mSlider: View
    private lateinit var mShadow: View
    private lateinit var mOrigin: View
    private lateinit var mBackground: View

    private var isOneDirection: Boolean = false
    private var mProcess: Float = 0f

    private var mCallback: Callback? = null

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        initView()
    }

    private fun initView() {
        val mView = LayoutInflater.from(context).inflate(R.layout.view_sliding_control, this, true)
        gravity = CENTER_VERTICAL

        //滑块
        mSlider = mView.findViewById(R.id.sliding_control_slider)
        //选中状态的阴影
        mShadow = mView.findViewById(R.id.sliding_control_shadow)
        //坐标原点
        mOrigin = mView.findViewById(R.id.sliding_control_origin)
        mBackground = mView.findViewById(R.id.sliding_control_bg)
    }

    fun setProcess(isOne: Boolean, process: Float) {
        isOneDirection = isOne
        //错误输入排查
        mProcess = if (isOne) {
            if (process < 0) 0f else if (process > 100) 100f else process
        } else {
            if (process < -100) -100f else if (process > 100) 100f else process
        }

        //防止加载后再次设置进度
        if (this.isAttachedToWindow) {
            requestLayout()
            invalidate()
        }
    }

    fun setCallback(callback: Callback) {
        mCallback = callback
    }


    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        //1.放置背景
        val mBackgroundLeft = mSlider.measuredWidth / 2
        val mBackgroundTop = (b - t) / 2 - mBackground.measuredHeight / 2
        val mBackgroundBottom = mBackgroundTop + mBackground.measuredHeight
        mBackground.layout(
            mBackgroundLeft,
            mBackgroundTop,
            mBackgroundLeft + mBackground.measuredWidth,
            mBackgroundBottom
        )

        if (isOneDirection) {
            //单向模式: 从最左侧到最右侧为0~100
            val mCenterY = (b - t) / 2

            //2.计算阴影位置
            val mShadowRight = mBackgroundLeft + mBackground.measuredWidth * mProcess / 100
            mShadow.layout(mBackgroundLeft, mBackgroundTop, mShadowRight.toInt(), mBackgroundBottom)

            //3.放原点 - 最左边
            mOrigin.layout(
                mBackgroundLeft - mOrigin.measuredWidth / 2,
                mCenterY - mOrigin.measuredHeight / 2,
                mBackgroundLeft + mOrigin.measuredWidth / 2,
                mCenterY + mOrigin.measuredHeight / 2
            )

            //4.放滑块
            mSlider.layout(
                (mShadowRight - mSlider.measuredWidth / 2).toInt(),
                mCenterY - mSlider.measuredHeight / 2,
                (mShadowRight + mSlider.measuredWidth / 2).toInt(),
                mCenterY + mSlider.measuredHeight / 2,
            )
        } else {
            //双向模式: 从中间开始
            val mCenterX = (r - l) / 2
            val mCenterY = (b - t) / 2
            //2.先放原点
            mOrigin.layout(
                mCenterX - mOrigin.measuredWidth / 2,
                mCenterY - mOrigin.measuredHeight / 2,
                mCenterX + mOrigin.measuredWidth / 2,
                mCenterY + mOrigin.measuredHeight / 2
            )

            if (mProcess > 0) {
                //进度大于0,游标在右边
                val mShadowRight = mCenterX + mBackground.measuredWidth * mProcess / 200
                mShadow.layout(mCenterX, mBackgroundTop, mShadowRight.toInt(), mBackgroundBottom)

                //4.放星星
                mSlider.layout(
                    (mShadowRight - mSlider.measuredWidth / 2).toInt(),
                    mCenterY - mSlider.measuredHeight / 2,
                    (mShadowRight + mSlider.measuredWidth / 2).toInt(),
                    mCenterY + mSlider.measuredHeight / 2,
                )
            } else {
                //进度小于0,游标在左边
                val mShadowLeft = mCenterX + mBackground.measuredWidth * mProcess / 200
                mShadow.layout(mShadowLeft.toInt(), mBackgroundTop, mCenterX, mBackgroundBottom)

                //4.放滑块
                mSlider.layout(
                    (mShadowLeft - mSlider.measuredWidth / 2).toInt(),
                    mCenterY - mSlider.measuredHeight / 2,
                    (mShadowLeft + mSlider.measuredWidth / 2).toInt(),
                    mCenterY + mSlider.measuredHeight / 2,
                )
            }
        }
    }

    /**
     * 点击事件处理 - 设置滑动更新
     */
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                val x = event.x
                val y = event.y

                //只有Down事件在滑块上,才能拖动。
                val result = x >= mSlider.left && x <= mSlider.right && y >= mSlider.top && y <= mSlider.bottom
                return result
            }
            MotionEvent.ACTION_MOVE -> {
                val x = event.x

                //获取偏差
                if (isOneDirection) {
                    //单项
                    mProcess = when {
                        x <= mBackground.left -> {
                            0f
                        }
                        x >= mBackground.right -> {
                            100f
                        }
                        else -> {
                            (x - mBackground.left) * 100 / mBackground.measuredWidth
                        }
                    }
                } else {
                    //双向
                    mProcess = if (x <= mBackground.left) {
                        -100f
                    } else if (x >= mBackground.right) {
                        100f
                    } else {
                        if (x > (mBackground.left + mBackground.right) / 2) {
                            //正的
                            (x - (mBackground.left + mBackground.right) / 2) * 100 / (mBackground.measuredWidth / 2)
                        } else {
                            -((((mBackground.left + mBackground.right) / 2)) - x) * 100 / (mBackground.measuredWidth / 2)
                        }
                    }
                }
                requestLayout()
                invalidate()

                mCallback?.onProcessChanged(mProcess)
                return true
            }
            MotionEvent.ACTION_CANCEL -> {
                return true
            }
            else -> {
                return false
            }
        }
    }

    interface Callback {
        fun onProcessChanged(process: Float)
    }
}

踩坑

  • ChildView.layout传入的应该是在ParentView中的坐标位置,而不是全局坐标。
举报

相关推荐

0 条评论