ItemTouchHelper源码分析
2021/6/3 1:24:21
本文主要是介绍ItemTouchHelper源码分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
ItemTouchHelper是一个强大的帮助类。用来配合RecyclerView使用,ItemTouchHelper同一时刻只能支持两种效果:swipe、drag中的一种。分别用来实现RecyclerView里面item侧滑删除(swipe)效果或者item长按拖拽移动(drag)。当然swipe和drag效果同一时刻只能支持一种。因为事件冲突不能同时支持。
一、ItemTouchHelper效果展示
实例代码下载地址
https://github.com/tuacy/ItemTouchHelperDemo
二、ItemTouchHelper使用的简单介绍
ItemTouchHelper使用过程中最关键的点其实就是一个回调类的使用,我们上层所有的逻辑操作都在这个类的回调函数中实现。而且这个类必须继承自ItemTouchHelper.Callback类。这里我帮大家总结出来了ItemTouchHelper.Callback里面常用函数。同时相关的解释如下:
/** * 针对swipe和drag状态,设置不同状态(swipe、drag)下支持的方向 * (LEFT, RIGHT, START, END, UP, DOWN) * idle:0~7位表示swipe和drag的方向 * swipe:8~15位表示滑动方向 * drag:16~23位表示拖动方向 */ public abstract int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder); /** * 针对swipe和drag状态,当swipe或者drag对应的ViewHolder改变的时候调用 * 我们可以通过重写这个函数获取到swipe、drag开始和结束时机,viewHolder 不为空的时候是开始,空的时候是结束 */ public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { super.onSelectedChanged(viewHolder, actionState); } /** * 针对swipe状态,是否允许swipe(滑动)操作 */ public boolean isItemViewSwipeEnabled() { return true; } /** * 针对swipe状态,swipe滑动的位置超过了百分之多少就消失 */ public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) { return .5f; } /** * 针对swipe状态,swipe的逃逸速度,换句话说就算没达到getSwipeThreshold设置的距离,达到了这个逃逸速度item也会被swipe消失掉 */ public float getSwipeEscapeVelocity(float defaultValue) { return defaultValue; } /** * 针对swipe状态,swipe滑动的阻尼系数,设置最大滑动速度 */ public float getSwipeVelocityThreshold(float defaultValue) { return defaultValue; } /** * 针对swipe状态,swipe 到达滑动消失的距离回调函数,一般在这个函数里面处理删除item的逻辑 * 确切的来讲是swipe item滑出屏幕动画结束的时候调用 */ public abstract void onSwiped(RecyclerView.ViewHolder viewHolder, int direction); /** * 针对drag状态,当item长按的时候是否允许进入drag(拖动)状态 */ public boolean isLongPressDragEnabled() { return true; } /** * 针对drag状态,当前target对应的item是否允许move * 换句话说我们一般用drag来做一些换位置的操作,就是当前target对应的item是否可以换位置 */ public boolean canDropOver(RecyclerView recyclerView, RecyclerView.ViewHolder current, RecyclerView.ViewHolder target) { return true; } /** * 针对drag状态,在canDropOver()函数返回true的情况下,会调用该函数让我们去处理拖动换位置的逻辑(需要重写自己处理变换位置的逻辑) * 如果有位置变换返回true,否则发挥false */ public abstract boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target); /** * 针对drag状态,当drag itemView和底下的itemView重叠的时候,可以给drag itemView设置额外的margin,让重叠更加容易发生。 * 相当于增大了drag itemView的区域 */ public int getBoundingBoxMargin() { return 0; } /** * 针对drag状态,滑动超过百分之多少的距离可以可以调用onMove()函数(注意哦,这里指的是onMove()函数的调用,并不是随手指移动的那个view哦) */ public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) { return .5f; } /** * 针对drag状态,在drag的过程中获取drag itemView底下对应的ViewHolder(一般不用我们处理直接super就好了) */ public RecyclerView.ViewHolder chooseDropTarget(RecyclerView.ViewHolder selected, List<RecyclerView.ViewHolder> dropTargets, int curX, int curY) { return super.chooseDropTarget(selected, dropTargets, curX, curY); } /** * 当onMove return true的时候调用(一般不用我们自己处理,直接super就好) */ public void onMoved(final RecyclerView recyclerView, final RecyclerView.ViewHolder viewHolder, int fromPos, final RecyclerView.ViewHolder target, int toPos, int x, int y) { super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y); } /** * 针对swipe和drag状态,当一个item view在swipe、drag状态结束的时候调用 * drag状态:当手指释放的时候会调用 * swipe状态:当item从RecyclerView中删除的时候调用,一般我们会在onSwiped()函数里面删除掉指定的item view */ public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { } /** * 针对swipe和drag状态,整个过程中一直会调用这个函数,随手指移动的view就是在super里面做到的(和ItemDecoration里面的onDraw()函数对应) */ public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } /** * 针对swipe和drag状态,整个过程中一直会调用这个函数(和ItemDecoration里面的onDrawOver()函数对应) * 这个函数提供给我们可以在RecyclerView的上面再绘制一层东西,比如绘制一层蒙层啥的 */ public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { super.onChildDrawOver(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } /** * 针对swipe和drag状态,当手指离开之后,view回到指定位置动画的持续时间(swipe可能是回到原位,也有可能是swipe掉) */ public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy) { return super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy); } /** * 针对drag状态,当itemView滑动到RecyclerView边界的时候(比如下面边界的时候),RecyclerView会scroll, * 同时会调用该函数去获取scroller距离(不用我们处理 直接super) */ public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds, int totalSize, long msSinceStartScroll) { return super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); }
有了对ItemTouchHelper.Callback里面函数的了解,我们来看一看ItemTouchHelper的使用步骤,我们分为四个步骤:
一定要有RecyclerView(ItemTouchHelper一定要配合RecyclerView使用)。
新建一个类继承自ItemTouchHelper.Callback(也可以是ItemTouchHelper.SimpleCallback)。我们主要在这个类的回调函数中处理swipe或者drag过程中我们自己需要处理的一些逻辑。比如,在getMovementFlags()函数里面设置swipe或者drag支持的方向(上下左右)、在onSwiped()函数里面删除掉需要swipe侧滑删除item的逻辑、在onMove()函数里面处理drag拖拽移动变换位置的逻辑等等。
新建ItemTouchHelper对象,参数正好是第二步继承自ItemTouchHelper.Callback的类。
ItemTouchHelper.attachToRecyclerView(RecyclerView) 把ItemTouchHelper关联到RecyclerView上去,之后所有的处理都会归总到第二步骤继承自ItemTouchHelper.Callback的类中。
三、ItemTouchHelper源码走读
只有当我们在对ItemTouchHelper源码有了一个大概的了解之后,才会让我们更好的理解ItemTouchHelper使用。
在ItemTouchHelper源码走读之前,我们先抛出几个疑问:
RecyclerView的触摸事件是怎么在ItemTouchHelper里面进行处理的。
ItemTouchHelper是里面怎么判断进入swipe或者drag状态。
进入swipe或者drag状态之后,选中的item是怎么随着手指移动的。
随手指移动的item在移动的时候感觉是在RecyclerView上面移动的,当和item有重叠的时候是怎么让选中的item在上层移动的。
现在开始进入正题,开始对ItemTouchHelper代码进行走读。
ItemTouchHelper源码所有的的逻辑处理都是围绕RecyclerView使的四个类来进行,分别是:ItemDecoration、OnItemTouchListener、OnChildAttachStateChangeListener、GestureDetector。ItemTouchHelper里面所有的逻辑处理都是围绕着四个类来进行的。所以我们先对这四个帮助类做一个简单的介绍,关于这几个类更加详细的解释可以自行去google。
ItemDecoration:用来装饰RecyclerView中每个item的帮助类。ItemDecoration里面就三个函数:getItemOffsets()用来给每个item设置额外的offset、onDraw()可以通过这个函数给item绘制任何合适的decoration装饰、onDrawOver()也是用来给item绘制装饰用的但是它和onDraw()函数还是有区别的;onDrawOver()是在RecyclerView的draw()调用完之后在调用的,换句话说onDrawOver()是绘制在最上层的。ItemTouchHelper源码里面我们会在ItemDecoration的onDraw()里面让item随手指移动。之前的博客我们也使用ItemDecoration实现了一个简单的功能,有兴趣的可以瞧下RecyclerView分组悬浮列表
OnItemTouchListener:RecyclerView提供给我们处理item各种事件的一个类,一般会配合GestureDetector来处理item的各种手势事件。或者用来处理item里面一些事件的拦截。OnItemTouchListener里面有三个大家非常熟悉的函数:onInterceptTouchEvent()、onTouchEvent()、onRequestDisallowInterceptTouchEvent()。ItemTouchHelper源码里面我们会在OnItemTouchListener里面处理item的各种事件。
OnChildAttachStateChangeListener:用来监听RecyclerView里面item添加和删除。当我们上层逻辑有item删除的时候,ItemTouchHelper会在OnChildAttachStateChangeListener的onChildViewDetachedFromWindow()函数里面做一些回收工作的处理。
GestureDetector:GestureDetector用来获取触摸过程中的各种手势事件。ItemTouchHelper源码里面会用到GestureDetector来获取item长按的的手势,长按之后然后判断要不要进入drag状态。
ItemTouchHelper select()函数,进入退出swipe或者drag状态的时候会调用到select()函数。
我们先来看下ItemTouchHelper里面的select()函数,首先select()函数两个参数:ViewHolder代表当前swipe或者drag选中的item对应的ViewHolder(如果ViewHolder不为空代表选中了一个item,为空代表swipe或者drag释放了item)、actionState代表当前模式有三个值ACTION_STATE_IDLE空闲状态、ACTION_STATE_SWIPE状态对应 swipe模式、ACTION_STATE_DRAG状态对应 drag模式。select()函数里面做的主要工作有:
如果之前mSelected不会空的时候,会给该mSelected对应的item设置动画,这个动画主要用来处理这些情况的,比如是swipe模式对应的mSelected的时候,当手指释放的时候该mSelected对应的item要么是回到原始位置,要么是滑出屏幕之外。这些都是通过动画来完成的。如果是drag模式对应的mSelected的时候,同样当手指释放的时候该mSelected对应的item也要回到指定的位置上去。这些动画都会存放在mRecoverAnimations里面。
把当前参数的ViewHolder设置给mSelected,同时记录mSelectedStartX,mSelectedStartY等的一些位置,便于后面计算移动的距离。
调用Callback的onSelectedChanged()函数。可以通过参数ViewHolder是否为空来判断是进入还是退出swipe或者drag状态。
告诉RecyclerView重绘,强迫RecyclerView去调用onDraw()函数。(RecyclerView的onDraw()函数的调用会引起ItemDecoration里面onDraw()函数的调用)
简单的看了下select()函数,之后,我们再来从头来梳理下ItemTouchHelper逻辑的流程。
attachToRecyclerView()函数相关逻辑处理
第一步,从attachToRecyclerView()函数开始,该函数里面setupCallbacks()的调用给RecyclerView设置ItemDecoration、OnItemTouchListener、OnChildAttachStateChangeListener、GestureDetector的一些处理。(会在ItemDecoration里面onDraw()函数里面处理item随手指移动的逻辑,OnItemTouchListener里面处理item事件拦截和事件处理的逻辑,OnChildAttachStateChangeListener里面处理当上层逻辑删除item的时候一些回收机制的逻辑,GestureDetector里面处理item长按的逻辑)。
private void setupCallbacks() { ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext()); mSlop = vc.getScaledTouchSlop(); mRecyclerView.addItemDecoration(this); mRecyclerView.addOnItemTouchListener(mOnItemTouchListener); mRecyclerView.addOnChildAttachStateChangeListener(this); initGestureDetector(); }
OnItemTouchListener相关逻辑处理
第二步,因为ItemTouchHelper里面所的逻辑都是围绕触摸事件来进行的,所以当MotionEvent 事件没有被item的子view处理的时候,该MotionEvent 事件会进入到RecyclerView的帮助类OnItemTouchListener里面去,所以这一步的重点就转到了OnItemTouchListener里面的逻辑处理部分了
private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() { @Override public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) { mGestureDetector.onTouchEvent(event); if (DEBUG) { Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event); } final int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { mActivePointerId = event.getPointerId(0); mInitialTouchX = event.getX(); mInitialTouchY = event.getY(); obtainVelocityTracker(); if (mSelected == null) { final RecoverAnimation animation = findAnimation(event); if (animation != null) { mInitialTouchX -= animation.mX; mInitialTouchY -= animation.mY; endRecoverAnimation(animation.mViewHolder, true); if (mPendingCleanup.remove(animation.mViewHolder.itemView)) { mCallback.clearView(mRecyclerView, animation.mViewHolder); } select(animation.mViewHolder, animation.mActionState); updateDxDy(event, mSelectedFlags, 0); } } } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { mActivePointerId = ACTIVE_POINTER_ID_NONE; select(null, ACTION_STATE_IDLE); } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) { // in a non scroll orientation, if distance change is above threshold, we // can select the item final int index = event.findPointerIndex(mActivePointerId); if (DEBUG) { Log.d(TAG, "pointer index " + index); } if (index >= 0) { checkSelectForSwipe(action, event, index); } } if (mVelocityTracker != null) { mVelocityTracker.addMovement(event); } return mSelected != null; } @Override public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) { mGestureDetector.onTouchEvent(event); if (DEBUG) { Log.d(TAG, "on touch: x:" + mInitialTouchX + ",y:" + mInitialTouchY + ", :" + event); } if (mVelocityTracker != null) { mVelocityTracker.addMovement(event); } if (mActivePointerId == ACTIVE_POINTER_ID_NONE) { return; } final int action = event.getActionMasked(); final int activePointerIndex = event.findPointerIndex(mActivePointerId); if (activePointerIndex >= 0) { checkSelectForSwipe(action, event, activePointerIndex); } ViewHolder viewHolder = mSelected; if (viewHolder == null) { return; } switch (action) { case MotionEvent.ACTION_MOVE: { // Find the index of the active pointer and fetch its position if (activePointerIndex >= 0) { updateDxDy(event, mSelectedFlags, activePointerIndex); moveIfNecessary(viewHolder); mRecyclerView.removeCallbacks(mScrollRunnable); mScrollRunnable.run(); mRecyclerView.invalidate(); } break; } case MotionEvent.ACTION_CANCEL: if (mVelocityTracker != null) { mVelocityTracker.clear(); } // fall through case MotionEvent.ACTION_UP: select(null, ACTION_STATE_IDLE); mActivePointerId = ACTIVE_POINTER_ID_NONE; break; case MotionEvent.ACTION_POINTER_UP: { final int pointerIndex = event.getActionIndex(); final int pointerId = event.getPointerId(pointerIndex); if (pointerId == mActivePointerId) { // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mActivePointerId = event.getPointerId(newPointerIndex); updateDxDy(event, mSelectedFlags, pointerIndex); } break; } } } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (!disallowIntercept) { return; } select(null, ACTION_STATE_IDLE); } };
看到里面有三个我们非常熟悉的函数:onInterceptTouchEvent()、onTouchEvent()、onRequestDisallowInterceptTouchEvent()。其中一个用来处理拦截事件的逻辑,一个用来处理事件逻辑,最后一个用来给子view设置item是否可以拦截的设置。
onInterceptTouchEvent()函数里面的逻辑细节
总的来就是如果有mSelected的时候事件就会被拦截下来(mSelected是会随手指移动的item对应的ViewHolder)。onInterceptTouchEvent()函数里面更加具体的细节先把事件添加到GestureDetector里面去(便于GestureDetector里面获取不同的手势的处理),然后分别对MotionEvent.ACTION_DOWN、 MotionEvent.ACTION_UP做不同的逻辑处理;MotionEvent.ACTION_DOWN里面会先记录下初始按下的位置,接下来如果当前触摸位置对应的item有动画(不管是swipe还是drag模式,在手指离开的时候,当前选中的item都会有一个到指定位置的动画)还在执行动画中。这个时候这个item会当做选中的item来处理。MotionEvent.ACTION_UP里面就是清除之前mSelected的选择。
onTouchEvent()函数里面,
checkSelectForSwipe(action, event, activePointerIndex)的调用里面会先去判断是否支持swipe模式Callback.isItemViewSwipeEnabled(),然后去判断swipe支持的方向是否和滑动的方向是否一致Callback.getAbsoluteMovementFlags()。如果这两个条件都满足会在select(vh, ACTION_STATE_SWIPE)函数里面把当前手指下对应的item设置为mSelected,模式对应设置为ACTION_STATE_SWIPE swipe模式。
MotionEvent.ACTION_MOVE的时候先调用updateDxDy(event, mSelectedFlags, activePointerIndex)更新已经滑动的距离,接着调用moveIfNecessary(viewHolder)去设置是否要move,里面也会去回调Callback里面的chooseDropTarget()、onMoved()的函数。接着调用RecyclerView的invalidate()函数迫使RecyclerView去调用onDraw()函数。
MotionEvent.ACTION_UP的时候就是调用了select(null, ACTION_STATE_IDLE)做一些释放操作。
onRequestDisallowInterceptTouchEvent函数里面
如果子view设置disallow的时候会调用select(null, ACTION_STATE_IDLE)函数,其实也好理解,子view都告诉父view不能处理这个事件饿,所以要做一些释放操作。
GestureDetectorCompat类的使用
OnItemTouchListener的帮助类mOnItemTouchListener里面我们看到到了swipe模式的进入时机(onTouchEvent()函数里面checkSelectForSwipe()函数的调用)。但是没有看到drag模式是怎么进入的呀,别着急,另一个帮助类登场了;GestureDetectorCompat。GestureDetectorCompat里面用到的关键的东西都在ItemTouchHelperGestureListener类里面呢:
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener { ItemTouchHelperGestureListener() { } @Override public boolean onDown(MotionEvent e) { return true; } @Override public void onLongPress(MotionEvent e) { View child = findChildView(e); if (child != null) { ViewHolder vh = mRecyclerView.getChildViewHolder(child); if (vh != null) { if (!mCallback.hasDragFlag(mRecyclerView, vh)) { return; } int pointerId = e.getPointerId(0); // Long press is deferred. // Check w/ active pointer id to avoid selecting after motion // event is canceled. if (pointerId == mActivePointerId) { final int index = e.findPointerIndex(mActivePointerId); final float x = e.getX(index); final float y = e.getY(index); mInitialTouchX = x; mInitialTouchY = y; mDx = mDy = 0f; if (DEBUG) { Log.d(TAG, "onlong press: x:" + mInitialTouchX + ",y:" + mInitialTouchY); } if (mCallback.isLongPressDragEnabled()) { select(vh, ACTION_STATE_DRAG); } } } } } }
咦,就处理了一个onLongPress长按手势事件,里面逻辑就是先得到当前MotionEvent事件对应的ViewHolder,调用Callback的hasDragFlag()函数判断是否允许进入drag模式,在调用Callback的isLongPressDragEnabled()函数判断是否允许长按进入drag模式,最后调用select(vh, ACTION_STATE_DRAG)设置mSelected。之后MotionEvent移动事件的处理就都跑到OnItemTouchListener帮助类里面的onTouchEvent()函数里面去了。
通过上面对OnItemTouchListener和手势帮助类GestureDetectorCompat的分析我们可以知道:
swipe和drag模式进入的时机。swipe模式进入的判断是在OnItemTouchListener帮助类里面onTouchEvent()的函数的checkSelectForSwipe()的调用里面判断是否进入,drag模式进入的判断是在GestureDetectorCompat帮助里ItemTouchHelperGestureListener里面onLongPress()里面判断是否进入。
触摸事件在移动的过程中(信息信息请看OnItemTouchListener帮助类里面onTouchEvent()函数的MotionEvent.ACTION_MOVE逻辑处理)会一直去更新滑动的位置(updateDxDy函数)和一直让去重绘(mRecyclerView.invalidate的调用)。
ItemDecoration类的使用
分析到这个时候,咱们还没看到当进入swipe或者drag模式之后,mSelected对应的item是怎么随着手指移动的呀。这个时候就是ItemDecoration派上用场的时候了。上面触摸移动的过程中一直会调用mRecyclerView.invalidate()函数,迫使RecyclerView的onDraw()函数的调用。RecyclerView的onDraw()的调用又会引起ItemDecoration里面的onDraw()函数的调用。瞧一瞧
@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { // we don't know if RV changed something so we should invalidate this index. mOverdrawChildPosition = -1; float dx = 0, dy = 0; if (mSelected != null) { getSelectedDxDy(mTmpPosition); dx = mTmpPosition[0]; dy = mTmpPosition[1]; } mCallback.onDraw(c, parent, mSelected, mRecoverAnimations, mActionState, dx, dy); }
又跑到Callback里面的onDraw()函数去了,在更进去瞧一瞧,
void onDraw(Canvas c, RecyclerView parent, ViewHolder selected, List<ItemTouchHelper.RecoverAnimation> recoverAnimationList, int actionState, float dX, float dY) { final int recoverAnimSize = recoverAnimationList.size(); for (int i = 0; i < recoverAnimSize; i++) { final ItemTouchHelper.RecoverAnimation anim = recoverAnimationList.get(i); anim.update(); final int count = c.save(); onChildDraw(c, parent, anim.mViewHolder, anim.mX, anim.mY, anim.mActionState, false); c.restoreToCount(count); } if (selected != null) { final int count = c.save(); onChildDraw(c, parent, selected, dX, dY, actionState, true); c.restoreToCount(count); } }
该函数里面分两部分,一部分是动画列表里面对应item的绘制,另一部分就是swipe或者drag模式选中的item的绘制。也好理解动画过程中的那些view要更新位置,随跟随手指移动的view也要更新位置。两个都是调用了onChildDraw()函数,最终到了ItemTouchUIUtilImpl里面BaseImpl类的
@Override public void onDraw(Canvas c, RecyclerView recyclerView, View view, float dX, float dY, int actionState, boolean isCurrentlyActive) { view.setTranslationX(dX); view.setTranslationY(dY); }
到这里我们就分析完了item随手指移动的逻辑了。
总结下随手指移动的逻辑,在手指移动的过程中会一直调用mRecyclerView.invalidate(),迫使RecyclerView去调用onDraw(),接着调用到ItemDecoration里面的onDraw(),又调用到Callback里面的onDraw(),接着又到Callback里面的onChildDraw()函数。最终到了ItemTouchUIUtilImpl内部BaseImpl类的onDraw()函数里面最后会调用view.setTranslationX(),view.setTranslationY()来移动view。
这里你可能会提出一个疑问,不对呀。看效果的时候随手指移动的那个item感觉是绘制在RecyclerView之上的呀,因为我们手指滑动的时候选中的item是在RecyclerView的上层滑动的呀。这个是咋做到的呀。这里就要分:Build.VERSION.SDK_INT<21、Build.VERSION.SDK_INT>=21两种情况了。
Build.VERSION.SDK_INT<21:改变了RecyclerView里面item的绘制顺序,把选中的item放到最后一个绘制。详细的内容请参考select()函数里面addChildDrawingOrderCallback()里面具体细节。
Build.VERSION.SDK_INT>=21:通过给选中的item 调用View.setElevation()增加效果来实现的,详细的内容请参考ItemTouchUIUtilImpl内部Api21Impl类onDraw()函数里面具体细节。
四、总结
ItemTouchHelper源码逻辑看起来也不是很复杂么,上面的分析也只是做了一个非常简单的分析,里面很多细节都是一笔带过没有讲的很清楚。我也希望这次的分析能给大家起到一个抛砖引玉的作用。如果大家有什么疑问或者对ItemTouchHelper使用有哪里不清楚的,欢迎在底下留言。在能力范围之内都会为大家解答的。
————————————————
版权声明:本文为CSDN博主「tuacy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wuyuxing24/article/details/78985026
这篇关于ItemTouchHelper源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享