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_widthandroid: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"
        }
    }
}