美文网首页
ItemTouchHelper学习

ItemTouchHelper学习

作者: enjoycc97 | 来源:发表于2019-08-16 01:44 被阅读0次

    测试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学习

          本文链接:https://www.haomeiwen.com/subject/tyzbjctx.html