前言
RecyclerView的侧滑功能主要是通过ItemTouchHelper这个类来完成的
这个类在androidx和support库里面都有
这里我们主要是手动拷贝一份,来讲解所有关键代码的实现思路
在阅读时,没必要每行代码都弄懂,那是不太现实的
关键是能读懂关键代码,能对自己想要的地方进行改动
实现思路
核心代码
//这个控件的拖拽还有BUG,有时交换元素时,元素会消失
//主要通过Demo来读源码流程,细节上不要太较真
@SuppressWarnings("all")
public class ItemTouchHelper extends RecyclerView.ItemDecoration implements RecyclerView.OnChildAttachStateChangeListener {
/**
* Up direction, used for swipe & drag control.
*/
public static final int UP = 1;
/**
* Down direction, used for swipe & drag control.
*/
public static final int DOWN = 1 (mActionState * DIRECTION_FLAG_COUNT);
mSelectedStartX = selected.itemView.getLeft();
mSelectedStartY = selected.itemView.getTop();
mSelected = selected;
if (actionState == ACTION_STATE_DRAG) {
mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
}
final ViewParent rvParent = mRecyclerView.getParent();
if (rvParent != null) {
rvParent.requestDisallowInterceptTouchEvent(mSelected != null);//REMARK => 禁止RecyclerView父控件拦截事件
}
if (!preventLayout) {
mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
}
mCallback.onSelectedChanged(mSelected, mActionState); //REMARK => 新元素被选中时,执行回调
mRecyclerView.invalidate();
}
void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) {
// wait until animations are complete.
mRecyclerView.post(new Runnable() {
@Override
public void run() {
if (mRecyclerView != null && mRecyclerView.isAttachedToWindow()
&& !anim.mOverridden
&& anim.mViewHolder.getAdapterPosition() != RecyclerView.NO_POSITION) {
final RecyclerView.ItemAnimator animator = mRecyclerView.getItemAnimator();
// if animator is running or we have other active recover animations, we try
// not to call onSwiped because DefaultItemAnimator is not good at merging
// animations. Instead, we wait and batch.
if ((animator == null || !animator.isRunning(null))
&& !hasRunningRecoverAnim()) {
mCallback.onSwiped(anim.mViewHolder, swipeDir);
} else {
mRecyclerView.post(this);
}
}
}
});
}
boolean hasRunningRecoverAnim() {
final int size = mRecoverAnimations.size();
for (int i = 0; i > (DIRECTION_FLAG_COUNT * ACTION_STATE_SWIPE);
if (swipeFlags == 0) {
return false;
}
// mDx and mDy are only set in allowed directions. We use custom x/y here instead of
// updateDxDy to avoid swiping if user moves more in the other direction
final float x = motionEvent.getX(pointerIndex);
final float y = motionEvent.getY(pointerIndex);
// Calculate the distance moved
final float dx = x - mInitialTouchX;
final float dy = y - mInitialTouchY;
// swipe target is chose w/o applying flags so it does not really check if swiping in that
// direction is allowed. This why here, we use mDx mDy to check slope value again.
final float absDx = Math.abs(dx);
final float absDy = Math.abs(dy);
if (absDx = 0; i--) {
final RecoverAnimation anim = mRecoverAnimations.get(i);
final View view = anim.mViewHolder.itemView;
if (hitTest(view, x, y, anim.mX, anim.mY)) {
return view;
}
}
return mRecyclerView.findChildViewUnder(x, y);
}
/**
* Starts dragging the provided ViewHolder. By default, ItemTouchHelper starts a drag when a
* View is long pressed. You can disable that behavior by overriding
* {@link Callback#isLongPressDragEnabled()}.
*
* For this method to work:
*
* - The provided ViewHolder must be a child of the RecyclerView to which this
* ItemTouchHelper
* is attached.
* - {@link Callback} must have dragging enabled.
* - There must be a previous touch event that was reported to the ItemTouchHelper
* through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener
* grabs previous events, this should work as expected.
*
*
* For example, if you would like to let your user to be able to drag an Item by touching one
* of its descendants, you may implement it as follows:
*
* viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
* public boolean onTouch(View v, MotionEvent event) {
* if (MotionEvent.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
* mItemTouchHelper.startDrag(viewHolder);
* }
* return false;
* }
* });
*
*
*
* @param viewHolder The ViewHolder to start dragging. It must be a direct child of
* RecyclerView.
* @see Callback#isItemViewSwipeEnabled()
*/
public void startDrag(ViewHolder viewHolder) {
if (!mCallback.hasDragFlag(mRecyclerView, viewHolder)) {
return;
}
if (viewHolder.itemView.getParent() != mRecyclerView) {
return;
}
obtainVelocityTracker();
mDx = mDy = 0f;
select(viewHolder, ACTION_STATE_DRAG);
}
/**
* Starts swiping the provided ViewHolder. By default, ItemTouchHelper starts swiping a View
* when user swipes their finger (or mouse pointer) over the View. You can disable this
* behavior
* by overriding {@link Callback}
*
* For this method to work:
*
* - The provided ViewHolder must be a child of the RecyclerView to which this
* ItemTouchHelper is attached.
* - {@link Callback} must have swiping enabled.
* - There must be a previous touch event that was reported to the ItemTouchHelper
* through RecyclerView's ItemTouchListener mechanism. As long as no other ItemTouchListener
* grabs previous events, this should work as expected.
*
*
* For example, if you would like to let your user to be able to swipe an Item by touching one
* of its descendants, you may implement it as follows:
*
* viewHolder.dragButton.setOnTouchListener(new View.OnTouchListener() {
* public boolean onTouch(View v, MotionEvent event) {
* if (MotionEvent.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
* mItemTouchHelper.startSwipe(viewHolder);
* }
* return false;
* }
* });
*
*
* @param viewHolder The ViewHolder to start swiping. It must be a direct child of
* RecyclerView.
*/
public void startSwipe(ViewHolder viewHolder) {
if (!mCallback.hasSwipeFlag(mRecyclerView, viewHolder)) {
return;
}
if (viewHolder.itemView.getParent() != mRecyclerView) {
return;
}
obtainVelocityTracker();
mDx = mDy = 0f;
select(viewHolder, ACTION_STATE_SWIPE);
}
RecoverAnimation findAnimation(MotionEvent event) {
if (mRecoverAnimations.isEmpty()) {
return null;
}
View target = findChildView(event);
for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
final RecoverAnimation anim = mRecoverAnimations.get(i);
if (anim.mViewHolder.itemView == target) {
return anim;
}
}
return null;
}
void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Calculate the distance moved
mDx = x - mInitialTouchX;
mDy = y - mInitialTouchY;
if ((directionFlags & LEFT) == 0) {
mDx = Math.max(0, mDx);
}
if ((directionFlags & RIGHT) == 0) {
mDx = Math.min(0, mDx);
}
if ((directionFlags & UP) == 0) {
mDy = Math.max(0, mDy);
}
if ((directionFlags & DOWN) == 0) {
mDy = Math.min(0, mDy);
}
}
private int swipeIfNecessary(ViewHolder viewHolder) {
if (mActionState == ACTION_STATE_DRAG) {
return 0;
}
final int originalMovementFlags = mCallback.getMovementFlags(mRecyclerView, viewHolder);
final int absoluteMovementFlags = mCallback.convertToAbsoluteDirection(
originalMovementFlags,
ViewCompat.getLayoutDirection(mRecyclerView));
final int flags = (absoluteMovementFlags
& ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
if (flags == 0) {
return 0;
}
final int originalFlags = (originalMovementFlags
& ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE * DIRECTION_FLAG_COUNT);
int swipeDir;
if (Math.abs(mDx) > Math.abs(mDy)) {
if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
// if swipe dir is not in original flags, it should be the relative direction
if ((originalFlags & swipeDir) == 0) {
// convert to relative
return Callback.convertToRelativeDirection(swipeDir,
ViewCompat.getLayoutDirection(mRecyclerView));
}
return swipeDir;
}
if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
return swipeDir;
}
} else {
if ((swipeDir = checkVerticalSwipe(viewHolder, flags)) > 0) {
return swipeDir;
}
if ((swipeDir = checkHorizontalSwipe(viewHolder, flags)) > 0) {
// if swipe dir is not in original flags, it should be the relative direction
if ((originalFlags & swipeDir) == 0) {
// convert to relative
return Callback.convertToRelativeDirection(swipeDir,
ViewCompat.getLayoutDirection(mRecyclerView));
}
return swipeDir;
}
}
return 0;
}
private int checkHorizontalSwipe(ViewHolder viewHolder, int flags) {
if ((flags & (LEFT | RIGHT)) != 0) {
final int dirFlag = mDx > 0 ? RIGHT : LEFT;
if (mVelocityTracker != null && mActivePointerId > -1) {
mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
final float xVelocity = mVelocityTracker.getXVelocity(mActivePointerId);
final float yVelocity = mVelocityTracker.getYVelocity(mActivePointerId);
final int velDirFlag = xVelocity > 0f ? RIGHT : LEFT;
final float absXVelocity = Math.abs(xVelocity);
if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag
&& absXVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity)
&& absXVelocity > Math.abs(yVelocity)) {
return velDirFlag;
}
}
final float threshold = mRecyclerView.getWidth() * mCallback
.getSwipeThreshold(viewHolder);
if ((flags & dirFlag) != 0 && Math.abs(mDx) > threshold) {
return dirFlag;
}
}
return 0;
}
private int checkVerticalSwipe(ViewHolder viewHolder, int flags) {
if ((flags & (UP | DOWN)) != 0) {
final int dirFlag = mDy > 0 ? DOWN : UP;
if (mVelocityTracker != null && mActivePointerId > -1) {
mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
final float xVelocity = mVelocityTracker.getXVelocity(mActivePointerId);
final float yVelocity = mVelocityTracker.getYVelocity(mActivePointerId);
final int velDirFlag = yVelocity > 0f ? DOWN : UP;
final float absYVelocity = Math.abs(yVelocity);
if ((velDirFlag & flags) != 0 && velDirFlag == dirFlag
&& absYVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity)
&& absYVelocity > Math.abs(xVelocity)) {
return velDirFlag;
}
}
final float threshold = mRecyclerView.getHeight() * mCallback
.getSwipeThreshold(viewHolder);
if ((flags & dirFlag) != 0 && Math.abs(mDy) > threshold) {
return dirFlag;
}
}
return 0;
}
private void addChildDrawingOrderCallback() {
if (Build.VERSION.SDK_INT >= 21) {
return; // we use elevation on Lollipop
}
if (mChildDrawingOrderCallback == null) {
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
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?