自定义的可以随意拖动并自动贴边的View
自定义view的效果 可以自动绕开某一个view
接下来开始讲解代码
代码部分
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
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)) {
val changeX = mRawX - mLastRawX;
val changeY = mRawY - mLastRawY;
if (!mIsDrug) {
mIsDrug =
sqrt(changeX * changeX + changeY * changeY) >= 2
}
val ownX = x
val ownY = y
var endX = ownX + changeX
var endY = ownY + changeY
val maxX = mRootWindowMeasuredWidth - width.toFloat()
val maxY = mRootWindowMeasuredHeight - height.toFloat()
endX = if (endX < 0f) {
0f
} else {
endX.coerceAtMost(maxX)
}
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()
}
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()
}
}
} 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.使用教程
skipView.post {
moveView.setSkipViewRectF(
RectF(
skipView.x,
skipView.y,
skipView.x + skipView.measuredWidth,
skipView.y + skipView.measuredHeight
)
)
}
注意事项
请务必保证View在一个ViewGroup里面,如果没办法保证,可以运用屏幕坐标来实现。 请务必在View完成绘制并准备好了之后去获取坐标,不然有极大的可能是空的。
参考:_____ 代码:GitHub
|