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 setting
app:layout_behavior="@string/appbar_scrolling_view_behavior"
AppBarLayout$ScrollingViewBehavior (AppBarLayout$ScrollingViewBehavior);
- AppBarLayout through
AppBarLayout.Behavior
Monitor 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 dragged
mOnItemTouchListener
theonTouchEvent
In 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