我遇到了同样的问题,我花了将近一整天的时间来解决它。
前提:
首先,我的 xml 布局如下所示:
<CoordinatorLayout>
<com.google.android.material.appbar.AppBarLayout
...
</com.google.android.material.appbar.AppBarLayout>
<NestedScrollView>
<RecyclerView/>
</NestedScrollView>
</CoordinatorLayout>
为了使滚动行为正常,我还让nestedScrolling
残疾人RecyclerView
士通过:RecyclerView.setIsNestedScrollingEnabled(false);
原因:
但是当我在其中拖动项目时,ItemTouchHelper
我仍然无法按预期进行自动滚动。IT CANNOT SCROLL的Recyclerview
原因在于:scrollIfNecessary()
ItemTouchHelper
boolean scrollIfNecessary() {
RecyclerView.LayoutManager lm = mRecyclerView.getLayoutManager();
if (mTmpRect == null) {
mTmpRect = new Rect();
}
int scrollY = 0;
lm.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect);
if (lm.canScrollVertically()) {
int curY = (int) (mSelectedStartY + mDy);
final int topDiff = curY - mTmpRect.top - mRecyclerView.getPaddingTop();
if (mDy < 0 && topDiff < 0) {
scrollY = topDiff;
} else if (mDy > 0) {
final int bottomDiff = curY + mSelected.itemView.getHeight() + mTmpRect.bottom
- (mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom());
if (bottomDiff > 0) {
scrollY = bottomDiff;
}
}
}
if (scrollY != 0) {
scrollY = mCallback.interpolateOutOfBoundsScroll(mRecyclerView,
mSelected.itemView.getHeight(), scrollY,
mRecyclerView.getHeight(), scrollDuration);
}
if (scrollY != 0) {
mRecyclerView.scrollBy(scrollX, scrollY);
return true;
}
return false;
}
- 原因一:当
nestedScrolling
forRecyclerView
设置为 false 时,实际上有效的滚动对象是NestedScrollView
,它是 的父对象RecyclerView
。所以RecyclerView.scrollBy(x, y)
这里根本不起作用!
- 原因2:
mRecyclerView.getHeight()
比 大得多NestedScrollView.getHeight()
。因此,当我将项目拖到RecyclerView
底部时,结果scrollIfNecessary()
也是错误的。
- 原因 3:
mSelectedStartY
在我们的案例中,它看起来不像预期值。因为我们需要在我们的例子中计算scrollY
of NestedScrollView
。
因此,我们需要重写这个方法来满足我们的期望。解决方案来了:
解决方案:
步骤1:
为了覆盖这个scrollIfNecessary()
(这个方法不是),你需要在一个与'spublic
同名的包下新建一个类。ItemTouchHelper
像这样:
第2步:
除了覆盖之外scrollIfNecessary()
,我们还需要覆盖select()
,以便在开始拖动时获取mSelectedStartY
和scrollY
的值NestedScrollView
。
public override fun select(selected: RecyclerView.ViewHolder?, actionState: Int) {
super.select(selected, actionState)
if (selected != null) {
mSelectedStartY = selected.itemView.top
mSelectedStartScrollY = (mRecyclerView.parent as NestedScrollView).scrollY.toFloat()
}
}
注意: mSelectedStartY
和对于向上或向下mSelectedStartScrollY
滚动都非常重要。NestedScrollView
第 3 步:
现在我们可以覆盖scrollIfNecessary()
了,你需要注意下面的注释:
public override fun scrollIfNecessary(): Boolean {
...
val lm = mRecyclerView.layoutManager
if (mTmpRect == null) {
mTmpRect = Rect()
}
var scrollY = 0
val currentScrollY = (mRecyclerView.parent as NestedScrollView).scrollY
// We need to use the height of NestedScrollView, not RecyclerView's!
val actualShowingHeight = (mRecyclerView.parent as NestedScrollView).height
lm!!.calculateItemDecorationsForChild(mSelected.itemView, mTmpRect!!)
if (lm.canScrollVertically()) {
// The true current Y of the item in NestedScrollView, not in RecyclerView!
val curY = (mSelectedStartY + mDy - currentScrollY).toInt()
// The true mDy should plus the initial scrollY and minus current scrollY of NestedScrollView
val checkDy = (mDy + mSelectedStartScrollY - currentScrollY).toInt()
val topDiff = curY - mTmpRect!!.top - mRecyclerView.paddingTop
if (checkDy < 0 && topDiff < 0) {// User is draging the item out of the top edge.
scrollY = topDiff
} else if (checkDy > 0) { // User is draging the item out of the bottom edge.
val bottomDiff = (curY + mSelected.itemView.height + mTmpRect!!.bottom
- (actualShowingHeight - mRecyclerView.paddingBottom))
if (bottomDiff > 0) {
scrollY = bottomDiff
}
}
}
if (scrollY != 0) {
scrollY = mCallback.interpolateOutOfBoundsScroll(
mRecyclerView,
mSelected.itemView.height, scrollY, actualShowingHeight, scrollDuration
)
}
if (scrollY != 0) {
...
// The scrolling behavior should be assigned to NestedScrollView!
(mRecyclerView.parent as NestedScrollView).scrollBy(0, scrollY)
return true
}
...
return false
}
结果:
我可以通过下面的 Gif 向您展示我的工作: