具有 recyclerview 和 tablayout 的动态高度查看器

Posted

技术标签:

【中文标题】具有 recyclerview 和 tablayout 的动态高度查看器【英文标题】:Dynamic height viewpager with recyclerview and tablayout 【发布时间】:2021-03-10 02:02:00 【问题描述】:

我有一个 3 页的 viewpager,每个页面可能包含也可能不包含 recyclerview。 recyclerview 中的项目数量因用户而异。我在这个问题之后制作了一个自定义视图寻呼机: How to wrap the height of a ViewPager to the height of its current Fragment?。 我现在面临的问题有两点:

1. 在切换选项卡时,会为每个片段调用 requestLayout()。所以当recyclerview包含很多项目时,切换时会有延迟,因为它是一次又一次地测量选项卡。并且 recyclerview 中的项目是动态的,这意味着它在滚动行为上有加载更多项目。

2. 当recyclerview 没有item 时,会显示一个textview 来指示没有item。因为 viewpager 包装了内容,所以我似乎无法将 textview 在片段中居中。所以当没有recyclerview时,我需要viewpager填满整个高度。

这是我的 CustomViewPager 代码:


public class CustomViewPager extends ViewPager 

    private int currentPagePosition = 0;
    Context context;

    public CustomViewPager(@NonNull Context context) 
        super(context);
        this.context = context;
    

    public CustomViewPager(@NonNull Context context, @Nullable AttributeSet attrs) 
        super(context, attrs);
        this.context = context;
    

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        setMeasuredDimension(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        View child = getChildAt(currentPagePosition);
        if(child != null) 
            child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
            int h = child.getMeasuredHeight();
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
        
        if(heightMeasureSpec != 0) 
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        
    

    public void measureCurrentView(int position) 
        currentPagePosition = position;
        requestLayout();
    


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) 
        return false;
    

    @Override
    public boolean onTouchEvent(MotionEvent ev) 
        return false;
    


这是我的 viewpager 布局文件:


<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_
    android:layout_
    xmlns:app="http://schemas.android.com/apk/res-auto">
    
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_
        android:layout_>
        
        <RelativeLayout
            android:layout_
            android:layout_
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:background="#F8F8F8"
            android:id="@+id/header"/>

        <com.google.android.material.tabs.TabLayout
            android:layout_
            android:layout_
            app:layout_constraintTop_toBottomOf="@id/header"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:id="@+id/tabs">

            <com.google.android.material.tabs.TabItem
                android:layout_
                android:layout_
                android:text="1ST"
                />
            <com.google.android.material.tabs.TabItem
                android:layout_
                android:layout_
                android:text="2ND"
                />
            <com.google.android.material.tabs.TabItem
                android:layout_
                android:layout_
                android:text="3RD"
                />


        </com.google.android.material.tabs.TabLayout>

        <com.example.smilee.adapters.CustomViewPager
            android:layout_
            android:layout_
            android:id="@+id/items"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tabs"

            />
        
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.core.widget.NestedScrollView> 

这是每个片段的布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_
    android:layout_
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@android:color/white">

    <androidx.recyclerview.widget.RecyclerView
        android:layout_
        android:layout_
        android:id="@+id/recyclerview"
        android:nestedScrollingEnabled="false"
        android:visibility="gone"/>


    <TextView
        android:layout_
        android:layout_
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:text="No items to show."
        android:fontFamily="@font/medium"
        android:textColor="#777"
        android:textSize="15dp"
        android:gravity="center"
        android:id="@+id/noItemText"
        android:includeFontPadding="false"
        android:visibility="visible"/>


</androidx.constraintlayout.widget.ConstraintLayout>


如何做到这一点? 无论recyclerview中的项目数如何,如何快速切换选项卡,如果recyclerview不可用,如何通过填充整个高度来居中textview?希望你能帮忙。问候。

【问题讨论】:

您考虑过使用 ViewPager2 吗?它应该与 RecyclerView 一起玩得更好,因为垂直 RecyclerView(ViewPager2 基于 RecyclerView)内的水平 RecyclerView 是一种常见模式,它旨在处理这种情况。 但是它会根据内容改变它的高度吗? 我不确定你的意思。一些图表可能会有所帮助 我的意思是,viewpager 的高度会根据其子项(动态高度)而变化。假设我有 3 个标签。第一个选项卡可能包含一个 recyclerview 和 1000 个项目。第二个可能包含一个 recyclerview 和只有 3 个项目。因此,当我从第一个选项卡切换到第二个选项卡时,第二个选项卡会占用包含 1000 个项目的第一个选项卡的高度,还是只会占用包含 3 个项目的回收视图的高度?希望你能理解。 哦,我明白了。为什么你有一个嵌套的滚动视图?在滚动视图中使用 RecyclerView 通常是个坏主意,因为它会完全禁用所有回收(它必须立即布置列表中的每个项目),因此如果您有多个垂直页面的项目,可能会导致您滞后。在上面的代码中,滚动视图似乎不是很有用 【参考方案1】:

您的主要问题是将 RecyclerView 嵌套在 ScrollView 中。这很容易导致性能问题,因为 RecyclerView 不知道它自己的哪一部分当前在屏幕上,所以它一次加载所有项目。添加打印登录onBindViewHolder 以确认这一点。这也意味着 RecyclerView 的高度(以及 viewpager 的高度)现在是任意的,并且不限于屏幕的大小,这将使您的文本无法居中。

为了实现你想要的布局,我会将 NestedScrollView 替换为 CoordinatorLayout。对于我认为完全相同的情况,我已经在我的应用程序中成功地做到了这一点。那么您将不再需要自定义 ViewPager。

这是一个使用您的布局的经过测试的工作示例:

 <?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_
    android:layout_>

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/app_layout"
        android:layout_
        android:layout_>

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_
            android:layout_
            app:layout_scrollFlags="scroll|enterAlways">

            <RelativeLayout
                android:id="@+id/header"
                android:layout_
                android:layout_
                android:background="#F8F8F8"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <com.google.android.material.tabs.TabLayout
                android:id="@+id/tabs"
                android:layout_
                android:layout_
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/header">

            </com.google.android.material.tabs.TabLayout>
        </androidx.constraintlayout.widget.ConstraintLayout>
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/items"
        android:layout_
        android:layout_
        app:layout_anchor="@id/app_layout"
        app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

请注意,您必须从 RecyclerView 中删除此行: android:nestedScrollingEnabled="false"

【讨论】:

谢谢。我还没有使用协调器布局。将研究并让您知道它是否有效。问候。 你能给我一个简单的演示代码吗?我不能让它工作。 对不起,我不能让它工作。我在约束布局内放了一个回收视图,它在固定位置滚动。你能做这样的布局吗: CoordinatorLayout -> ConstraintLayout -> RelativeLayout -> TabLayout -> ViewPager viewpager 必须在约束布局内,在 AppBarLayout 下方 是的,我也是这样做的。但没有帮助。我没有在布局中同时使用 appbarlayout 和工具栏,因为这不是必需的。但是我试过了,还是不行。【参考方案2】:

这是工作的 XML 代码。您可以尝试使用此代码。添加此属性使 NestedScrollView 内容全高

android:fillViewport="true"

这是您修改后的主要布局 XML 代码

<androidx.core.widget.NestedScrollView
            android:layout_
            android:layout_
            android:fillViewport="true">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_
                android:layout_>

                <RelativeLayout
                    android:layout_
                    android:layout_
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    android:background="#F8F8F8"
                    android:id="@+id/header"/>

                <com.google.android.material.tabs.TabLayout
                    android:layout_
                    android:layout_
                    app:layout_constraintTop_toBottomOf="@id/header"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    android:id="@+id/tabs">

                    <com.google.android.material.tabs.TabItem
                        android:layout_
                        android:layout_
                        android:text="1ST"
                        />
                    <com.google.android.material.tabs.TabItem
                        android:layout_
                        android:layout_
                        android:text="2ND"
                        />
                    <com.google.android.material.tabs.TabItem
                        android:layout_
                        android:layout_
                        android:text="3RD"
                        />


                </com.google.android.material.tabs.TabLayout>

                <androidx.viewpager.widget.ViewPager
                    android:id="@+id/items"
                    android:layout_
                    android:layout_
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toBottomOf="@id/tabs"
                    app:layout_constraintBottom_toBottomOf="parent" />

            </androidx.constraintlayout.widget.ConstraintLayout>

        </androidx.core.widget.NestedScrollView>

【讨论】:

recyclerview 仍处于固定状态。我需要滚动它。 请确保您的 recyclerview android:layout_ android:nestedScrollingEnabled="false" 的 XML 代码中的这两行,您可以关注此***.com/a/36977637/9158688

以上是关于具有 recyclerview 和 tablayout 的动态高度查看器的主要内容,如果未能解决你的问题,请参考以下文章

具有垂直和水平滚动的 Recyclerview

如何将 DiffUtil.Callback 与具有页眉和页脚的 RecyclerView 一起使用?

具有水平滚动的嵌套 RecyclerView 中的滚动行为

具有 RecyclerView 的页面中 Persistent 或 Standard BottomSheet 的奇怪滚动行为和可见性

如何在具有多个edittext的recyclerview中验证和设置错误?

RecyclerView 不会删除项目 - 尽管列表具有正确的项目编号