Nestedscrollview 中的 Viewpager2 包含两个片段,Recyclerview 有问题
Viewpager2 inside Nestedscrollview consist two Fragment with Recyclerview having problem
我做了一个viewpager2
,里面有两个Fragments
,每个Fragment
里面都有一个Recyclerview
。 viewpager 本身位于 Nestedscrollview
内,以便在向上滚动时隐藏工具栏。这是我的代码:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbarlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways">
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
app:tabGravity="fill"
app:tabMode="fixed"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<include layout="@layout/material_design_floating_action_menu" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
正如我所说,viewPager2 有两个片段,每个片段都有一个 recyclerview。这里的问题是,片段 2 recyclerView 采用与片段 1 recyclerView 相同的高度,尽管两个 recyclerView 都有不同的列表项,并且它们的高度应该取决于列表项。我的意思是,我希望这些 recyclerViews 高度应该根据列表单独行动。我该如何解决这个问题?如果您需要片段代码,请告诉我。
编辑:
Activity 保存 viewPager2 的代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initToolbar();
init();
viewPager.setAdapter(createCardAdapter());
new TabLayoutMediator(tabLayout, viewPager,
new TabLayoutMediator.TabConfigurationStrategy() {
@Override public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
//tab.setText("Tab " + (position + 1));
if(position == 0){
tab.setText("Home");
}else if(position == 1){
tab.setText("Events");
}
}
}).attach();
RunnTimePermissions.requestForAllRuntimePermissions(this);
showNotifyDialog();
}
ViewPager 适配器代码:
public class ViewPagerAdapter extends FragmentStateAdapter {
private static final int CARD_ITEM_SIZE = 2;
public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
@NonNull @Override public Fragment createFragment(int position) {
switch (position){
case 0:
return HomeFragment.newInstance("abc","abc");
// break;
case 1:
return EventListFragment.newInstance("abc","abc");
// break;
}
return HomeFragment.newInstance("abc","abc");
}
@Override public int getItemCount() {
return CARD_ITEM_SIZE;
}
}
片段 1 布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragment.EventListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_event_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_marginTop="16dp"
/>
</FrameLayout>
片段 2 布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragment.MedicineListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_medicine_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_marginTop="16dp"
/>
</FrameLayout>
我不明白你为什么在 FrameLayout 中有 recyclerview。
Here problem is, fragment 2 recyclerView take the same height of
fragment 1 recyclerView though both recyclerView have different list
items and their height should be depends on the list items
RecyclerView
通常总是依赖于 item_layout
直到我们没有将其 height
n width
固定为 match_parent
.
在您的情况下,您已将 Fragment 中的 RecyclerView android:layout_width
和 android:layout_width
修复为 match_parent
。
试试这个:
片段 1
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragment.EventListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_event_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
/>
</FrameLayout>
片段 2
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragment.MedicineListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_medicine_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
/>
</FrameLayout>
P.S:假设每个 reacyclerView 的 item_layout
的高度为 wrap_content
好的伙计。我做的!我有这个问题的解决方案。
首先,这是我打开一个问题后Google的官方回复。
https://issuetracker.google.com/issues/188474850?pli=1
无论如何,继续修复。将 NestedScrollView
替换为 class:
https://gist.github.com/AfzalivE/fdce03eeee8e16203bcc37ba26d7abf3
这个想法基本上是创建一个非常 light-weight 版本的 NestedScrollView
。我们没有弄乱 child 高度,而是听取 children 的滚动并让它们滚动,或者将滚动转发到 BottomSheetBehavior
。例如,当 RecyclerView
一直滚动到顶部时,它不会消耗任何滚动,因此我们可以将其转发到底部 sheet 以便它可以滚动。我们只允许 RecyclerView
在底部 sheet 展开时滚动。
另外,我必须补充一点,这个场景与 Jetpack Compose + Accompanist-pager 开箱即用,所以如果你真的需要完美的功能,就去做吧。
class BottomSheetScrollView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs),
NestedScrollingParent2 {
private val TAG = "NestedScroll3"
private val childHelper = NestedScrollingChildHelper(this).apply {
isNestedScrollingEnabled = true
}
private var behavior: BottomSheetBehavior<*>? = null
var started = false
var canScroll = false
var pendingCanScroll = false
var dyPreScroll = 0
init {
ViewCompat.setNestedScrollingEnabled(this, true)
}
private val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
onNextScrollStop(newState == BottomSheetBehavior.STATE_EXPANDED)
Log.d(
"BottomSheet",
"Can scroll CHANGED to: $canScroll, because bottom sheet state is ${
getBottomSheetStateString(newState)
}"
)
}
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
}
fun onNextScrollStop(canScroll: Boolean) {
pendingCanScroll = canScroll
}
override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
// ViewPager2's RecyclerView does not participate in this nested scrolling.
// This allows it to show it's overscroll indicator.
if (target is RecyclerView) {
val layoutManager = target.layoutManager as LinearLayoutManager
if (layoutManager.orientation == LinearLayoutManager.HORIZONTAL) {
target.isNestedScrollingEnabled = false
}
}
if (!started) {
Log.d(TAG, "started nested scroll from $target")
childHelper.startNestedScroll(axes, type)
started = true
}
return true
}
override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) {
Log.d(TAG, "accepted nested scroll from $target")
}
override fun onStopNestedScroll(target: View, type: Int) {
if (started) {
childHelper.stopNestedScroll(type)
started = false
Log.d(
TAG,
"stopped nested scroll from $target, changing canScroll from $canScroll to $pendingCanScroll"
)
canScroll = pendingCanScroll
}
}
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int
) {
Log.d(
TAG,
"onNestedScroll: dxC: $dxConsumed, dyC: $dyConsumed, dxU: $dxUnconsumed, dyU: $dyUnconsumed"
)
if (dyUnconsumed == dyPreScroll && dyPreScroll < 0) {
canScroll = false
Log.d(TAG, "Can scroll CHANGED to: $canScroll, because scrolled to the top of the list")
}
}
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
Log.d(
TAG,
"onNestedPreScroll: dx: $dx, dy: $dy, consumed: [ ${consumed.joinToString(", ")} ]"
)
if (!canScroll) {
childHelper.dispatchNestedPreScroll(dx, dy, consumed, null, type)
// Ensure all dy is consumed to prevent premature scrolling when not allowed.
consumed[1] = dy
} else {
dyPreScroll = dy
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
behavior = findBottomSheetBehaviorParent(parent) as BottomSheetBehavior<*>?
behavior?.addBottomSheetCallback(bottomSheetCallback)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
behavior?.removeBottomSheetCallback(bottomSheetCallback)
}
private fun findBottomSheetBehaviorParent(parent: ViewParent?): CoordinatorLayout.Behavior<*>? {
if (parent !is View) {
throw IllegalArgumentException(
"None of this view's ancestors are associated with BottomSheetBehavior"
)
}
val layoutParams = parent.layoutParams
return if (layoutParams is CoordinatorLayout.LayoutParams && layoutParams.behavior != null) {
layoutParams.behavior
} else {
findBottomSheetBehaviorParent((parent as View).parent)
}
}
private fun getBottomSheetStateString(state: Int): String {
return when (state) {
1 -> "STATE_DRAGGING"
2 -> "STATE_SETTLING"
3 -> "STATE_EXPANDED"
4 -> "STATE_COLLAPSED"
5 -> "STATE_HIDDEN"
6 -> "STATE_HALF_EXPANDED"
else -> "0"
}
}
}
我做了一个viewpager2
,里面有两个Fragments
,每个Fragment
里面都有一个Recyclerview
。 viewpager 本身位于 Nestedscrollview
内,以便在向上滚动时隐藏工具栏。这是我的代码:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbarlayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways">
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
app:tabGravity="fill"
app:tabMode="fixed"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<include layout="@layout/material_design_floating_action_menu" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
正如我所说,viewPager2 有两个片段,每个片段都有一个 recyclerview。这里的问题是,片段 2 recyclerView 采用与片段 1 recyclerView 相同的高度,尽管两个 recyclerView 都有不同的列表项,并且它们的高度应该取决于列表项。我的意思是,我希望这些 recyclerViews 高度应该根据列表单独行动。我该如何解决这个问题?如果您需要片段代码,请告诉我。
编辑: Activity 保存 viewPager2 的代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initToolbar();
init();
viewPager.setAdapter(createCardAdapter());
new TabLayoutMediator(tabLayout, viewPager,
new TabLayoutMediator.TabConfigurationStrategy() {
@Override public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
//tab.setText("Tab " + (position + 1));
if(position == 0){
tab.setText("Home");
}else if(position == 1){
tab.setText("Events");
}
}
}).attach();
RunnTimePermissions.requestForAllRuntimePermissions(this);
showNotifyDialog();
}
ViewPager 适配器代码:
public class ViewPagerAdapter extends FragmentStateAdapter {
private static final int CARD_ITEM_SIZE = 2;
public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
@NonNull @Override public Fragment createFragment(int position) {
switch (position){
case 0:
return HomeFragment.newInstance("abc","abc");
// break;
case 1:
return EventListFragment.newInstance("abc","abc");
// break;
}
return HomeFragment.newInstance("abc","abc");
}
@Override public int getItemCount() {
return CARD_ITEM_SIZE;
}
}
片段 1 布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragment.EventListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_event_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_marginTop="16dp"
/>
</FrameLayout>
片段 2 布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragment.MedicineListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_medicine_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_marginTop="16dp"
/>
</FrameLayout>
我不明白你为什么在 FrameLayout 中有 recyclerview。
Here problem is, fragment 2 recyclerView take the same height of fragment 1 recyclerView though both recyclerView have different list items and their height should be depends on the list items
RecyclerView
通常总是依赖于 item_layout
直到我们没有将其 height
n width
固定为 match_parent
.
在您的情况下,您已将 Fragment 中的 RecyclerView android:layout_width
和 android:layout_width
修复为 match_parent
。
试试这个:
片段 1
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragment.EventListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_event_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
/>
</FrameLayout>
片段 2
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragment.MedicineListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_medicine_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
/>
</FrameLayout>
P.S:假设每个 reacyclerView 的 item_layout
的高度为 wrap_content
好的伙计。我做的!我有这个问题的解决方案。
首先,这是我打开一个问题后Google的官方回复。 https://issuetracker.google.com/issues/188474850?pli=1
无论如何,继续修复。将 NestedScrollView
替换为 class:
https://gist.github.com/AfzalivE/fdce03eeee8e16203bcc37ba26d7abf3
这个想法基本上是创建一个非常 light-weight 版本的 NestedScrollView
。我们没有弄乱 child 高度,而是听取 children 的滚动并让它们滚动,或者将滚动转发到 BottomSheetBehavior
。例如,当 RecyclerView
一直滚动到顶部时,它不会消耗任何滚动,因此我们可以将其转发到底部 sheet 以便它可以滚动。我们只允许 RecyclerView
在底部 sheet 展开时滚动。
另外,我必须补充一点,这个场景与 Jetpack Compose + Accompanist-pager 开箱即用,所以如果你真的需要完美的功能,就去做吧。
class BottomSheetScrollView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs),
NestedScrollingParent2 {
private val TAG = "NestedScroll3"
private val childHelper = NestedScrollingChildHelper(this).apply {
isNestedScrollingEnabled = true
}
private var behavior: BottomSheetBehavior<*>? = null
var started = false
var canScroll = false
var pendingCanScroll = false
var dyPreScroll = 0
init {
ViewCompat.setNestedScrollingEnabled(this, true)
}
private val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
onNextScrollStop(newState == BottomSheetBehavior.STATE_EXPANDED)
Log.d(
"BottomSheet",
"Can scroll CHANGED to: $canScroll, because bottom sheet state is ${
getBottomSheetStateString(newState)
}"
)
}
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
}
fun onNextScrollStop(canScroll: Boolean) {
pendingCanScroll = canScroll
}
override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
// ViewPager2's RecyclerView does not participate in this nested scrolling.
// This allows it to show it's overscroll indicator.
if (target is RecyclerView) {
val layoutManager = target.layoutManager as LinearLayoutManager
if (layoutManager.orientation == LinearLayoutManager.HORIZONTAL) {
target.isNestedScrollingEnabled = false
}
}
if (!started) {
Log.d(TAG, "started nested scroll from $target")
childHelper.startNestedScroll(axes, type)
started = true
}
return true
}
override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) {
Log.d(TAG, "accepted nested scroll from $target")
}
override fun onStopNestedScroll(target: View, type: Int) {
if (started) {
childHelper.stopNestedScroll(type)
started = false
Log.d(
TAG,
"stopped nested scroll from $target, changing canScroll from $canScroll to $pendingCanScroll"
)
canScroll = pendingCanScroll
}
}
override fun onNestedScroll(
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int,
type: Int
) {
Log.d(
TAG,
"onNestedScroll: dxC: $dxConsumed, dyC: $dyConsumed, dxU: $dxUnconsumed, dyU: $dyUnconsumed"
)
if (dyUnconsumed == dyPreScroll && dyPreScroll < 0) {
canScroll = false
Log.d(TAG, "Can scroll CHANGED to: $canScroll, because scrolled to the top of the list")
}
}
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
Log.d(
TAG,
"onNestedPreScroll: dx: $dx, dy: $dy, consumed: [ ${consumed.joinToString(", ")} ]"
)
if (!canScroll) {
childHelper.dispatchNestedPreScroll(dx, dy, consumed, null, type)
// Ensure all dy is consumed to prevent premature scrolling when not allowed.
consumed[1] = dy
} else {
dyPreScroll = dy
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
behavior = findBottomSheetBehaviorParent(parent) as BottomSheetBehavior<*>?
behavior?.addBottomSheetCallback(bottomSheetCallback)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
behavior?.removeBottomSheetCallback(bottomSheetCallback)
}
private fun findBottomSheetBehaviorParent(parent: ViewParent?): CoordinatorLayout.Behavior<*>? {
if (parent !is View) {
throw IllegalArgumentException(
"None of this view's ancestors are associated with BottomSheetBehavior"
)
}
val layoutParams = parent.layoutParams
return if (layoutParams is CoordinatorLayout.LayoutParams && layoutParams.behavior != null) {
layoutParams.behavior
} else {
findBottomSheetBehaviorParent((parent as View).parent)
}
}
private fun getBottomSheetStateString(state: Int): String {
return when (state) {
1 -> "STATE_DRAGGING"
2 -> "STATE_SETTLING"
3 -> "STATE_EXPANDED"
4 -> "STATE_COLLAPSED"
5 -> "STATE_HIDDEN"
6 -> "STATE_HALF_EXPANDED"
else -> "0"
}
}
}