前言
自己来实现一个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
中的坐标位置,而不是全局坐标。