I have a view hierarchy as shown in the image below.
I'm getting strange scroll behaviors like,
AppBar
collapses along with it. This is fine.AppBar
does not collapse. It stays there and RecyclerView
goes beneath it. However, it works fine with a fling.activity_challenge_detail.xml
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".challengedetail.ChallengeDetailActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/black">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentScrim="@color/black"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.2">
<FrameLayout
android:id="@+id/challengeBannerFrame"
android:layout_width="match_parent"
android:layout_height="0dp"
android:foreground="@drawable/banner_gradient"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/challengeBanner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/challenge_banner"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.Toolbar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="@dimen/dp16"
app:layout_collapseMode="pin">
<com.company.widget.StatusBarSpacer
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
app:contentInsetEnd="0dp"
app:contentInsetStart="0dp"
app:layout_collapseMode="pin">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent">
<com.google.android.material.tabs.TabLayout
android:id="@+id/switchingTabsBar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp0"
android:background="@drawable/switching_tab_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tabBackground="@drawable/active_tab_selector"
app:tabIconTint="@color/black"
app:tabIndicator="@drawable/active_tab_indicator"
app:tabIndicatorColor="@color/yellow_500"
app:tabMode="fixed"
app:tabRippleColor="@null" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/challengeDetailsViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
fragment_challenge_post.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/gradient_challenge_post"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".challengedetail.fragment.ChallengePostFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/challengePostRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:itemCount="1"
tools:listitem="@layout/list_item_post" />
</androidx.constraintlayout.widget.ConstraintLayout>
list_item_post.xml
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="@dimen/dp16"
app:cardElevation="@dimen/dp0"
app:strokeColor="@color/gray_f5"
app:strokeWidth="@dimen/dp1">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/dp16">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/userImageView"
android:layout_width="@dimen/dp48"
android:layout_height="@dimen/dp48"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shapeAppearanceOverlay="@style/ShapeAppearance.userProfileImage"
tools:src="@tools:sample/avatars" />
<TextView
android:id="@+id/userNameText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:lineSpacingExtra="5sp"
android:textAppearance="@style/Inter.Semi.16"
app:layout_constraintStart_toEndOf="@+id/userImageView"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/full_names" />
<TextView
android:id="@+id/timestampText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:lineSpacingExtra="7sp"
android:textAppearance="@style/Inter.Regular.14"
app:layout_constraintStart_toEndOf="@+id/userImageView"
app:layout_constraintTop_toBottomOf="@+id/userNameText"
tools:text="2 hrs ago" />
<com.company.widget.NestedScrollableHost
android:id="@+id/viewPagerHost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/userImageView"
tools:layout_constraintDimensionRatio="1:1">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/postImagesViewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.company.widget.NestedScrollableHost>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
I have tried solutions to other questions as well like wrapping the nested ViewPager2
by NestedScrollableHost class. But it did not seem to work. Any ideas?
To fix this you need a couple of steps:
Wrap the outer ViewPager2
in a NestedScrollView
, and of course transfer the scrolling behavior to it:
So in activity_challenge_detail.xml
:
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/challengeDetailsViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent />
</androidx.core.widget.NestedScrollView>
Disable the nested scrolling of the internal RecyclerView
of both ViewPagers
: and as it's not accessible, you can use java reflections to make that RecyclerView
accessible through its field definition in the ViewPager2
class:
Kotlin:
fun ViewPager2.getRecyclerView(): RecyclerView? {
try {
val field = ViewPager2::class.java.getDeclaredField("mRecyclerView")
field.isAccessible = true
return field.get(this) as RecyclerView
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
return null
}
val recyclerView = viewPager.getRecyclerView()
recyclerView?.isNestedScrollingEnabled = false
Java
public static RecyclerView getRecyclerView(ViewPager2 viewPager) {
try {
Field field = ViewPager2.class.getDeclaredField("mRecyclerView");
field.setAccessible(true);
return (RecyclerView) field.get(viewPager);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
RecyclerView recyclerView = getRecyclerView(viewPager);
if (recyclerView != null)
recyclerView.setNestedScrollingEnabled(false);
Preview:
AppBarLayout
ViewPager2
ViewPager
pagesUPDATE:
Thanks @Ankur Gupta & @SimpleAndroid:
There is a nice trick instead of reflections to get the RecyclerView
of the ViewPager2
, and disable the nested scrolling accordingly:
viewPager.children.find { it is RecyclerView }?.let {
(it as RecyclerView).isNestedScrollingEnabled = false
}