- 自定义的可以随意拖动并自动贴边的View
- 自定义view的效果 可以自动绕开某一个view
- 代码部分
- 1.自定义View
- 2.使用教程
- 注意事项
接下来开始讲解代码 代码部分 1.自定义View
-
要自定义一个View ,当然可以继承任何View,也可以把我的代码封装一个基类
这里我使用的kotlin,如果想看java的话 可以根据逻辑自己写 -
变量的定义 请直接查看注释
private var mIsDrug = true //判断是否是点击事件
private var mCustomIsAttach = true //是否需要自动吸附
private var mCustomIsDrag = true // 是否可以拖拽
private var mLastRawX = 0f //最终位置
private var mLastRawY = 0f //最终位置
private var mRootWindowMeasuredWidth = 0 //父布局的宽度
private var mRootWindowMeasuredHeight = 0 //父布局的高度
private var mRootTopY = 0 //父布局的顶部
//用来规避的View 位置
private val rectF: RectF = RectF(0f, 0f, 0f, 0f) //这里直接创建对象 后面进行内部赋值操作
- 最关键的一点 我们要阻止事件的分发
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
super.dispatchTouchEvent(event)
return true
}
- 设置我们所需要的规避View的坐标(矩形坐标),也就是View的上下左右四个边,也可以通过XY算
fun setSkipViewRectF(viewRectF: RectF) {
this.rectF.bottom = viewRectF.bottom
this.rectF.top = viewRectF.top
this.rectF.left = viewRectF.left
this.rectF.right = viewRectF.right
}
- 接下来就是整个代码的灵魂部分了 也是喜闻乐见的重写我们的onTouchEvent()
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (mCustomIsDrag) {
val mRawX = event?.rawX
val mRawY = event?.rawY
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
mIsDrug = false
mLastRawX = mRawX!!
mLastRawY = mRawY!!
val viewGroup = parent as ViewGroup?
if (viewGroup != null) {
val location = IntArray(2)
viewGroup.getLocationInWindow(location)
mRootWindowMeasuredHeight = viewGroup.measuredHeight
mRootWindowMeasuredWidth = viewGroup.measuredWidth
mRootTopY = location[1]
}
}
MotionEvent.ACTION_MOVE -> {
if (mRawX!! >= 0 && mRawX <= mRootWindowMeasuredWidth && mRawY!! >= mRootTopY && mRawY <= (mRootWindowMeasuredHeight + mRootTopY)) {
//手指X轴的滑动距离
val changeX = mRawX - mLastRawX;
//手指Y轴的滑动距离
val changeY = mRawY - mLastRawY;
//判断是否为拖动操作
if (!mIsDrug) {
mIsDrug =
sqrt(changeX * changeX + changeY * changeY) >= 2
}
//获取手指按下的距离与控件本身X轴的距离
val ownX = x
//获取手指按下的距离与控件本身Y轴的距离
val ownY = y
//理论中X轴拖动的距离
var endX = ownX + changeX
//理论中Y轴拖动的距离
var endY = ownY + changeY
//X轴可以拖动的最大距离
val maxX = mRootWindowMeasuredWidth - width.toFloat()
//Y轴可以拖动的最大距离
val maxY = mRootWindowMeasuredHeight - height.toFloat()
//X轴边界限制
endX = if (endX < 0f) {
0f
} else {
endX.coerceAtMost(maxX)
}
//Y轴边界限制
endY = if (endY < 0f) {
0f
} else {
endY.coerceAtMost(maxY)
}
//开始移动
x = endX
y = endY
//记录位置
mLastRawX = mRawX
mLastRawY = mRawY
}
}
MotionEvent.ACTION_UP -> {
if (mCustomIsAttach) {
//判断是否为点击事件
if (mIsDrug) {
val center = (mRootWindowMeasuredWidth shr 1).toFloat()
//自动贴边
if (mLastRawX <= center) {
mLastRawX = 0f
animate()
.setInterpolator(BounceInterpolator())
.setDuration(1500)
.x(mLastRawX)
.start()
} else {
mLastRawX = (mRootWindowMeasuredWidth - width).toFloat()
animate()
.setInterpolator(BounceInterpolator())
.setDuration(1500)
.x(mLastRawX)
.start()
}
//这里是因为我所做的跳过的view是固定右边的 而且自动吸边 所以判断的是上面的mLastRawX
if (mLastRawX != 0f) {
//获取View的Y中线
val y = y + measuredHeight / 2
if (y >= rectF.top && y < rectF.bottom) {
animate()
.setInterpolator(BounceInterpolator())
.setDuration(1500)
.y(rectF.top - height)
.start()
}
}
} else {
//这里可以处理点击事件
}
}
}
}
}
return if (mIsDrug) {
mIsDrug
} else {
super.onTouchEvent(event)
}
}
注释大部分代码都给予了说明,这里可能有人不知道怎么实现的自动贴边,这里其实是用了属性动画,可以直接设置View移动到指定的坐标,而不是移动了多少。
- 规避某一个View,自动去它的上方(这里可以在代码里自己算,我这里只写了在右侧的上方)
if (mLastRawX != 0f) {
val y = y + measuredHeight / 2
if (y >= rectF.top && y < rectF.bottom) {
animate()
.setInterpolator(BounceInterpolator())
.setDuration(1500)
.y(rectF.top - height)
.start()
}
}
2.使用教程
//设置规避的view坐标 主要 请务必在view完全绘制完成后调用 不然会拿不到坐标 常用方法是使用View.post(R r);
//这里我用了kotlin-android-extensions 这个就是id可以直接用
//这里如果看不懂 emm 就是kt的语法糖
skipView.post {
moveView.setSkipViewRectF(
//这里创建矩形对象 然后把四个边传入进去 这里我是直接算的 当然也可以直接获得
RectF(
skipView.x,
skipView.y,
skipView.x + skipView.measuredWidth,
skipView.y + skipView.measuredHeight
)
)
}
注意事项
请务必保证View在一个ViewGroup里面,如果没办法保证,可以运用屏幕坐标来实现。
请务必在View完成绘制并准备好了之后去获取坐标,不然有极大的可能是空的。
参考:_____
代码:GitHub



