NestedScrollView嵌套RecyclerView导致无法复用问题

前段时间,在和同事合作的时候看到了一段代码,scrollview 嵌套 recyclerview,然后adapter中没有处理view的复用,然后我就开始好奇这里不会出现bug吗?不过之前写代码的时候一直没注意过这种嵌套,那么,先实验一下。

activity_main.xml

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv_title"
                android:layout_width="match_parent"
                android:layout_height="110dp"
                android:background="#EEE"
                android:gravity="center"
                android:text="textview" />

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recycler"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
        </LinearLayout>
    </androidx.core.widget.NestedScrollView>

MyAdapter.java

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyccler, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        Log.d("VODD", "item: " + position);
        holder.tvItem.setText(String.format("Item:%1$d", position));
    }

    @Override
    public int getItemCount() {
        return 20;
    }

    protected static class MyViewHolder extends RecyclerView.ViewHolder {

        TextView tvItem;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            tvItem = itemView.findViewById(R.id.tv_item);
        }
    }
}

代码贴在上面了,然后看log日志,毫不意外的发现,所有的view都在第一时间绘制出来了,包括屏幕外面的,因此recyclerview 完全没有复用呵呵。

2021-08-28 17:12:15.646 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 0
2021-08-28 17:12:15.649 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 1
2021-08-28 17:12:15.651 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 2
2021-08-28 17:12:15.653 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 3
2021-08-28 17:12:15.655 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 4
2021-08-28 17:12:15.658 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 5
2021-08-28 17:12:15.660 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 6
2021-08-28 17:12:15.662 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 7
2021-08-28 17:12:15.664 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 8
2021-08-28 17:12:15.666 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 9
2021-08-28 17:12:15.668 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 10
2021-08-28 17:12:15.671 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 11
2021-08-28 17:12:15.673 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 12
2021-08-28 17:12:15.675 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 13
2021-08-28 17:12:15.677 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 14
2021-08-28 17:12:15.679 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 15
2021-08-28 17:12:15.681 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 16
2021-08-28 17:12:15.683 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 17
2021-08-28 17:12:15.686 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 18
2021-08-28 17:12:15.688 20497-20497/com.vodlee.nestedscrollrecycler D/VODD: item: 19

那么这个地方为什么没有进行复用?我先把recyclerView 的 onMeasure 方法打印一下看看

2021-08-28 17:18:44.308 23468-23468/com.vodlee.nestedscrollrecycler D/VODD: widthSpec: 1073742904
2021-08-28 17:18:44.308 23468-23468/com.vodlee.nestedscrollrecycler D/VODD: heightSpec: 0
2021-08-28 17:18:44.318 23468-23468/com.vodlee.nestedscrollrecycler D/VODD: widthSpec: 1073742904
2021-08-28 17:18:44.318 23468-23468/com.vodlee.nestedscrollrecycler D/VODD: heightSpec: 0

通过log,发现 heightSpec 竟然是0?

为什么会是0呢,咱们看 MeasureSpec 的代码

        private static final int MODE_SHIFT = 30;
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        public static final int AT_MOST     = 2 << MODE_SHIFT;
        
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

也就是只有父布局测量子布局时,传递的 mode 为 UNSPECIFIED 并且 size 为0的时候,才会出现 heightSpec 是0的情况。

而其他情况下,recyclerView 高度为固定值、match_parent 或 wrap_content 的时候,都不会出现0的情况。

这样,就会导致 recyclerView 无法得知当前屏幕展示多少个就够了。


那么问题如何解决?

方案一:通过 getItemViewType(int position) 方法,在 RecyclerView 中创建不同的 viewholder


    @Override
    public int getItemViewType(int position) {
        return position == 0 ? 0 : 1;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == 0) {
            return new HeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyccler, parent, false));
        }
        return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyccler, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Log.d("VODD", "item: " + position);
        if (getItemViewType(position) == 0) {
            ((HeaderViewHolder)holder).tvItem.setText("This is Header.");
        } else {
            ((MyViewHolder)holder).tvItem.setText(String.format("Item:%1$d", position));
        }
    }

Log日志如下

2021-08-28 23:22:07.298 13234-13234/com.vodlee.nestedscrollrecycler D/VODD: item: 0
2021-08-28 23:22:07.302 13234-13234/com.vodlee.nestedscrollrecycler D/VODD: item: 1
2021-08-28 23:22:07.304 13234-13234/com.vodlee.nestedscrollrecycler D/VODD: item: 2
2021-08-28 23:22:07.306 13234-13234/com.vodlee.nestedscrollrecycler D/VODD: item: 3
2021-08-28 23:22:07.308 13234-13234/com.vodlee.nestedscrollrecycler D/VODD: item: 4

方案二:通过 CoordinatorLayout + AppBarLayout 方法实现顶部布局


    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="100dp"
                android:background="#CCC"
                android:gravity="center"
                android:text="This is Header"
                app:layout_scrollFlags="scroll" />

        </com.google.android.material.appbar.AppBarLayout>

        <com.vodlee.nestedscrollrecycler.MyRecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />
    </androidx.coordinatorlayout.widget.CoordinatorLayout>

Log日志如下

2021-08-28 23:26:17.478 13973-13973/com.vodlee.nestedscrollrecycler D/VODD: item: 0
2021-08-28 23:26:17.481 13973-13973/com.vodlee.nestedscrollrecycler D/VODD: item: 1
2021-08-28 23:26:17.482 13973-13973/com.vodlee.nestedscrollrecycler D/VODD: item: 2
2021-08-28 23:26:17.484 13973-13973/com.vodlee.nestedscrollrecycler D/VODD: item: 3
2021-08-28 23:26:17.486 13973-13973/com.vodlee.nestedscrollrecycler D/VODD: item: 4
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在使用 AndroidNestedScrollView 嵌套 RecyclerView 的过程中,出现了数据无法复用缓存的问题,这是因为 RecyclerView复用机制并不适用于 NestedScrollView 中的嵌套情况。 NestedScrollView 中包含的 RecyclerView 实际上是作为一个子View 的形式出现的,而每一个子View 的布局和数据都是不同的,所以 RecyclerView 无法反复利用之前已经使用过的缓存。这会导致在滑动过程中不断创建新的ViewHolder,从而降低应用程序的性能。 为了解决这个问题,可以将 RecyclerView 操作放到调用 NestedScrollView 的 onScrollChanged() 方法里面,以确保它的缓存机制被正确使用。另外,为RecyclerView设置setHasFixedSize(true)属性可以使性能有一些提升。 最好的解决方案是使用单个 RecyclerView 以及多个 Adapter 对其进行管理。这种方法可以让您更好地控制与管理 RecyclerView 的数据,在保证 NestedScrollView 能够适当地管理视图树的同时,确保 RecyclerView 没有内存和性能问题。 ### 回答2: Android中,RecyclerView是一个非常高效的列表控件,它可以使用ViewHolder机制对视图进行缓存,减少频繁更新视图的开销,但是在使用NestedScrollView嵌套RecyclerView时,却会出现数据无法复用缓存的问题。 这是因为NestedScrollView默认会将RecyclerView的所有子视图都展开,导致RecyclerView中所有的子视图都处于可见状态,无法被缓存。因此,当列表滑动时,RecyclerView会重新实例化缓存的ViewHolder,以展示新的子视图。这个过程会消耗大量的内存和CPU,导致RecyclerView变得非常缓慢,甚至会导致OOM崩溃等问题。 为了解决这个问题,我们需要使用一些技巧来保证RecyclerView的子视图只有在需要的时候才被展开。一种解决方案是通过设置NestedScrollView的fillViewport属性为false,使NestedScrollView不将RecyclerView的所有子视图都展开。这样做可以让RecyclerView正确地使用ViewHolder机制,但是在滑动过程中,子视图的高度会频繁变化,导致列表的抖动,用户体验也会受到一定的影响。 另一种解决方案是使用RecyclerView的setRecycledViewPool()方法,为RecyclerView缓存一个视图池,这个视图池可以在RecyclerView的所有嵌套层级间共享,让所有子视图都可以被重复使用。这个方案的优点是可以减少在滑动过程中子视图高度变化带来的问题,但是需要在代码中进行额外的编写。 总之,NestedScrollView嵌套RecyclerView在使用时要注意子视图的缓存问题,需要通过调整布局属性或使用RecyclerView的视图池等技巧来解决。 ### 回答3: Android嵌套滑动规范是在Android 5.0(API level 21)中引入的,用于使父View和子View之间的滑动效果更加协调。而NestedScrollViewRecyclerView都是Android中常用的滑动控件,但是在嵌套使用的过程中,RecyclerView的数据无法复用缓存,这是为什么呢? 首先,我们需要了解RecyclerView的缓存机制。RecyclerView使用三种缓存机制:首先是ViewHolder缓存,它可以在滑动时快速重新绑定已经存在的ViewHolder对象;其次是View缓存,它缓存了滑出屏幕的View,可以加速滑动时的UI响应;最后是Bitmap缓存,它用于缓存在RecyclerView中的图像。 然而,在嵌套使用NestedScrollViewRecyclerView时,RecyclerView的缓存机制无法发挥作用。这是因为NestedScrollView将滑动事件先处理,然后再将滑动事件交给RecyclerView处理,这会重新调用RecyclerView的Adapter中的getView()方法,导致RecyclerView中的缓存被清空。因此,RecyclerView的数据无法复用缓存,会造成性能上的损失。 为了解决这个问题,可以采取以下措施: 1.不要嵌套滑动控件:尽量避免使用NestedScrollViewRecyclerView嵌套滑动控件。 2.使用LayoutManager:使用LayoutManager可以缓存RecyclerView的ViewHolder对象,加速滑动过程中的UI响应。 3.自定义LayoutManager:自定义LayoutManager可以对滑动速度、滑动方向等进行优化,提高RecyclerView的性能。 4.使用分组显示:对RecyclerView的数据进行分组,将分组内的数据合并成一张图片,然后将图片缓存起来,这样可以减少RecyclerView的刷新次数。 总的来说,NestedScrollViewRecyclerView嵌套使用会导致RecyclerView的缓存无法复用,降低RecyclerView的性能,因此需要采取一些措施来解决这个问题。使用LayoutManager、自定义LayoutManager、分组显示等方法可以优化RecyclerView的性能,提高用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值