ViewDragHelper-Android拖拽輔助類白皮書

概述

我們先看下源代碼中針對該工具類的注釋:
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崇渗、輔助動畫:支持拖拽時的動畫效果字逗,例如拖拽到一定位置釋放時自動回到指定位置京郑。
5、多手勢支持:支持多點(diǎn)觸控葫掉,可以同時拖拽多個對象些举。

使用步驟

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)需要開始拖動某個視圖時調(diào)用此方法。

cancel()

取消當(dāng)前正在進(jìn)行的拖動操作芽唇。
當(dāng)需要取消拖動操作時調(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ò)大水平方向的可移動范圍時虫给,我們只需要修改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;
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末药蜻,一起剝皮案震驚了整個濱河市瓷式,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌语泽,老刑警劉巖贸典,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異踱卵,居然都是意外死亡廊驼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門惋砂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妒挎,“玉大人,你說我怎么就攤上這事西饵≡脱冢” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵眷柔,是天一觀的道長期虾。 經(jīng)常有香客問我,道長驯嘱,這世上最難降的妖魔是什么镶苞? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮鞠评,結(jié)果婚禮上茂蚓,老公的妹妹穿的比我還像新娘。我一直安慰自己谢澈,他們只是感情好煌贴,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锥忿,像睡著了一般牛郑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上敬鬓,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天淹朋,我揣著相機(jī)與錄音,去河邊找鬼钉答。 笑死础芍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的数尿。 我是一名探鬼主播仑性,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼右蹦!你這毒婦竟也來了诊杆?” 一聲冷哼從身側(cè)響起歼捐,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晨汹,沒想到半個月后豹储,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淘这,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年剥扣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铝穷。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡钠怯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出氧骤,到底是詐尸還是另有隱情呻疹,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布筹陵,位于F島的核電站刽锤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏朦佩。R本人自食惡果不足惜并思,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望语稠。 院中可真熱鬧宋彼,春花似錦、人聲如沸仙畦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慨畸。三九已至莱坎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寸士,已是汗流浹背檐什。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弱卡,地道東北人乃正。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像婶博,于是被迫代替她去往敵國和親瓮具。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容