概述
我們先看下源代碼中針對該工具類的注釋:
ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number of useful operations and state tracking for allowing a user to drag and reposition views within their parent ViewGroup.
所以:ViewDragHelper 是 Android 提供的一個輔助類,用于實(shí)現(xiàn)拖拽、滑動等手勢操作。它主要用于處理用戶觸摸操作,例如實(shí)現(xiàn)拖拽布局、滑動刪除等交互效果。
ViewDragHelper 能夠大大簡化開發(fā)者處理拖拽手勢的工作,使得實(shí)現(xiàn)復(fù)雜的交互效果變得更加簡單和高效。不過,需要注意的是,ViewDragHelper 在一些復(fù)雜場景下可能會有一定的學(xué)習(xí)曲線,并且需要注意處理好各種邊界情況,以確保用戶體驗(yàn)的流暢性。
主要功能:
ViewDragHelper 提供了以下主要功能:
1、拖拽控制:可以輕松地將 ViewDragHelper 與 ViewGroup 結(jié)合使用,實(shí)現(xiàn)拖拽指定的 View,這些 View 可以是任何可繪制的對象,例如圖片、文本等。
2、邊界檢測:可以設(shè)置邊界,限制用戶拖拽的范圍,防止對象移出屏幕范圍或者不可見。
3、速度計(jì)算:ViewDragHelper 能夠計(jì)算拖拽過程中的速度,以便根據(jù)用戶的操作進(jìn)行調(diào)整。
4、輔助動畫:支持拖拽時(shí)的動畫效果,例如拖拽到一定位置釋放時(shí)自動回到指定位置。
5、多手勢支持:支持多點(diǎn)觸控,可以同時(shí)拖拽多個對象。
使用步驟
1、初始化:在自定義的 ViewGroup 中創(chuàng)建 ViewDragHelper 實(shí)例,并重寫相應(yīng)的回調(diào)方法。
2、處理觸摸事件:在 ViewGroup 的 onTouchEvent() 方法中將觸摸事件傳遞給 ViewDragHelper 處理。
3、實(shí)現(xiàn)回調(diào)方法:重寫 ViewDragHelper.Callback 中的相關(guān)方法,用于處理拖拽、釋放等事件。
4、設(shè)置拖拽對象:在 ViewGroup 中設(shè)置需要拖拽的 View 對象。
舉個栗子??
比如我們要實(shí)現(xiàn)一個簡單布局的拖拽,最常用的拖拽方式是直接在onTouch事件中處理拖拽實(shí)現(xiàn),然后通過layout函數(shù)來更新View在父布局中的相對位置
以前我們會怎么做
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
public class DraggableRelativeLayout extends RelativeLayout implements View.OnTouchListener {
private int lastX;
private int lastY;
public DraggableRelativeLayout(Context context) {
super(context);
init();
}
public DraggableRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DraggableRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (action) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - lastX;
int deltaY = y - lastY;
// 獲取布局的當(dāng)前位置
int left = v.getLeft();
int top = v.getTop();
int right = v.getRight();
int bottom = v.getBottom();
// 根據(jù)手指移動的距離更新布局的位置
v.layout(left + deltaX, top + deltaY, right + deltaX, bottom + deltaY);
lastX = x;
lastY = y;
break;
}
return true;
}
}
如果使用ViewDragHelper會怎么寫
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import androidx.customview.widget.ViewDragHelper;
public class DraggableRelativeLayout extends RelativeLayout {
private ViewDragHelper viewDragHelper;
public DraggableRelativeLayout(Context context) {
super(context);
init();
}
public DraggableRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DraggableRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
viewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}
}
所以ViewDragHelper幫我們做了什么呢?
ViewDragHelper 封裝了常見的拖動操作,可以簡化代碼,并提供了更好的性能和穩(wěn)定性。
比如在以上的代碼對比中。
方案一中:我們需要處理Down、Move、Up事件記錄坐標(biāo)進(jìn)行計(jì)算,并更新布局
方案二中:ViewDragHelper 工具類,幫助我們封裝了拖動手勢的處理邏輯,只需簡單地設(shè)置拖動的邊界和拖動對象,ViewDragHelper 就會幫助你完成剩余的工作。
還有哪些我們常用的成員函數(shù)?
create(ViewGroup forParent, float sensitivity, Callback cb)
創(chuàng)建一個 ViewDragHelper 實(shí)例。
參數(shù) forParent 是指定要與 ViewDragHelper 關(guān)聯(lián)的父布局。
參數(shù) sensitivity 是靈敏度,表示拖動的靈敏程度,一般取 1.0f。
參數(shù) cb 是 ViewDragHelper.Callback 對象,用于定義拖動行為。
shouldInterceptTouchEvent(MotionEvent ev)
判斷是否攔截觸摸事件。
在父布局的 onInterceptTouchEvent() 方法中調(diào)用,用于決定是否攔截觸摸事件交給 ViewDragHelper 處理。
processTouchEvent(MotionEvent event):
處理觸摸事件。
在父布局的 onTouchEvent() 方法中調(diào)用,用于將觸摸事件傳遞給 ViewDragHelper 處理。
captureChildView(View child, int activePointerId)
捕獲指定的子視圖。
當(dāng)需要開始拖動某個視圖時(shí)調(diào)用此方法。
cancel()
取消當(dāng)前正在進(jìn)行的拖動操作。
當(dāng)需要取消拖動操作時(shí)調(diào)用此方法。
flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop)
在當(dāng)前位置釋放被捕獲的視圖并執(zhí)行慣性動畫。
參數(shù) minLeft、minTop、maxLeft、maxTop 為釋放后視圖的位置范圍。
settleCapturedViewAt(int finalLeft, int finalTop)
將被捕獲的視圖移動到指定位置。
參數(shù) finalLeft、finalTop 為目標(biāo)位置。
smoothSlideViewTo(View child, int finalLeft, int finalTop)
平滑地將指定的子視圖移動到指定位置。
參數(shù) child 是要移動的子視圖,finalLeft、finalTop 為目標(biāo)位置。
getCapturedView()
獲取當(dāng)前被捕獲的子視圖。
getViewDragState()
獲取當(dāng)前的拖動狀態(tài),返回值為 STATE_IDLE、STATE_DRAGGING 或 STATE_SETTLING。
所以我們的處理重點(diǎn)是什么呢?
ViewDragHelper的Create函數(shù)中,ViewDragHelper.Callback對象是一個回調(diào)接口,用于處理拖拽手勢的各個階段,我們有很多針對手勢的“干預(yù)”、“探測”工作都是在這里做的。
比如我們上面的例子,只是在clampViewPositionHorizontal回調(diào)函數(shù)中限制了View在水平方面的移動范圍,就可以不讓View移出我們限制的移動區(qū)域,所以當(dāng)我們需要縮小或者擴(kuò)大水平方向的可移動范圍時(shí),我們只需要修改return返回的值即可。
再比如,如果我們想要讓View只在Y軸移動(僅豎直移動)。
我們可以通過實(shí)現(xiàn)
ViewDragHelper.Callback接口,并且復(fù)寫clampViewPositionHorizontal、clampViewPositionVertical函數(shù)即可
完成代碼如下:
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import androidx.customview.widget.ViewDragHelper;
public class LimitedDraggableRelativeLayout extends RelativeLayout {
private ViewDragHelper viewDragHelper;
public LimitedDraggableRelativeLayout(Context context) {
super(context);
init();
}
public LimitedDraggableRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LimitedDraggableRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
viewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
// 限制視圖只能在水平方向上移動到指定的范圍內(nèi)
return left;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
// 限制視圖只能在垂直方向上移動到指定的范圍內(nèi)
return Math.max(0, Math.min(getHeight() - child.getHeight(), top));
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}
}