I have the following layout:
<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>
When scrolling down (finger goes upwards) from fixedBanner
or card1
, the appBarLayout
collapses first, then nestedScrollView
scrolls down. However, if scrolling down from recyclerView
, recyclerView
starts scrolling. I want nestedScrollView
to scroll down first before recyclerView
.
I have tried using a custom CardViewBehavior
set on nestedScrollView
which overrides onNestedPreScroll to consume scroll deltas if appBarLayout
is not fully collapsed and there is still range to scroll in nestedScrollView
.
However, if I swipe fast enough on recyclerView
, recyclerView
starts flinging before nestedScrollView
has fully scrolled to bottom. I tried overriding onNestedPreFling
and onNestedFling
in CardViewBehavior
, but it seems those two methods were never called when RecyclerView starts flinging by itself.
How can I ensure that nestedScrollView
scrolls to bottom before recyclerView
starts scrolling?
Thanks @Henry, I have successfully recreate the behaviour from the article you mentioned by @AlexLockwood in kotlin. I've dealing with this problem for two days, the other answers usually suggest using WRAP_CONTENT
attribute and basically defeat the purpose of RecyclerView
as the View
is not being recycled anymore. I'm providing the code below just in case this may help someone in the future.
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
}
}
}
Then we can use NestedScrollLayout
as follows:
<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>