ItemTouchHelper学习

测试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:

  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事件监听,然后就传递给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和ViewHolder,如果设置flag拖拽支持,则继续;
ItemTouchHelper.this.select(vh, 2);
主要是初始化this.mSelected = selected;
并且设置回味动画对象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传递出来事件然后最终导致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();

分析ItemDecoration

public class 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 是选中初始位置,见
this.mSelectedStartX = (float)selected.itemView.getLeft();
select方法里面设置的,注意mInitialTouchY是onLongPress设置手势落下点
,俩个参数不一样

通知交换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();

其中ItemTouchHelper.this.moveIfNecessary(viewHolder);就是触发回调交换顺序

分析浮在RecyclerView最上面实现原理

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

分析总结

默认长按的情况,进入拖拽模式,随着手移动,计算x,y偏移量,然后
手势触发绘制不是直接让view调用setTranlateX方法,而是recyclerview.invalidate,然后触发ItemDecoration绘制调用onDraw方法,
然后设置拖拽的view设置setTranlateX以及setTranlateY

你可能感兴趣的:(ItemTouchHelper学习)