NestedScrollView嵌套RecyclerView导致RecyclerView复用失效的原因?

本文主要是介绍NestedScrollView嵌套RecyclerView导致RecyclerView复用失效的原因?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、问题描述

使用NestedScrollView嵌套RecyclerView导致RecyclerView复用失效,RecyclerView会将所有数据一次性全部加载。
布局文件如下:

<androidx.core.widget.NestedScrollView 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"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recycler_view"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_below="@id/tool_bar" />
</androidx.core.widget.NestedScrollView>

Adapter的onBindViewHolder打印日志代码如下:

    public void onBindViewHolder(final ViewHolder holder, final int position) {Log.d("tag", "onBindViewHolder>>" + holder.itemView.toString());}

日志如下

onBindViewHolder>>android.widget.LinearLayout{5853c0e
onBindViewHolder>>android.widget.LinearLayout{a7e243c
onBindViewHolder>>android.widget.LinearLayout{95d0b1a
onBindViewHolder>>android.widget.LinearLayout{e274828
onBindViewHolder>>android.widget.LinearLayout{cb0bee6
onBindViewHolder>>android.widget.LinearLayout{4bcbed4
onBindViewHolder>>android.widget.LinearLayout{a39e372
onBindViewHolder>>android.widget.LinearLayout{a90f440
onBindViewHolder>>android.widget.LinearLayout{cbec4be
onBindViewHolder>>android.widget.LinearLayout{cb1146c
onBindViewHolder>>android.widget.LinearLayout{31e6eca
onBindViewHolder>>android.widget.LinearLayout{9d10b58
onBindViewHolder>>android.widget.LinearLayout{91cad96
onBindViewHolder>>android.widget.LinearLayout{5f78504
onBindViewHolder>>android.widget.LinearLayout{ee0d22 
onBindViewHolder>>android.widget.LinearLayout{ce9ed70
onBindViewHolder>>android.widget.LinearLayout{983d96e
onBindViewHolder>>android.widget.LinearLayout{f58709c
onBindViewHolder>>android.widget.LinearLayout{d981e7a
onBindViewHolder>>android.widget.LinearLayout{6c9fa88

我们在Adapter中加载20条数据,RecylerView就所有数据一次性显示完了。这样在数据量小不会有问题,数据量大时就会造成卡顿或者OOM。

二、原因分析

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);...}

NestedScrollView的onMeasure方法如上。会调用父类的onMeasure。NestedScrollView是继承FrameLayout,因此会调用FrameLayout的onMeasure。FrameLayout的onMeasure代码如下:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int count = getChildCount();for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (mMeasureAllChildren || child.getVisibility() != GONE) {measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);}}    }    

FrameLayout的onMeasure会循环调用measureChildWithMargins测量子View。
因为NestedScrollView重写了measureChildWithMargins,因此我们应该看NestedScrollView的measureChildWithMargins:

    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

这里将高度的测量模式指定为 MeasureSpec.UNSPECIFIED。一般情况topMargin和bottomMargin指定为0,因此高度的测量值是0。
RecyclerView的measure和layout最终都交给LayoutManager完成。上面例子使用的LayoutManager是LinearLayoutManager,RecyclerView的measure和layout最终会执行到LinearLayoutManager的fill方法。

    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {...while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {layoutChunkResult.resetInternal();if (RecyclerView.VERBOSE_TRACING) {TraceCompat.beginSection("LLM LayoutChunk");}layoutChunk(recycler, state, layoutState, layoutChunkResult);...return start - layoutState.mAvailable;}

fill方法在while循环中设置子item的布局。layoutState.hasMore(state)是当还有item就为true,layoutState.mInfinite的赋值如下:

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {...mLayoutState.mInfinite = resolveIsInfinite();...fill(recycler, mLayoutState, state, false);...
}

resolveIsInfinite源码如下:

    boolean resolveIsInfinite() {return mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED&& mOrientationHelper.getEnd() == 0;}

getMode和getEnd的源码如下:

    public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {return new OrientationHelper(layoutManager) {...@Overridepublic int getEnd() {return mLayoutManager.getHeight();}@Overridepublic int getMode() {return mLayoutManager.getHeightMode();}...};}

实际调用mLayoutManager的方法。

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {public abstract static class LayoutManager {...@Pxpublic int getHeight() {return mHeight;}public int getHeightMode() {return mHeightMode;}...}
}

在RecyclerView的onMeasure会调用setMeasureSpecs:

protected void onMeasure(int widthSpec, int heightSpec) {...mLayout.setMeasureSpecs(widthSpec, heightSpec);...
}

setMeasureSpecs源码如下:

        void setMeasureSpecs(int wSpec, int hSpec) {mWidth = MeasureSpec.getSize(wSpec);mWidthMode = MeasureSpec.getMode(wSpec);if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {mWidth = 0;}mHeight = MeasureSpec.getSize(hSpec);mHeightMode = MeasureSpec.getMode(hSpec);if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {mHeight = 0;}}

在这里会初始化mHeight和mHeightMode,即使用父控件NestedScrollView传入的测量高度进行赋值,因此上面例子得到的mHeight =0,mHeightMode =MeasureSpec.UNSPECIFIED。所以上面的mLayoutState.mInfinite会返回true,在fill中加载子item时就会一直加载所有的item。

三、如何解决

要解决上面问题,只需要NestedScrollView测量RecyclerView不使用MeasureSpec.UNSPECIFIED模式即可。因此可以重写measureChildWithMargins。

    @Overrideprotected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {child.measure(parentWidthMeasureSpec, parentHeightMeasureSpec);}

这篇关于NestedScrollView嵌套RecyclerView导致RecyclerView复用失效的原因?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1063114

相关文章

使用雪花算法产生id导致前端精度缺失问题解决方案

《使用雪花算法产生id导致前端精度缺失问题解决方案》雪花算法由Twitter提出,设计目的是生成唯一的、递增的ID,下面:本文主要介绍使用雪花算法产生id导致前端精度缺失问题的解决方案,文中通过代... 目录一、问题根源二、解决方案1. 全局配置Jackson序列化规则2. 实体类必须使用Long封装类3.

Idea插件MybatisX失效的问题解决

《Idea插件MybatisX失效的问题解决》:本文主要介绍Idea插件MybatisX失效的问题解决,详细的介绍了4种问题的解决方法,具有一定的参考价值,感兴趣的可以了解一下... 目录一、重启idea或者卸载重装MyBATis插件(无需多言)二、检查.XML文件与.Java(该文件后缀Idea可能会隐藏

C++迭代器失效的避坑指南

《C++迭代器失效的避坑指南》在C++中,迭代器(iterator)是一种类似指针的对象,用于遍历STL容器(如vector、list、map等),迭代器失效是指在对容器进行某些操作后... 目录1. 什么是迭代器失效?2. 哪些操作会导致迭代器失效?2.1 vector 的插入操作(push_back,

MySQL索引失效问题及解决方案

《MySQL索引失效问题及解决方案》:本文主要介绍MySQL索引失效问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录mysql索引失效一、概要二、常见的导致MpythonySQL索引失效的原因三、如何诊断MySQL索引失效四、如何解决MySQL索引失

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

基于Python实现读取嵌套压缩包下文件的方法

《基于Python实现读取嵌套压缩包下文件的方法》工作中遇到的问题,需要用Python实现嵌套压缩包下文件读取,本文给大家介绍了详细的解决方法,并有相关的代码示例供大家参考,需要的朋友可以参考下... 目录思路完整代码代码优化思路打开外层zip压缩包并遍历文件:使用with zipfile.ZipFil

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

找不到Anaconda prompt终端的原因分析及解决方案

《找不到Anacondaprompt终端的原因分析及解决方案》因为anaconda还没有初始化,在安装anaconda的过程中,有一行是否要添加anaconda到菜单目录中,由于没有勾选,导致没有菜... 目录问题原因问http://www.chinasem.cn题解决安装了 Anaconda 却找不到 An

Spring定时任务只执行一次的原因分析与解决方案

《Spring定时任务只执行一次的原因分析与解决方案》在使用Spring的@Scheduled定时任务时,你是否遇到过任务只执行一次,后续不再触发的情况?这种情况可能由多种原因导致,如未启用调度、线程... 目录1. 问题背景2. Spring定时任务的基本用法3. 为什么定时任务只执行一次?3.1 未启用