The problem

When realizing the function of RecyclerView sorting, When CoordinatorLayout+AppBarLayout+ APP :layout_behavior=”@string/ Appbar_SCrolling_view_behavior “, the phenomenon of not being able to drag the ranking occurs:

This paper expounds the causes and solutions of this phenomenon.

Knowledge of the premise

Nesting sliding mechanism itemTouchHelper principle of CoordinatorLayout+AppBarLayout principle

Drag sorting is mainly implemented by Google’s encapsulated ItemTouchHelper. The sliding linkage effect of RecyclerView involves nesting sliding and CoordinatorLayout mechanism. You need to understand the relevant principles. There have been many excellent explanations on the web, so not here to teach fish to swim.

In a nutshell

  • CoordinatorLayout uses behavior to inform each other of its immediate child views (positions, nested slides…) In which the subview can make corresponding changes;
  • RecyclerView through settingapp:layout_behavior="@string/appbar_scrolling_view_behavior"AppBarLayout$ScrollingViewBehavior (AppBarLayout$ScrollingViewBehavior);

  • AppBarLayout throughAppBarLayout.BehaviorMonitor nested sliding to achieve linkage effect;
  • Drag sort brief flow:

Select item and start dragging -> item to see if it exceeds the RecyclerView boundary. Check whether to switch item location -> RecyclerView boundary check -> item boundary check….. -> Release Item to finish dragging

why

It can be observed that when the RecyclerView cannot be dragged to sort, it has a large offset due to the influence of the height of AppBarLayout, which is easy to know from the principle of ItemTouchHelper. The reason is that when the item is dragged, it cannot be moved to the boundary of RecyclerView for scrolling, so it cannot continue to sort.

The solution

  • As long as the item of drag sorting is normally close to RecyclerView boundary, that is, correct the offset of RecyclerView in the process of drag;
  • The offset of RecyclerView is determined by AppBarLayout (the maximum offset is the height of AppBarLayout, and the real-time offset is determined by nested sliding generated by RecyclerView scrolling);
  • ItemTouchHelper can be used to create a RecyclerView when an item is draggedmOnItemTouchListenertheonTouchEventIn the method, the category of nested sliding mechanism has been removed;

So we can use the ItemTouchHelper boundary check to make AppBarLayout linkage by triggering nested slides to correct the offset. ItemTouchHelper () : scrollIfNecessary () : scrollIfNecessary () : scrollIfNecessary () : scrollIfNecessary (); With the solution, there are two questions:

  • How to obtain the offset of RecyclerView
  • How do I trigger a nested slide

For offset: a simple point can directly take the visible height of RecyclerView and the actual height, the difference is offset. Trigger nested sliding: This can be done by constructing a process for nested sliding based on the mechanism.

Add the following logic except the original code:

    boolean scrollIfNecessary(a) {...// Only drag up and down
        if (lm.canScrollVertically()) {
            int curY = (int) (mSelectedStartY + mDy);
            final int topDiff = curY - mTmpRect.top - mRecyclerView.getPaddingTop();
            // Drag up, original logic
            if (mDy < 0 && topDiff < 0) {
                scrollY = topDiff;
            } else if (mDy > 0) { // Drag down

                // Visible range
                mRecyclerView.getGlobalVisibleRect(mRecyclerviewGlobalVisibleRect, null);
                //mRecyclerView bottom coordinates
                int recyclerViewBottom = mRecyclerviewGlobalVisibleRect.top + mRecyclerView.getHeight();
                // The distance offset by AppBarLayout$ScrollingViewBehavior
                int offsetOfRecyclerView = recyclerViewBottom - mRecyclerviewGlobalVisibleRect.bottom;

                final int bottomDiff = curY + mSelected.itemView.getHeight() + mTmpRect.bottom
                        - (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom())
                        // Add offset
                        + offsetOfRecyclerView;

                if (bottomDiff > 0) {

                    // If the offset is greater than 0, consume the offset first
                    if (offsetOfRecyclerView > 0) {
                        // The amount of scrolling required
                        int needScrollDistance = this.mCallback.interpolateOutOfBoundsScroll(this.mRecyclerView, this.mSelected.itemView.getHeight(), bottomDiff, this.mRecyclerView.getHeight(), scrollDuration);
                        // Nested slide consumption
                        int nestedScrollConsume = Math.min(needScrollDistance, offsetOfRecyclerView);

                        // Construct nested slides
                        mRecyclerView.startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
                        mRecyclerView.dispatchNestedPreScroll(0, nestedScrollConsume, null.null);
                        mRecyclerView.dispatchNestedScroll(0.0.0, nestedScrollConsume, null);
                        mRecyclerView.stopNestedScroll();

                        if (mDragScrollStartTimeInMs == Long.MIN_VALUE) {
                            mDragScrollStartTimeInMs = now;
                        }
                        / / correct mDy
                        mDy += nestedScrollConsume;
                        // Eliminate jitter
                        mRecyclerView.invalidate();

                        if (nestedScrollConsume == needScrollDistance) {
                            // If the offset is equal to the slide value, scrollY == 0 does not start the scroll
                            return true;
                        } else {
                            // The remaining offsetscrollY = needScrollDistance - offsetOfRecyclerView; }}else {
                        / / the original logic
                        scrollY = bottomDiff;
                    }
                }
            }
        }
        ......
    }
Copy the code

Effect:

Code: NestScrollableItemTouchHelper