import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.animation.BounceInterpolator
/**
* 自定义View实现拖动并自动吸边效果
*
*
* 处理滑动和贴边
* 处理事件分发
* * @author: bill
* * ls9421@vip.qq.com
* * @since: 2023年08月24日18:01:49
*
* @attr customIsAttach //是否需要自动吸边
* @attr customIsDrag //是否可拖曳
*/
class AttachButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) :
View(context, attrs, defStyleAttr) {
private var mLastRawX = 0f
private var mLastRawY = 0f
private val TAG = "AttachButton"
private var isDrug = false
private var mRootMeasuredWidth = 0
private var mRootMeasuredHeight = 0
private var mRootTopY = 0
private var customIsAttach = false
private var customIsDrag = false
init {
isClickable = true
initAttrs(context, attrs)
}
/**
* 初始化自定义属性
*/
private fun initAttrs(context: Context, attrs: AttributeSet?) {
val mTypedAttay = context.obtainStyledAttributes(attrs, R.styleable.AttachButton)
customIsAttach = mTypedAttay.getBoolean(R.styleable.AttachButton_customIsAttach, true)
customIsDrag = mTypedAttay.getBoolean(R.styleable.AttachButton_customIsDrag, true)
mTypedAttay.recycle()
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
super.dispatchTouchEvent(event)
return true
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
//判断是否需要滑动
if (customIsDrag) {
//当前手指的坐标
val mRawX = ev.rawX
val mRawY = ev.rawY
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
isDrug = false
//记录按下的位置
mLastRawX = mRawX
mLastRawY = mRawY
val mViewGroup = parent as ViewGroup
if (mViewGroup != null) {
val location = IntArray(2)
mViewGroup.getLocationInWindow(location)
//获取父布局的高度
mRootMeasuredHeight = mViewGroup.measuredHeight
mRootMeasuredWidth = mViewGroup.measuredWidth
//获取父布局顶点的坐标
mRootTopY = location[1]
}
}
MotionEvent.ACTION_MOVE -> if (mRawX >= 0 && mRawX <= mRootMeasuredWidth && mRawY >= mRootTopY && mRawY <= mRootMeasuredHeight + mRootTopY) {
//手指X轴滑动距离
val differenceValueX = mRawX - mLastRawX
//手指Y轴滑动距离
val differenceValueY = mRawY - mLastRawY
//判断是否为拖动操作
if (!isDrug) {
isDrug =
if (Math.sqrt((differenceValueX * differenceValueX + differenceValueY * differenceValueY).toDouble()) < 2) {
false
} else {
true
}
}
//获取手指按下的距离与控件本身X轴的距离
val ownX = x
//获取手指按下的距离与控件本身Y轴的距离
val ownY = y
//理论中X轴拖动的距离
var endX = ownX + differenceValueX
//理论中Y轴拖动的距离
var endY = ownY + differenceValueY
//X轴可以拖动的最大距离
val maxX = (mRootMeasuredWidth - width).toFloat()
//Y轴可以拖动的最大距离
val maxY = (mRootMeasuredHeight - height).toFloat()
//X轴边界限制
endX = if (endX < 0) {
0f
} else if (endX > maxX) {
maxX
} else {
endX
}
//Y轴边界限制
endY = if (endY < 0) {
0f
} else if (endY > maxY) {
maxY
} else {
endY
}
//开始移动
x = endX
y = endY
//记录位置
mLastRawX = mRawX
mLastRawY = mRawY
}
MotionEvent.ACTION_UP -> //根据自定义属性判断是否需要贴边
if (customIsAttach) {
//判断是否为点击事件
if (isDrug) {
val center = (mRootMeasuredWidth / 2).toFloat()
//自动贴边
if (mLastRawX <= center) {
//向左贴边
animate()
.setInterpolator(BounceInterpolator())
.setDuration(500)
.x(0f)
.start()
} else {
//向右贴边
animate()
.setInterpolator(BounceInterpolator())
.setDuration(500)
.x((mRootMeasuredWidth - width).toFloat())
.start()
}
}
}
else -> {}
}
}
//是否拦截事件
return if (isDrug) isDrug else super.onTouchEvent(ev)
}
}
手势处理的view
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.ViewGroup
import android.widget.LinearLayout
/**
* @author: bill
* ls9421@vip.qq.com
* @since: 2023年08月24日18:01:49
*/
class GestureView : LinearLayout {
private var canRotation = false
private var canScale = false
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
val typedArray: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.GestureView)
canRotation = typedArray.getBoolean(R.styleable.GestureView_canRotation, false)
canScale = typedArray.getBoolean(R.styleable.GestureView_canScale, false)
typedArray.recycle()
}
val gesture = BestGestureDetector(this).apply {
setOnTouchListener(object : OnSimpleTouchListener() {
override fun onTouchMove(detector: BestGestureDetector): Boolean {
val parentwidth = (parent as ViewGroup).width
val parentHeight = (parent as ViewGroup).height
when {
left + detector.moveX < 0 -> {
val offset = left
left -= offset
right -= offset
}
right + detector.moveX > parentwidth -> {
val offset = parentwidth - right
left += offset
right += offset
}
else -> {
offsetLeftAndRight(detector.moveX.toInt())
}
}
when {
top + detector.moveY < 0 -> {
val offset = top
top -= offset
bottom -= offset
}
bottom + detector.moveY > parentHeight -> {
val offset = parentHeight - bottom
top += offset
bottom += offset
}
else -> {
offsetTopAndBottom(detector.moveY.toInt())
}
}
return super.onTouchMove(detector)
}
})
setMoveListener(object : OnSimpleMoveListener() {
override fun onMove(detector: BestGestureDetector): Boolean {
val parentwidth = (parent as ViewGroup).width
val parentHeight = (parent as ViewGroup).height
when {
left + detector.moveX < 0 -> {
val offset = left
left -= offset
right -= offset
}
right + detector.moveX > parentwidth -> {
val offset = parentwidth - right
left += offset
right += offset
}
else -> {
offsetLeftAndRight(detector.moveX.toInt())
}
}
when {
top + detector.moveY < 0 -> {
val offset = top
top -= offset
bottom -= offset
}
bottom + detector.moveY > parentHeight -> {
val offset = parentHeight - bottom
top += offset
bottom += offset
}
else -> {
offsetTopAndBottom(detector.moveY.toInt())
}
}
return super.onMove(detector)
}
})
setRotationListener(object : OnSimpleRotateListener() {
override fun onRotate(detector: BestGestureDetector): Boolean {
if (canRotation) {
this@GestureView.rotation += detector.rotation
}
return super.onRotate(detector)
}
})
setScaleListener(object : OnSimpleScaleListener() {
override fun onScale(detector: BestGestureDetector): Boolean {
if (canScale) {
scaleX *= detector.scaleFactor
scaleY *= detector.scaleFactor
}
return super.onScale(detector)
}
})
}
override fun onTouchEvent(event: MotionEvent): Boolean {
return gesture.onTouchEvent(event)
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<declare-styleable name="GestureView">
<attr name="canRotation" format="boolean"/>
<attr name="canScale" format="boolean"/>/>
</declare-styleable>
<declare-styleable name="AttachButton">
<!--是否需要自动吸边-->
<attr name="customIsAttach" format="boolean" />
<!--是否可拖曳-->
<attr name="customIsDrag" format="boolean" />
</declare-styleable>
</resources>