自定義ViewGroup實(shí)戰(zhàn):結(jié)合ViewDragHelper實(shí)現(xiàn)列表拖拽

前言

  • ViewDragHelper 是一個(gè)用于編寫自定義 ViewGroup 的工具類伺帘,它提供了許多有用的操作和狀態(tài)跟蹤,允許用戶在其父 ViewGroup 中拖動(dòng)和重新定位視圖损离。
  • 本文是結(jié)合 ViewDragHelper 和自定義 ViewGroup 相關(guān)知識(shí)來實(shí)現(xiàn)固定頭部與列表的拖拽功能厉熟。

1.概述

1.1 功能描述

在自定義的 ViewGroup 中放兩個(gè)布局叉谜,本例中頭部是一個(gè)固定的 TextView 错敢,也可以是其他任意布局,而拖動(dòng)部分為了簡單起見厂画,是一個(gè) ListView凸丸,也可以是 Recyclerview。當(dāng)然袱院,無論是固定還是拖動(dòng)部分屎慢,都是可以修改成其他的布局,此處主要講述的是 ViewGroup 中如何結(jié)合 ViewDragHelper 實(shí)現(xiàn)一些效果忽洛。下圖是實(shí)現(xiàn)的效果圖腻惠。


效果圖.gif

1.2 涉及主要知識(shí)點(diǎn)

  • 自定義 ViewGroup
  • ViewDragHelper 的使用
  • 事件分發(fā)機(jī)制

1.3 實(shí)現(xiàn)步驟分析

  • 新建 VerticalDragListView extends FrameLayout,做好初始化工作
  • 構(gòu)建 ViewDragHelper 實(shí)例
  • 指定可拖拽的 View
  • 計(jì)算相關(guān)拖拽點(diǎn)
  • 計(jì)算相關(guān)滑動(dòng)距離
  • 滑動(dòng)攔截

2.具體實(shí)現(xiàn)

此處我直接上代碼欲虚,關(guān)聯(lián)性邏輯較多集灌,相關(guān)內(nèi)容看注釋。

public class VerticalDragListView extends FrameLayout {

    // 可以認(rèn)為這是系統(tǒng)給我們寫好的一個(gè)工具類
    private ViewDragHelper mDragHelper;

    private View mDragListView;
    // 后面菜單的高度
    private int mMenuHeight;
    // 菜單是否打開
    private boolean mMenuIsOpen = false;

    public VerticalDragListView(Context context) {
        this(context, null);
    }

    public VerticalDragListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VerticalDragListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDragHelper = ViewDragHelper.create(this, mDragHelperCallback);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (changed) {
            View menuView = getChildAt(0);
            mMenuHeight = menuView.getMeasuredHeight();
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        int childCount = getChildCount();
        if (childCount != 2) {
            throw new RuntimeException("VerticalDragListView 只能包含兩個(gè)子布局");
        }

        mDragListView = getChildAt(1);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }

    // 1.拖動(dòng)我們的子View
    private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            // 指定該子View是否可以拖動(dòng),就是 child
            // 只能是列表可以拖動(dòng)
            // 2.1 固定頭部不能拖動(dòng)
            return mDragListView == child;
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            // 垂直拖動(dòng)移動(dòng)的位置
            // 2.3 垂直拖動(dòng)的范圍只能是頭部 View 的高度
            if (top <= 0) {
                top = 0;
            }

            if (top >= mMenuHeight) {
                top = mMenuHeight;
            }
            return top;
        }


        // 2.2 列表只能垂直拖動(dòng)
        /*@Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            // 水平拖動(dòng)移動(dòng)的位置
            return left;
        }*/

        // 2.4 手指松開的時(shí)候兩者選其一欣喧,要么打開要么關(guān)閉
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
           
            if (releasedChild == mDragListView) {
                if (mDragListView.getTop() > mMenuHeight / 2) {
                    // 滾動(dòng)到頭部的高度(打開)
                    mDragHelper.settleCapturedViewAt(0, mMenuHeight);
                    mMenuIsOpen = true;
                } else {
                    // 滾動(dòng)到0的位置(關(guān)閉)
                    mDragHelper.settleCapturedViewAt(0, 0);
                    mMenuIsOpen = false;
                }
                invalidate();
            }
        }
    };

    private float mDownY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 頭部打開要攔截
        if (mMenuIsOpen) {
            return true;
        }

        // 向下滑動(dòng)攔截腌零,不要給ListView做處理
        // 誰攔截誰 父View攔截子View ,但是子 View 可以調(diào)這個(gè)方法
        // requestDisallowInterceptTouchEvent 請(qǐng)求父View不要攔截唆阿,改變的其實(shí)就是 mGroupFlags 的值
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDownY = ev.getY();
                // 讓 DragHelper 拿一個(gè)完整的事件
                mDragHelper.processTouchEvent(ev);
                break;
            case MotionEvent.ACTION_MOVE:
                float moveY = ev.getY();
                if ((moveY - mDownY) > 0 && !canChildScrollUp()) {
                    // 向下滑動(dòng) && 滾動(dòng)到了頂部益涧,攔截不讓ListView做處理
                    return true;
                }
                break;
        }

        return super.onInterceptTouchEvent(ev);
    }

    /**
     * @return Whether it is possible for the child view of this layout to
     * scroll up. Override this if the child view is a custom view.
     * 此處參考系統(tǒng) SwipeRefreshLayout 中的方法,判斷View是否滾動(dòng)到了最頂部,還能不能向上滾
     */
    public boolean canChildScrollUp() {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            if (mDragListView instanceof AbsListView) {
                final AbsListView absListView = (AbsListView) mDragListView;
                return absListView.getChildCount() > 0
                        && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
                        .getTop() < absListView.getPaddingTop());
            } else {
                return ViewCompat.canScrollVertically(mDragListView, -1) || mDragListView.getScrollY() > 0;
            }
        } else {
            return ViewCompat.canScrollVertically(mDragListView, -1);
        }
    }

    /**
     * 響應(yīng)滾動(dòng)
     */
    @Override
    public void computeScroll() {
        if (mDragHelper.continueSettling(true)) {
            invalidate();
        }
    }
}

在Activity中只作簡單的測(cè)試實(shí)現(xiàn)

public class MainActivity extends AppCompatActivity {
    private ListView mListView;
    private List<String> mItems;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mListView = (ListView) findViewById(R.id.list_view);

        mItems = new ArrayList<String>();

        for (int i = 0; i < 100; i++) {
            mItems.add("i -> " + i);
        }

        mListView.setAdapter(new BaseAdapter() {
            @Override
            public int getCount() {
                return mItems.size();
            }

            @Override
            public Object getItem(int position) {
                return null;
            }

            @Override
            public long getItemId(int position) {
                return 0;
            }

            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                TextView item = (TextView) LayoutInflater.from(MainActivity.this)
                        .inflate(R.layout.item_lv, parent, false);
                item.setText(mItems.get(position));
                return item;
            }
        });
    }
}

3.總結(jié)

通過上面自定義 ViewGroup 的實(shí)現(xiàn)驯鳖,可以了解到 ViewDragHelper 的神奇用法闲询,而 ViewDragHelper 的用法不僅僅于此,對(duì)于 View 的拖拽浅辙,可以實(shí)現(xiàn)更多神奇的效果扭弧,在網(wǎng)上這方面的 demo 比較多,讀者可自行搜索记舆,后續(xù)有必要再出一些炫酷效果的實(shí)現(xiàn)寄狼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市氨淌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伊磺,老刑警劉巖盛正,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異屑埋,居然都是意外死亡豪筝,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門摘能,熙熙樓的掌柜王于貴愁眉苦臉地迎上來续崖,“玉大人,你說我怎么就攤上這事团搞⊙贤” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵逻恐,是天一觀的道長像吻。 經(jīng)常有香客問我,道長复隆,這世上最難降的妖魔是什么拨匆? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮挽拂,結(jié)果婚禮上惭每,老公的妹妹穿的比我還像新娘。我一直安慰自己亏栈,他們只是感情好台腥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布宏赘。 她就那樣靜靜地躺著,像睡著了一般览爵。 火紅的嫁衣襯著肌膚如雪置鼻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天蜓竹,我揣著相機(jī)與錄音箕母,去河邊找鬼。 笑死俱济,一個(gè)胖子當(dāng)著我的面吹牛嘶是,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛛碌,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼聂喇,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蔚携?” 一聲冷哼從身側(cè)響起希太,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎酝蜒,沒想到半個(gè)月后誊辉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亡脑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年堕澄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霉咨。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蛙紫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出途戒,到底是詐尸還是另有隱情坑傅,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布喷斋,位于F島的核電站裁蚁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏继准。R本人自食惡果不足惜枉证,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望移必。 院中可真熱鬧室谚,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至入篮,卻和暖如春陈瘦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背潮售。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國打工痊项, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酥诽。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓鞍泉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肮帐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子在扰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

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