AppBar 不使用嵌套的 ViewPager2 滚动

AppBar not scrolling with nested ViewPager2

我有一个视图层次结构,如下图所示。

我遇到奇怪的滚动行为,例如,

  1. 如果我从区域 1 滚动(缓慢拖动或猛击),AppBar 会随之折叠。这很好。
  2. 但是,如果我从区域 2 缓慢拖动,AppBar 不会折叠。它留在那里,RecyclerView 在它下面。但是,它可以正常使用。

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>

我已经尝试过其他问题的解决方案,比如用 NestedScrollableHost class 包装嵌套的 ViewPager2。但它似乎没有用。有什么想法吗?

要解决此问题,您需要执行几个步骤:

  1. 把外面的ViewPager2包在一个NestedScrollView里,当然把滚动行为也传给它了:

    所以在 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>
    
  2. 禁用两个 ViewPagers 的内部 RecyclerView 的嵌套滚动:并且由于它不可访问,您可以使用 java 反射使 RecyclerView 可通过其在 ViewPager2 class:

    中的字段定义访问

    科特林:

    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);

预览:

  • 黑色区域是AppBarLayout
  • 灰色区域是ViewPager2
  • 紫色区域为ViewPager

更新:

感谢@Ankur Gupta 和@SimpleAndroid:

而不是反射来获取 ViewPager2RecyclerView,并相应地禁用嵌套滚动:

viewPager.children.find { it is RecyclerView }?.let {
        (it as RecyclerView).isNestedScrollingEnabled = false
}