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

相关文章

部署Vue项目到服务器后404错误的原因及解决方案

《部署Vue项目到服务器后404错误的原因及解决方案》文章介绍了Vue项目部署步骤以及404错误的解决方案,部署步骤包括构建项目、上传文件、配置Web服务器、重启Nginx和访问域名,404错误通常是... 目录一、vue项目部署步骤二、404错误原因及解决方案错误场景原因分析解决方案一、Vue项目部署步骤

pip install jupyterlab失败的原因问题及探索

《pipinstalljupyterlab失败的原因问题及探索》在学习Yolo模型时,尝试安装JupyterLab但遇到错误,错误提示缺少Rust和Cargo编译环境,因为pywinpty包需要它... 目录背景问题解决方案总结背景最近在学习Yolo模型,然后其中要下载jupyter(有点LSVmu像一个

Goland debug失效详细解决步骤(合集)

《Golanddebug失效详细解决步骤(合集)》今天用Goland开发时,打断点,以debug方式运行,发现程序并没有断住,程序跳过了断点,直接运行结束,网上搜寻了大量文章,最后得以解决,特此在这... 目录Bug:Goland debug失效详细解决步骤【合集】情况一:Go或Goland架构不对情况二:

SpringBoot中的404错误:原因、影响及解决策略

《SpringBoot中的404错误:原因、影响及解决策略》本文详细介绍了SpringBoot中404错误的出现原因、影响以及处理策略,404错误常见于URL路径错误、控制器配置问题、静态资源配置错误... 目录Spring Boot中的404错误:原因、影响及处理策略404错误的出现原因1. URL路径错

在Rust中要用Struct和Enum组织数据的原因解析

《在Rust中要用Struct和Enum组织数据的原因解析》在Rust中,Struct和Enum是组织数据的核心工具,Struct用于将相关字段封装为单一实体,便于管理和扩展,Enum用于明确定义所有... 目录为什么在Rust中要用Struct和Enum组织数据?一、使用struct组织数据:将相关字段绑

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

Linux内存泄露的原因排查和解决方案(内存管理方法)

《Linux内存泄露的原因排查和解决方案(内存管理方法)》文章主要介绍了运维团队在Linux处理LB服务内存暴涨、内存报警问题的过程,从发现问题、排查原因到制定解决方案,并从中学习了Linux内存管理... 目录一、问题二、排查过程三、解决方案四、内存管理方法1)linux内存寻址2)Linux分页机制3)

mysql外键创建不成功/失效如何处理

《mysql外键创建不成功/失效如何处理》文章介绍了在MySQL5.5.40版本中,创建带有外键约束的`stu`和`grade`表时遇到的问题,发现`grade`表的`id`字段没有随着`studen... 当前mysql版本:SELECT VERSION();结果为:5.5.40。在复习mysql外键约

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

Spring常见错误之Web嵌套对象校验失效解决办法

《Spring常见错误之Web嵌套对象校验失效解决办法》:本文主要介绍Spring常见错误之Web嵌套对象校验失效解决的相关资料,通过在Phone对象上添加@Valid注解,问题得以解决,需要的朋... 目录问题复现案例解析问题修正总结  问题复现当开发一个学籍管理系统时,我们会提供了一个 API 接口去