拦截RecyclerView在NestedScrollView中向下滑动
Intercepting RecyclerView downwards fling in NestedScrollView
我有以下布局:
<android.support.design.widget.CoordinatorLayout
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<ImageView
android:layout_width="match_parent"
android:layout_height="160dp"
app:layout_collapseMode="parallax" />
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="52dp"
app:layout_collapseMode="pin" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="com.example.CardViewBehavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:id="@+id/fixedBanner"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</FrameLayout>
<FrameLayout
android:id="@+id/card1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- random content -->
</FrameLayout>
<FrameLayout
android:id="@+id/card2"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</layout>
当从 fixedBanner
或 card1
向下滚动(手指向上)时,appBarLayout
首先折叠,然后 nestedScrollView
向下滚动。但是,如果从 recyclerView
向下滚动,recyclerView
开始滚动。我希望 nestedScrollView
在 recyclerView
之前先向下滚动。
我已经尝试使用在 nestedScrollView
上设置的自定义 CardViewBehavior
,它会覆盖 onNestedPreScroll 以在 appBarLayout
未完全折叠且仍有滚动范围时使用滚动增量 nestedScrollView
.
但是,如果我在 recyclerView
上滑动得足够快,recyclerView
在 nestedScrollView
完全滚动到底部之前就开始快速滑动。我尝试在 CardViewBehavior
中重写 onNestedPreFling
和 onNestedFling
,但当 RecyclerView 开始自行投掷时,这两个方法似乎从未被调用过。
如何确保 nestedScrollView
在 recyclerView
开始滚动之前滚动到底部?
扩展 NestedScrollView
并直接覆盖 onNestedPreScroll
方法。
(在 https://www.androiddesignpatterns.com/2018/01/experimenting-with-nested-scrolling.html 上找到了解决方案)
谢谢@Henry,我已经成功地重新创建了您在 kotlin 中@AlexLockwood 提到的 the article 的行为。我已经处理这个问题两天了,其他答案通常建议使用 WRAP_CONTENT
属性并且基本上打败了 RecyclerView
的目的,因为 View
不再被回收。我提供下面的代码以防将来对某人有所帮助。
class NestedScrollLayout(
context: Context,
attrs: AttributeSet?
) : NestedScrollView(context, attrs),
NestedScrollingParent3 {
private var parentHelper = NestedScrollingParentHelper(this)
override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
return (axes and ViewCompat.SCROLL_AXIS_VERTICAL) != 0
}
override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) {
parentHelper.onNestedScrollAccepted(child, target, axes)
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, type)
}
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
if(target is RecyclerView) {
if ((dy < 0 && isRvScrolledToTop(target)) || (dy > 0 && !isNsvScrolledToBottom(this))) {
scrollBy(0, dy)
consumed[1] = dy
return
}
}
dispatchNestedPreScroll(dx, dy, consumed, null, type)
}
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int
) {
val oldScrollY = scrollY
scrollBy(0, dyUnconsumed)
val mConsumed = scrollY - oldScrollY
val mUnconsumed = dyUnconsumed - mConsumed
dispatchNestedScroll(0, mConsumed, 0, mUnconsumed, null, type)
}
override fun onStopNestedScroll(target: View, type: Int) {
parentHelper.onStopNestedScroll(target, type)
stopNestedScroll(type)
}
override fun onStartNestedScroll(child: View, target: View, axes: Int): Boolean {
Log.println(Log.ASSERT, "NestedScrollLayout:onStartNestedScroll", "Requested")
return onStartNestedScroll(child, target, axes, ViewCompat.TYPE_TOUCH)
}
override fun onNestedScrollAccepted(child: View, target: View, axes: Int) {
onNestedScrollAccepted(child, target, axes, ViewCompat.TYPE_TOUCH)
}
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray) {
onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH)
}
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int
) {
onNestedScroll(
target,
dxConsumed,
dyConsumed,
dxUnconsumed,
dyUnconsumed,
ViewCompat.TYPE_TOUCH
)
}
override fun onStopNestedScroll(target: View) {
onStopNestedScroll(target, ViewCompat.TYPE_TOUCH)
}
override fun getNestedScrollAxes(): Int {
return parentHelper.nestedScrollAxes
}
companion object {
private fun isNsvScrolledToBottom(nsv: NestedScrollView): Boolean {
return !nsv.canScrollVertically(1)
}
private fun isRvScrolledToTop(rv: RecyclerView): Boolean {
rv.layoutManager?.let { lm ->
return when (lm) {
is LinearLayoutManager -> {
lm.findViewByPosition(0)?.top == 0 && lm.findFirstVisibleItemPosition() == 0
}
is GridLayoutManager -> {
lm.findViewByPosition(0)?.top == 0 && lm.findFirstVisibleItemPosition() == 0
}
is StaggeredGridLayoutManager -> {
lm.findViewByPosition(0)?.top == 0 && lm.findFirstVisibleItemPositions(
intArrayOf(0)
)[0] == 0
}
else -> lm.findViewByPosition(0)?.top == 0
}
}
return false
}
}
}
然后我们可以使用NestedScrollLayout
如下:
<com.organization.appname.NestedScrollLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="400dp" />
...
</com.organization.appname.NestedScrollLayout>
我有以下布局:
<android.support.design.widget.CoordinatorLayout
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<ImageView
android:layout_width="match_parent"
android:layout_height="160dp"
app:layout_collapseMode="parallax" />
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="52dp"
app:layout_collapseMode="pin" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="@+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="com.example.CardViewBehavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<FrameLayout
android:id="@+id/fixedBanner"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</FrameLayout>
<FrameLayout
android:id="@+id/card1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- random content -->
</FrameLayout>
<FrameLayout
android:id="@+id/card2"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</layout>
当从 fixedBanner
或 card1
向下滚动(手指向上)时,appBarLayout
首先折叠,然后 nestedScrollView
向下滚动。但是,如果从 recyclerView
向下滚动,recyclerView
开始滚动。我希望 nestedScrollView
在 recyclerView
之前先向下滚动。
我已经尝试使用在 nestedScrollView
上设置的自定义 CardViewBehavior
,它会覆盖 onNestedPreScroll 以在 appBarLayout
未完全折叠且仍有滚动范围时使用滚动增量 nestedScrollView
.
但是,如果我在 recyclerView
上滑动得足够快,recyclerView
在 nestedScrollView
完全滚动到底部之前就开始快速滑动。我尝试在 CardViewBehavior
中重写 onNestedPreFling
和 onNestedFling
,但当 RecyclerView 开始自行投掷时,这两个方法似乎从未被调用过。
如何确保 nestedScrollView
在 recyclerView
开始滚动之前滚动到底部?
扩展 NestedScrollView
并直接覆盖 onNestedPreScroll
方法。
(在 https://www.androiddesignpatterns.com/2018/01/experimenting-with-nested-scrolling.html 上找到了解决方案)
谢谢@Henry,我已经成功地重新创建了您在 kotlin 中@AlexLockwood 提到的 the article 的行为。我已经处理这个问题两天了,其他答案通常建议使用 WRAP_CONTENT
属性并且基本上打败了 RecyclerView
的目的,因为 View
不再被回收。我提供下面的代码以防将来对某人有所帮助。
class NestedScrollLayout(
context: Context,
attrs: AttributeSet?
) : NestedScrollView(context, attrs),
NestedScrollingParent3 {
private var parentHelper = NestedScrollingParentHelper(this)
override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
return (axes and ViewCompat.SCROLL_AXIS_VERTICAL) != 0
}
override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) {
parentHelper.onNestedScrollAccepted(child, target, axes)
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, type)
}
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
if(target is RecyclerView) {
if ((dy < 0 && isRvScrolledToTop(target)) || (dy > 0 && !isNsvScrolledToBottom(this))) {
scrollBy(0, dy)
consumed[1] = dy
return
}
}
dispatchNestedPreScroll(dx, dy, consumed, null, type)
}
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int
) {
val oldScrollY = scrollY
scrollBy(0, dyUnconsumed)
val mConsumed = scrollY - oldScrollY
val mUnconsumed = dyUnconsumed - mConsumed
dispatchNestedScroll(0, mConsumed, 0, mUnconsumed, null, type)
}
override fun onStopNestedScroll(target: View, type: Int) {
parentHelper.onStopNestedScroll(target, type)
stopNestedScroll(type)
}
override fun onStartNestedScroll(child: View, target: View, axes: Int): Boolean {
Log.println(Log.ASSERT, "NestedScrollLayout:onStartNestedScroll", "Requested")
return onStartNestedScroll(child, target, axes, ViewCompat.TYPE_TOUCH)
}
override fun onNestedScrollAccepted(child: View, target: View, axes: Int) {
onNestedScrollAccepted(child, target, axes, ViewCompat.TYPE_TOUCH)
}
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray) {
onNestedPreScroll(target, dx, dy, consumed, ViewCompat.TYPE_TOUCH)
}
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int
) {
onNestedScroll(
target,
dxConsumed,
dyConsumed,
dxUnconsumed,
dyUnconsumed,
ViewCompat.TYPE_TOUCH
)
}
override fun onStopNestedScroll(target: View) {
onStopNestedScroll(target, ViewCompat.TYPE_TOUCH)
}
override fun getNestedScrollAxes(): Int {
return parentHelper.nestedScrollAxes
}
companion object {
private fun isNsvScrolledToBottom(nsv: NestedScrollView): Boolean {
return !nsv.canScrollVertically(1)
}
private fun isRvScrolledToTop(rv: RecyclerView): Boolean {
rv.layoutManager?.let { lm ->
return when (lm) {
is LinearLayoutManager -> {
lm.findViewByPosition(0)?.top == 0 && lm.findFirstVisibleItemPosition() == 0
}
is GridLayoutManager -> {
lm.findViewByPosition(0)?.top == 0 && lm.findFirstVisibleItemPosition() == 0
}
is StaggeredGridLayoutManager -> {
lm.findViewByPosition(0)?.top == 0 && lm.findFirstVisibleItemPositions(
intArrayOf(0)
)[0] == 0
}
else -> lm.findViewByPosition(0)?.top == 0
}
}
return false
}
}
}
然后我们可以使用NestedScrollLayout
如下:
<com.organization.appname.NestedScrollLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="400dp" />
...
</com.organization.appname.NestedScrollLayout>