在之前的文章中,分别介绍了使用layout(left, top, right, bottom)
、layoutParams
、offsetLeftAndRight/offsetTopAndBottom
、scrollBy/scrollTo
、Scroller
方式实现移动效果,本篇文章,将讲解一个更高阶的实现移动效果的方式---ViewDragHelper
。
1、ViewDragHelper
使用方法介绍
ViewDragHelper
使用十分简单,基本上就是固定的使用套路。因为ViewDragHelper
是一个拖动、移动的辅助类,一般情况下,我们会使用在自定义view or viewgroup
之中。
- 使用
ViewDragHelper.create(this, object : ViewDragHelper.Callback() {}
方法创建一个ViewDragHelper
对象
val itemDragHelper = ViewDragHelper.create(this, object : ViewDragHelper.Callback() {
...//do somethings
}
- 在
View or ViewGroup
中的事件拦截使用viewDragHelper
的方法进行*事件接管*
。
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
return mItemViewDragHelper?.shouldInterceptTouchEvent(ev) == true
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
this.performClick()
}
mItemViewDragHelper?.processTouchEvent(event)
return true
}
- 重写
computeScroll
方法,在其中处理刷新
override fun computeScroll() {
if (mItemViewDragHelper?.continueSettling(true) == true) {
ViewCompat.postInvalidateOnAnimation(this)
}
}
- 实现
ViewDragHelper.Callback
的方法,进行逻辑处理操作。
下面介绍下ViewDragHelper.Callback
常用的方法
//用来判断需要拦截那个view对象用来拖动
override fun tryCaptureView(child: View, pointerId: Int): Boolean
//用来控制拖动的边界,默认情况下,left和top就是系统默认计算的边界值
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int
//手指释放时候的反馈
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float)
//触摸边界时候回调
override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int)
2、实现一个简单的拖动移动效果
上面三个分别是普通拖动(无回弹效果)、推动松手后回弹、在布局边缘拖动效果。
<org.fireking.basic.animator.basic.widget.SimpleViewDragHelperUseView2
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tvTouchV1"
android:layout_width="180dp"
android:layout_height="60dp"
android:background="#80ff00ff"
android:gravity="center"
android:text="Touch"
android:textColor="@android:color/white"
android:textSize="12dp" />
<TextView
android:id="@+id/tvTouchV2"
android:layout_width="180dp"
android:layout_height="60dp"
android:background="#80FF8877"
android:gravity="center"
android:text="Touch"
android:textColor="@android:color/white"
android:textSize="12dp" />
<TextView
android:id="@+id/tvTouchV3"
android:layout_width="180dp"
android:layout_height="60dp"
android:background="#80334455"
android:gravity="center"
android:text="我是在布局边界滑动触发"
android:textColor="@android:color/white"
android:textSize="12dp" />
</org.fireking.basic.animator.basic.widget.SimpleViewDragHelperUseView2>
class SimpleViewDragHelperUseView2 @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayoutCompat(context, attrs, defStyleAttr) {
private var mTouch1: TextView? = null
private var mTouch2: TextView? = null
private var mTouch3: TextView? = null
private var mItemViewDragHelper: ViewDragHelper? = null
private var mTouch1OriginLeft: Int = 0
private var mTouch1OriginTop: Int = 0
private var mTouch2OriginLeft: Int = 0
private var mTouch2OriginTop: Int = 0
private var mTouch3OriginLeft: Int = 0
private var mTouch3OriginTop: Int = 0
init {
isFocusableInTouchMode = true
isMotionEventSplittingEnabled = true
mItemViewDragHelper =
ViewDragHelper.create(this, object : ViewDragHelper.Callback() {
override fun getViewHorizontalDragRange(child: View): Int {
return mItemViewDragHelper?.touchSlop ?: 0
}
override fun getViewVerticalDragRange(child: View): Int {
return mItemViewDragHelper?.touchSlop ?: 0
}
/**
* 用来判断要支持拖动的view
*/
override fun tryCaptureView(child: View, pointerId: Int): Boolean {
return child == mTouch1 || child == mTouch2
}
/**
* 表示支持在水平方向上移动
*/
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
return left
}
/**
* 表示支持在垂直方向上移动
*/
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
return top
}
/**
* 手指释放时候的反馈
*/
override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) {
releasedChild.let {
if (it == mTouch2) {
mItemViewDragHelper?.smoothSlideViewTo(
it,
mTouch2OriginLeft,
mTouch2OriginTop
)
} else if (it == mTouch3) {
mItemViewDragHelper?.smoothSlideViewTo(
it,
mTouch3OriginLeft,
mTouch3OriginTop
)
}
ViewCompat.postInvalidateOnAnimation(this@SimpleViewDragHelperUseView2)
}
}
/**
* 触摸边界时候回调
*/
override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int) {
//边界拖动触发后,设置拖动事件捕获touch3
mTouch3?.let {
mItemViewDragHelper?.captureChildView(it, pointerId)
}
}
})
//设置边界拖动可用
mItemViewDragHelper?.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)
}
override fun onFinishInflate() {
super.onFinishInflate()
mTouch1 = findViewById(R.id.tvTouchV1)
mTouch2 = findViewById(R.id.tvTouchV2)
mTouch3 = findViewById(R.id.tvTouchV3)
}
override fun computeScroll() {
if (mItemViewDragHelper?.continueSettling(true) == true) {
ViewCompat.postInvalidateOnAnimation(this)
}
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
mTouch1OriginLeft = mTouch1?.left ?: 0
mTouch1OriginTop = mTouch1?.top ?: 0
mTouch2OriginLeft = mTouch2?.left ?: 0
mTouch2OriginTop = mTouch2?.top ?: 0
mTouch3OriginLeft = mTouch3?.left ?: 0
mTouch3OriginTop = mTouch3?.top ?: 0
}
private var startX = 0F
private var startY = 0F
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
var result: Boolean = mItemViewDragHelper?.shouldInterceptTouchEvent(ev) == true
val curX = ev.x
val curY = ev.y
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
startX = curX
startY = curY
}
MotionEvent.ACTION_MOVE -> {
if (abs(curX - startX) > mItemViewDragHelper?.touchSlop ?: 0 || abs(curY - startY) > mItemViewDragHelper?.touchSlop ?: 0) {
result = true
}
}
}
return result
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
this.performClick()
}
mItemViewDragHelper?.processTouchEvent(event)
return true
}
}