NestedScrolling 在 BottomSheetDialog 内的 Viewpager 内

NestedScrolling inside a Viewpager inside a BottomSheetDialog

短版:

如何设置 NestedScrollingParentNestedScrollingChild 具有多个这样的 child。

长版

我实现了一个 BottomSheetDialogFragment,它的布局由一个 ViewPager 组成,这个 viewpager 的适配器包含一个 RecyclerView.

现在的问题是,因为 NestedScrollingParent 这一次底部的协调器布局sheet 只支持一个 direct NestedScrollingChild,只有适配器的第一个片段可以是nest-scrolled.

我的意思是,每当在 viewpager 上调用 setAdapter 时,第一项支持嵌套滚动。但是在我更改页面后,新页面现在不会滚动。然后回到上一页,还是支持滚动

另外我注意到,如果fragment或者可以滚动的页面被销毁,后面的页面现在可以滚动了,也就是说后面的页面变成了底部的滚动childsheet.问题是现在获得滚动能力的页面不是当前项目而是前一个项目(我的适配器必须维护 3 个片段)。

总结:

setAdapter

之后

深入研究源代码后,我发现问题出在查找底页 NestedScrollingChild 时使用的错误算法(Google 的人没有考虑到a ViewPager inside the bottomsheet).

方法见这里:findScrollingChild()

此方法的作用是 return 它在给定视图(本例中为底页)上遇到的第一个 NestedScrollingChild,对于具有 3 个页面的 viewpager,当前页面之前的一个。此外,此方法在 bottomsheet 的 CoordinatorLayout 包装器的子项的布局阶段触发。

考虑到这一点,可以设计出许多解决方案,包括对行为本身进行子类化。

此外,可以通过添加和删除此类子项的一个实例(从旧页面删除,然后添加到当前页面)来限制 viewpager 内的 NestedScrollingChild,这就是我所做的。您可以在适配器 setPrimaryItem 内或在 OnPageChangeListener 上执行此操作。请务必在 bottomsheet 的协调器布局上调用 requestLayout。 (此解决方案取决于寻呼机适配器的类型 layout/structure,因此我不会 post 我的确切解决方案)。

我也 运行 遇到了这个问题,试图在 BottomSheetDialog 中的几乎每个页面中使用带有滚动视图的 ViewPager。 我发现这个解决方案是由 laenger (https://github.com/laenger/ViewPagerBottomSheet) 提出的,并采用了它,因此它适合 BottomSheetDialog。 希望它可以帮助一些遇到同样问题的人:)

我遇到了完全相同的问题并找到了非常相似的解决方案。
提供我的解决方案供参考:

ViewPagerAdapter.java

....
@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    super.setPrimaryItem(container, position, object);
    NestedScrollView current = ((NestedScrollView)object);
    current.setNestedScrollingEnabled(true);

    for (int i = 0; i < getCount(); i++) {
        if (i != position) {
            NestedScrollView otherScrollView = container.findViewWithTag(i);
            otherScrollView.setNestedScrollingEnabled(false);
        }
    }

    container.requestLayout();
}
...

另外,我写了一篇关于这个主题的博客 post:Cannot scroll scrollable content inside ViewPager as BottomSheet of CoordinatorLayout

我稍微简化了 saiday's 解决方案。重要的是要知道我返回的是 ViewBindings 而不是来自 instantiateItem.

的 Views
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    super.setPrimaryItem(container, position, object);
    for (int i = 0; i < container.getChildCount(); i++) {
        //First disable nested scrolling for all instantiated child views
        ((NestedScrollView)container.getChildAt(i)).setNestedScrollingEnabled(false);
    }

    //Enable nested scrolling for the primary item
    ViewDataBinding binding = (ViewDataBinding) object;
    NestedScrollView current = ((NestedScrollView)binding.getRoot());
    current.setNestedScrollingEnabled(true);

    container.requestLayout();
}

我对之前的回复有些疑惑。 事实上,在我的例子中,setPrimaryItem 方法 return 中的 @NonNull Object object 是一个片段,不能转换为 NestedScrollView.

这是我 XML 中的内容:

  • 一个 BottomSheet 包含一个 ViewPager
  • ViewPager 分两页,每页 Fragment
  • 每个 Fragment 在根视图中包含一个 NestedScrollView

这是我的 ViewPagerAdapter (Kotlin)

class MyViewPagerAdapter(val id: String) :
    FragmentStatePagerAdapter(supportFragmentManager) {

    val titles = arrayOf(getString(R.string.title1),
        getString(R.string.title2))

    override fun getItem(position: Int) = when (position) {
        0 -> MyFragment1.newInstance(id)
        1 -> MyFragment2.newInstance(id)
        else -> Fragment()
    }

    override fun getCount() = 2

    override fun getPageTitle(position: Int): CharSequence? {
        return titles[position]
    }

    override fun setPrimaryItem(container: ViewGroup, position: Int, `object`: Any) {
        super.setPrimaryItem(container, position, `object`)

        val currentFragment = `object` as Fragment

        if (currentFragment.view != null) { //The first time the view isn't created yet
            for (i in 0 until count) {
                (container.getChildAt(i) as NestedScrollView).isNestedScrollingEnabled = false
            }

            val currentNestedScrollView: NestedScrollView = currentFragment.view as NestedScrollView
            currentNestedScrollView.isNestedScrollingEnabled = true

            container.requestLayout()
        }
    }
}

使用 Androidx 库时,RecyclerView 实现 NestedScrollingChild 而不是 NestedScrollView

override fun setPrimaryItem(container: ViewGroup, position: Int, `object`: Any) {
        super.setPrimaryItem(container, position, `object`)

        val currentFragment = `object` as Fragment

        if (currentFragment.view != null) {
            for (i in 0 until count) {
                (container.getChildAt(i) as? NestedScrollingChild)?.isNestedScrollingEnabled = false
            }

            val currentNestedScrollView: NestedScrollingChild = currentFragment.view as NestedScrollingChild
            currentNestedScrollView.isNestedScrollingEnabled = true

            container.requestLayout()
        }
    }

除了@saiday 的回答之外,这些代码对我来说非常有效(API 21 岁及以上);

Here what I have in my XML :

One BottomSheet containing a ViewPager.

The ViewPager with two pages containing one Fragment each.

Each Fragment Containing one NestedScrollView at the root view.

和我的 ViewPagerAdapter 代码 (java):

@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    super.setPrimaryItem(container, position, object);

    Fragment currentFragment = (Fragment) object;
    if (currentFragment.getView() != null) {
        for (int i = 0; i < getCount(); i++) {
            ((NestedScrollView) container.getChildAt(i)).setNestedScrollingEnabled(false);
            ((NestedScrollView) container.getChildAt(i)).getChildAt(0).setNestedScrollingEnabled(false); //this is recyclerview's nestedscroll
        }

        NestedScrollView currentNestedScrollView = ((NestedScrollView) currentFragment.getView());
        currentNestedScrollView.setNestedScrollingEnabled(true);
        currentNestedScrollView.getChildAt(0).setNestedScrollingEnabled(true); //this is recyclerview's nestedscroll, I used getChildAt(0) because I have one element in NestedScrollView.

        container.requestLayout();
    }
}

我的第一个和第二个布局(只是ID不同):

<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/marketMembersFragmentScrollView"
    android:fillViewport="true">
    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/marketMembersFragmentRecyclerView"/>
</androidx.core.widget.NestedScrollView>