NestedScrollView 与 RecyclerView 的嵌套使用问题,在网上看到的都是说给 RecyclerView 设置 setNestedScrollingEnabled(false)。但是这个方法会有一个问题,
那就是要求 RecyclerView 的高度是 wrap_content 的 (要不然高度太小了,底部的 Item View 滑动不出来),即有多少 Item View,就得要有相应的多大高度。但这不是我想要的,一是这会导致 ViewHolder 不能被回收!!!RecyclerView 就失去了 Recycle 的意义!!! 二是在向顶部快速 fling 滑动的时候,整体滑动效果不连贯。 有人还称这个方法很优雅,其实一点也不优雅。。。
上视频看看使用前,使用后分别是什么样的效果:
before
after
(原创作品,转载请声明出处:https://blog.csdn.net/hegan2010/article/details/113103751)
为了解决这个问题,使得 NestedScrollView 与 RecyclerView 可以双滑动,并且不产生滑动冲突,写了一个布局管理器。
原理是,在RecyclerView的可见区域顶部低于 NestedScrollView 可见区域顶部时,减少 RecyclerView 在Y轴上消费掉滑动距离,或者设为0,或者为两者高度距离差,让NestedScrollView 消费掉更多的滑动距离。
实现代码如下(继承自GridLayoutManager,可以根据实际情况修改为继承自哪个LayoutManager,只要重载了 scrollVerticallyBy 方法就行):
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import androidx.annotation.CallSuper;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewParentCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class AutoScrollGridLayoutManager extends GridLayoutManager implements
View.OnLayoutChangeListener {
protected RecyclerView mRecyclerView;
private boolean mForceScrolling;
public AutoScrollGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public AutoScrollGridLayoutManager(Context context, int spanCount) {
super(context, spanCount);
}
public AutoScrollGridLayoutManager(Context context, int spanCount, int orientation,
boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
}
@CallSuper
@Override
public void onAttachedToWindow(RecyclerView view) {
super.onAttachedToWindow(view);
mRecyclerView = view;
mRecyclerView.addOnLayoutChangeListener(this);
}
@CallSuper
@Override
public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
super.onDetachedFromWindow(view, recycler);
mRecyclerView.removeOnLayoutChangeListener(this);
mRecyclerView = null;
}
@CallSuper
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (dy == 0) {
return super.scrollVerticallyBy(dy, recycler, state);
} else if (mForceScrolling) {
super.scrollVerticallyBy(-dy, recycler, state);
mForceScrolling = false;
return 0;
}
ViewGroup parentView = getParentNestedScrollView(mRecyclerView);
if (parentView == null) {
return super.scrollVerticallyBy(dy, recycler, state);
}
Rect parentVisibleRect = new Rect();
parentView.getGlobalVisibleRect(parentVisibleRect);
Rect recyclerVisibleRect = new Rect();
mRecyclerView.getGlobalVisibleRect(recyclerVisibleRect);
if (dy > 0) {
int verticalOffset = recyclerVisibleRect.top - parentVisibleRect.top;
if (verticalOffset <= 0) {
return super.scrollVerticallyBy(dy, recycler, state);
} else { // verticalOffset > 0
if (dy <= verticalOffset) {
return 0;
} else { // dy > verticalOffset
return super.scrollVerticallyBy(dy - verticalOffset, recycler, state);
}
}
} else {
int verticalOffset = recyclerVisibleRect.bottom - parentVisibleRect.bottom;
if (verticalOffset >= 0) {
return super.scrollVerticallyBy(dy, recycler, state);
} else { // verticalOffset < 0
if (dy >= verticalOffset) {
return 0;
} else { // dy < verticalOffset
return super.scrollVerticallyBy(dy - verticalOffset, recycler, state);
}
}
}
}
@CallSuper
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
mForceScrolling = false;
ViewGroup parentView = getParentNestedScrollView(mRecyclerView);
if (parentView == null) {
return;
}
int offset = mRecyclerView.computeVerticalScrollOffset();
int extent = mRecyclerView.computeVerticalScrollExtent();
int range = mRecyclerView.computeVerticalScrollRange();
int bottomRemain = range - offset - extent;
if (bottomRemain <= 0) {
return;
}
Rect parentVisibleRect = new Rect();
parentView.getGlobalVisibleRect(parentVisibleRect);
Rect recyclerVisibleRect = new Rect();
mRecyclerView.getGlobalVisibleRect(recyclerVisibleRect);
int bottomCovered = parentVisibleRect.bottom - recyclerVisibleRect.bottom;
if (bottomCovered > 0) {
mForceScrolling = true;
mRecyclerView.startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
mRecyclerView.scrollBy(0, -Math.min(bottomRemain, bottomCovered));
}
}
public static ViewGroup getParentNestedScrollView(RecyclerView recyclerView) {
if (!recyclerView.isNestedScrollingEnabled()) {
return null;
}
int axes = ViewCompat.SCROLL_AXIS_VERTICAL;
ViewParent p = recyclerView.getParent();
View child = recyclerView;
while (p != null) {
if (ViewParentCompat.onStartNestedScroll(p, child, recyclerView, axes)) {
return (p instanceof ViewGroup) ? (ViewGroup) p : null;
}
if (p instanceof View) {
child = (View) p;
}
p = p.getParent();
}
return null;
}
}