NestedScrolling 在 BottomSheetDialog 内的 Viewpager 内
NestedScrolling inside a Viewpager inside a BottomSheetDialog
短版:
如何设置 NestedScrollingParent
的 NestedScrollingChild
具有多个这样的 child。
长版
我实现了一个 BottomSheetDialogFragment
,它的布局由一个 ViewPager
组成,这个 viewpager 的适配器包含一个 RecyclerView
.
现在的问题是,因为 NestedScrollingParent
这一次底部的协调器布局sheet 只支持一个 direct NestedScrollingChild
,只有适配器的第一个片段可以是nest-scrolled.
我的意思是,每当在 viewpager 上调用 setAdapter
时,第一项支持嵌套滚动。但是在我更改页面后,新页面现在不会滚动。然后回到上一页,还是支持滚动
另外我注意到,如果fragment或者可以滚动的页面被销毁,后面的页面现在可以滚动了,也就是说后面的页面变成了底部的滚动childsheet.问题是现在获得滚动能力的页面不是当前项目而是前一个项目(我的适配器必须维护 3 个片段)。
总结:
setAdapter
之后
- 片段 0 可以滚动
- 然后换页到fragment 1后,fragment 1无法滚动
- 但是切换到片段 2,然后返回片段 1 允许片段 1 滚动(我猜是因为片段 0 被破坏了)
深入研究源代码后,我发现问题出在查找底页 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>
短版:
如何设置 NestedScrollingParent
的 NestedScrollingChild
具有多个这样的 child。
长版
我实现了一个 BottomSheetDialogFragment
,它的布局由一个 ViewPager
组成,这个 viewpager 的适配器包含一个 RecyclerView
.
现在的问题是,因为 NestedScrollingParent
这一次底部的协调器布局sheet 只支持一个 direct NestedScrollingChild
,只有适配器的第一个片段可以是nest-scrolled.
我的意思是,每当在 viewpager 上调用 setAdapter
时,第一项支持嵌套滚动。但是在我更改页面后,新页面现在不会滚动。然后回到上一页,还是支持滚动
另外我注意到,如果fragment或者可以滚动的页面被销毁,后面的页面现在可以滚动了,也就是说后面的页面变成了底部的滚动childsheet.问题是现在获得滚动能力的页面不是当前项目而是前一个项目(我的适配器必须维护 3 个片段)。
总结:
setAdapter
- 片段 0 可以滚动
- 然后换页到fragment 1后,fragment 1无法滚动
- 但是切换到片段 2,然后返回片段 1 允许片段 1 滚动(我猜是因为片段 0 被破坏了)
深入研究源代码后,我发现问题出在查找底页 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
.
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>