ItemTouchHelper學(xué)習(xí)

測試demo代碼

        RecyclerView recyclerView = new RecyclerView(context);
        recyclerView.setLayoutManager(new LinearLayoutManager(context));
        final TouchAdapter adapter = new TouchAdapter();
        recyclerView.setAdapter(adapter);
        ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
            @Override
            public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
                return makeMovementFlags(ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
            }

            @Override
            public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
                //   adapter.swap(viewHolder,viewHolder1);
                return true;
            }

            @Override
            public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i)    {

            }
        });
        helper.attachToRecyclerView(recyclerView);

這樣當recyclerview長按就可以拖動子item上下左右擺動了任岸,但是并不會交換順序,所以默認ItemTouchHelper實現(xiàn)的就是上下左右拖拽item但是不會自動交換,只是會回調(diào)交換的函數(shù)颓帝。下面一點點分析函數(shù)調(diào)用流程

注冊監(jiān)聽器

helper.attachToRecyclerView(recyclerView);
內(nèi)部調(diào)用設(shè)置很多監(jiān)聽器和初始化

 private void setupCallbacks() {
        ViewConfiguration vc = ViewConfiguration.get(this.mRecyclerView.getContext());
        this.mSlop = vc.getScaledTouchSlop();
        this.mRecyclerView.addItemDecoration(this);
//添加這個裝飾器购城,下文介紹,其實就是用來繪制的
        this.mRecyclerView.addOnItemTouchListener(this.mOnItemTouchListener);
//監(jiān)聽Item觸摸事件吴趴,由RecyclerView事件分發(fā)而來
        this.mRecyclerView.addOnChildAttachStateChangeListener(this);
        this.startGestureDetection();
//手勢監(jiān)聽侮攀,比如長按判斷
    }

事件傳遞流程

Down:

  1. RecyclerView.onInterceptTouchEvent
  2. RecyclerView.listener.onInterceptTouchEvent
  3. RecyclerView.OnItemTouchListener.onInterceptTouchEvent( 見demo:this.mRecyclerView.addOnItemTouchListener(this.mOnItemTouchListener);
    )
  4. ItemTouchHelper.this.mGestureDetector.onTouchEvent(event);
    其實就是ItemTouchHelper注冊了RecyclerView事件監(jiān)聽兰英,然后就傳遞給mGestureDetector處理,mGestureDetector檢測到長按事件通知出來陨闹,
    這時候ItemTouchHelper也就知道長按了趋厉,然后
    調(diào)用
    View child = ItemTouchHelper.this.findChildView(e);
                if (child != null) {
                    ViewHolder vh = ItemTouchHelper.this.mRecyclerView.getChildViewHolder(child);
                    if (vh != null) {
                        if (!ItemTouchHelper.this.mCallback.hasDragFlag(ItemTouchHelper.this.mRecyclerView, vh)) {
                            return;
                        }

根據(jù)手勢找到屬于哪一個View和ViewHolder胶坠,如果設(shè)置flag拖拽支持沈善,則繼續(xù)帖蔓;
ItemTouchHelper.this.select(vh, 2);
主要是初始化this.mSelected = selected;
并且設(shè)置回味動畫對象ItemTouchHelper.RecoverAnimation
這樣ItemTouchHelper就知道當前有一個選中的ViewHolder

Move:

  1. RecyclerView.onTouchEvent(MotionEvent e)
  2. RecyclerView.dispatchOnItemTouch(e)
  3. RecyclerView.OnItemTouchListener.onTouchEvent()
  4. ViewHolder viewHolder = ItemTouchHelper.this.mSelected;
  5. switch(action) {
    case 2:recyclerview.invalidate();
    這幾步驟分析Move的時候,其實也是從RecyclerView傳遞出來事件然后最終導(dǎo)致RecyclerView繪制
    RecyclerView繪制看的會有點暈瞳脓,其實再看
 public void onDraw(Canvas c) {
        super.onDraw(c);
        int count = this.mItemDecorations.size();

        for(int i = 0; i < count; ++i) {
            ((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).onDraw(c, this, this.mState);
        }

    }
RecyclerView繪制會觸發(fā)ItemDecoration重新繪制的

Up:
case MotionEvent.ACTION_UP:
select(null, ACTION_STATE_IDLE);
標記當前選中為null
同時也會觸發(fā)this.mRecyclerView.invalidate();

分析ItemDecoration

public class ItemTouchHelper extends ItemDecoration
看onDraw方法會觸發(fā)ItemTouchUIUtilImpl.java的onChildDraw和onChildDrawOver
onChildDrawOver是空方法
onChildDraw是核心是view.setTranslationX(dX);
view.setTranslationY(dY);
其實就是x,y偏移塑娇,說來說去就是按照手移動設(shè)置x,y的偏移

偏移量計算規(guī)則

private void getSelectedDxDy(float[] outPosition) {
        if ((mSelectedFlags & (LEFT | RIGHT)) != 0) {
            outPosition[0] = mSelectedStartX + mDx - mSelected.itemView.getLeft();
        } else {
            outPosition[0] = mSelected.itemView.getTranslationX();
        }
        if ((mSelectedFlags & (UP | DOWN)) != 0) {
            outPosition[1] = mSelectedStartY + mDy - mSelected.itemView.getTop();
        } else {
            outPosition[1] = mSelected.itemView.getTranslationY();
        }
    }

參數(shù)mDx是當前手和之前落下的距離差,見

void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {
        float x = ev.getX(pointerIndex);
        float y = ev.getY(pointerIndex);
        this.mDx = x - this.mInitialTouchX;
        this.mDy = y - this.mInitialTouchY;

參數(shù)mSelectedStartX 是選中初始位置劫侧,見
this.mSelectedStartX = (float)selected.itemView.getLeft();
select方法里面設(shè)置的埋酬,注意mInitialTouchY是onLongPress設(shè)置手勢落下點
,倆個參數(shù)不一樣

通知交換ViewHolder回調(diào)

在ItemTouchHelper監(jiān)聽滑動時候發(fā)現(xiàn)當前選中不為空切拖拽模式

 ItemTouchHelper.this.updateDxDy(event, ItemTouchHelper.this.mSelectedFlags, activePointerIndex);
                            ItemTouchHelper.this.moveIfNecessary(viewHolder);
                            ItemTouchHelper.this.mRecyclerView.removeCallbacks(ItemTouchHelper.this.mScrollRunnable);
                            ItemTouchHelper.this.mScrollRunnable.run();
                            ItemTouchHelper.this.mRecyclerView.invalidate();

其中ItemTouchHelper.this.moveIfNecessary(viewHolder);就是觸發(fā)回調(diào)交換順序

分析浮在RecyclerView最上面實現(xiàn)原理

Build.VERSION.SDK_INT < 21
改寫拖拽的view位置getChildDrawingOrder

mChildDrawingOrderCallback = new RecyclerView.ChildDrawingOrderCallback() {
                @Override
                public int onGetChildDrawingOrder(int childCount, int i) {
                    if (mOverdrawChild == null) {
                        return i;
                    }
                    int childPosition = mOverdrawChildPosition;
                    if (childPosition == -1) {
                        childPosition = mRecyclerView.indexOfChild(mOverdrawChild);
                        mOverdrawChildPosition = childPosition;
                    }
                    if (i == childCount - 1) {
                        return childPosition;
                    }
                    return i < childPosition ? i : i + 1;
                }

Build.VERSION.SDK_INT >= 21見ItemTouchUIUtilImpl

 if (Build.VERSION.SDK_INT >= 21) {
            if (isCurrentlyActive) {
                Object originalElevation = view.getTag(R.id.item_touch_helper_previous_elevation);
                if (originalElevation == null) {
                    originalElevation = ViewCompat.getElevation(view);
                    float newElevation = 1f + findMaxElevation(recyclerView, view);
                    ViewCompat.setElevation(view, newElevation);
                    view.setTag(R.id.item_touch_helper_previous_elevation, originalElevation);
                }
            }
        }

分析總結(jié)

默認長按的情況烧栋,進入拖拽模式写妥,隨著手移動,計算x,y偏移量审姓,然后
手勢觸發(fā)繪制不是直接讓view調(diào)用setTranlateX方法,而是recyclerview.invalidate,然后觸發(fā)ItemDecoration繪制調(diào)用onDraw方法魔吐,
然后設(shè)置拖拽的view設(shè)置setTranlateX以及setTranlateY

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扎筒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子酬姆,更是在濱河造成了極大的恐慌嗜桌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辞色,死亡現(xiàn)場離奇詭異骨宠,居然都是意外死亡,警方通過查閱死者的電腦和手機相满,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門层亿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人立美,你說我怎么就攤上這事棕所。” “怎么了悯辙?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵琳省,是天一觀的道長。 經(jīng)常有香客問我躲撰,道長针贬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任拢蛋,我火速辦了婚禮桦他,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谆棱。我一直安慰自己快压,他們只是感情好圆仔,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蔫劣,像睡著了一般坪郭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脉幢,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天歪沃,我揣著相機與錄音,去河邊找鬼嫌松。 笑死沪曙,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的萎羔。 我是一名探鬼主播液走,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼贾陷!你這毒婦竟也來了缘眶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤昵宇,失蹤者是張志新(化名)和其女友劉穎磅崭,沒想到半個月后儿子,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓦哎,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年柔逼,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒋譬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡愉适,死狀恐怖犯助,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情维咸,我是刑警寧澤剂买,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站癌蓖,受9級特大地震影響瞬哼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜租副,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一坐慰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧用僧,春花似錦结胀、人聲如沸赞咙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽攀操。三九已至,卻和暖如春着逐,著一層夾襖步出監(jiān)牢的瞬間崔赌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工耸别, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留健芭,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓秀姐,卻偏偏與公主長得像慈迈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子省有,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

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