ItemTouchHelper学習

7589 ワード

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では、itemを上下左右にドラッグしても自動的に交換されず、交換をコールバックする関数が実現されます.次の分析関数呼び出しプロセス

リスナーの登録


helper.attachToRecyclerView(recyclerView); 内部コール多くのリスナーと初期化を設定
 private void setupCallbacks() {
        ViewConfiguration vc = ViewConfiguration.get(this.mRecyclerView.getContext());
        this.mSlop = vc.getScaledTouchSlop();
        this.mRecyclerView.addItemDecoration(this);
// , , 
        this.mRecyclerView.addOnItemTouchListener(this.mOnItemTouchListener);
// Item , RecyclerView 
        this.mRecyclerView.addOnChildAttachStateChangeListener(this);
        this.startGestureDetection();
// , 
    }

イベント転送プロセス


Down:
  • RecyclerView.onInterceptTouchEvent
  • RecyclerView.listener.onInterceptTouchEvent
  • RecyclerView.OnItemTouchListener.onInterceptTouchEvent(demo:this.mRecyclerView.addOnItemTouchListener(this.mOnItemTouchListener);)
  • ItemTouchHelper.this.mGestureDetector.onTouchEvent(event); 実はItemTouchHelperがRecyclerViewイベントリスニングを登録してmGestureDetector処理に渡し、mGestureDetectorが長押イベント通知を検出すると、ItemTouchHelperも長押を知って
  • を呼び出す.
        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;
                            }
    

    ジェスチャーに基づいて、どのViewとView Holderに属するかを見つけ、flagドラッグサポートを設定した場合は続行します.ItemTouchHelper.this.select(vh, 2); 主にthisを初期化する.mSelected = selected; 後味アニメーションオブジェクトを設定しますRecoverAnimationこれでItemTouchHelperは現在選択されているView Holderがあることを知っています
    Move:
  • RecyclerView.onTouchEvent(MotionEvent e)
  • RecyclerView.dispatchOnItemTouch(e)
  • RecyclerView.OnItemTouchListener.onTouchEvent()
  • ViewHolder viewHolder = ItemTouchHelper.this.mSelected;
  • switch(action){case 2:recyclerview.invalidate();このいくつかのステップはMoveを分析する時、実はRecyclerViewから事件を伝えて最終的に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 ItemDecoration 
    

    Up: case MotionEvent.ACTION_UP: select(null, ACTION_STATE_IDLE); タグがnullとして選択すると、thisもトリガーされます.mRecyclerView.invalidate();

    分析


    ItemTouchHelper extends ItemDecoration onDrawメソッドはItemTouchUIUtilImplをトリガーします.JAvaのonChildDrawとonChildDrawOver onChildDrawOverは空の方法onChildDrawはコアがviewです.setTranslationX(dX); view.setTranslationY(dY); 実はx,yのオフセットであり,いわば手の移動に応じてx,yのオフセットを設定する.

    オフセット計算規則

    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();
            }
        }
    

    パラメータ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;
    

    パラメータmSelectedStartXは初期位置が選択されています.これを参照してください.mSelectedStartX = (float)selected.itemView.getLeft(); selectメソッドでは、mInitialTouchYはonLongPress設定ジェスチャードロップポイントで、2つのパラメータが異なることに注意して設定されています

    通知交換ViewHolderコールバック


    ItemTouchHelperがスライドをリスニングしている間に、現在選択されている空のドラッグモードではないことがわかりました.
     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();
    

    その中にはthis.moveIfNecessary(viewHolder);コールバック交換シーケンスをトリガーします

    RecyclerViewの一番上に浮かぶ実現原理を分析する


    Build.VERSION.SDK_INT<21ドラッグビュー位置を書き換える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);
                    }
                }
            }
    

    分析のまとめ


    デフォルトの長押しの場合、ドラッグモードに入り、手が動くにつれてx,yのオフセット量を計算し、ジェスチャートリガ描画はviewにsetTranslateXメソッドを直接呼び出すのではなくrecyclerviewである.invalidate、ItemDecorationペイント呼び出しonDrawメソッドをトリガーし、ドラッグビュー設定setTranlateXおよびsetTranlateYを設定します.