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

相关文章

解决1093 - You can‘t specify target table报错问题及原因分析

《解决1093-Youcan‘tspecifytargettable报错问题及原因分析》MySQL1093错误因UPDATE/DELETE语句的FROM子句直接引用目标表或嵌套子查询导致,... 目录报js错原因分析具体原因解决办法方法一:使用临时表方法二:使用JOIN方法三:使用EXISTS示例总结报错原

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

SpringSecurity显示用户账号已被锁定的原因及解决方案

《SpringSecurity显示用户账号已被锁定的原因及解决方案》SpringSecurity中用户账号被锁定问题源于UserDetails接口方法返回值错误,解决方案是修正isAccountNon... 目录SpringSecurity显示用户账号已被锁定的解决方案1.问题出现前的工作2.问题出现原因各

javax.net.ssl.SSLHandshakeException:异常原因及解决方案

《javax.net.ssl.SSLHandshakeException:异常原因及解决方案》javax.net.ssl.SSLHandshakeException是一个SSL握手异常,通常在建立SS... 目录报错原因在程序中绕过服务器的安全验证注意点最后多说一句报错原因一般出现这种问题是因为目标服务器

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案(最新整理)

《MyBatisPlus中update_time字段自动填充失效的原因分析及解决方案(最新整理)》在使用MyBatisPlus时,通常我们会在数据库表中设置create_time和update... 目录前言一、问题现象二、原因分析三、总结:常见原因与解决方法对照表四、推荐写法前言在使用 MyBATis

MySQL版本问题导致项目无法启动问题的解决方案

《MySQL版本问题导致项目无法启动问题的解决方案》本文记录了一次因MySQL版本不一致导致项目启动失败的经历,详细解析了连接错误的原因,并提供了两种解决方案:调整连接字符串禁用SSL或统一MySQL... 目录本地项目启动报错报错原因:解决方案第一个:第二种:容器启动mysql的坑两种修改时区的方法:本地

MyBatis编写嵌套子查询的动态SQL实践详解

《MyBatis编写嵌套子查询的动态SQL实践详解》在Java生态中,MyBatis作为一款优秀的ORM框架,广泛应用于数据库操作,本文将深入探讨如何在MyBatis中编写嵌套子查询的动态SQL,并结... 目录一、Myhttp://www.chinasem.cnBATis动态SQL的核心优势1. 灵活性与可

IDEA中Maven Dependencies出现红色波浪线的原因及解决方法

《IDEA中MavenDependencies出现红色波浪线的原因及解决方法》在使用IntelliJIDEA开发Java项目时,尤其是基于Maven的项目,您可能会遇到MavenDependenci... 目录一、问题概述二、解决步骤2.1 检查 Maven 配置2.2 更新 Maven 项目2.3 清理本

Java空指针异常NullPointerException的原因与解决方案

《Java空指针异常NullPointerException的原因与解决方案》在Java开发中,NullPointerException(空指针异常)是最常见的运行时异常之一,通常发生在程序尝试访问或... 目录一、空指针异常产生的原因1. 变量未初始化2. 对象引用被显式置为null3. 方法返回null

Mybatis嵌套子查询动态SQL编写实践

《Mybatis嵌套子查询动态SQL编写实践》:本文主要介绍Mybatis嵌套子查询动态SQL编写方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、实体类1、主类2、子类二、Mapper三、XML四、详解总结前言MyBATis的xml文件编写动态SQL