您当前的位置: 首页 >  ui

命运之手

暂无认证

  • 3浏览

    0关注

    747博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【高级UI】【027】RecyclerView侧滑源码解析

命运之手 发布时间:2022-03-20 18:32:03 ,浏览量:3

前言

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
关注
打赏
1654938663
查看更多评论
0.0550s