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

相关文章

java.sql.SQLTransientConnectionException连接超时异常原因及解决方案

《java.sql.SQLTransientConnectionException连接超时异常原因及解决方案》:本文主要介绍java.sql.SQLTransientConnectionExcep... 目录一、引言二、异常信息分析三、可能的原因3.1 连接池配置不合理3.2 数据库负载过高3.3 连接泄漏

javacv依赖太大导致jar包也大的解决办法

《javacv依赖太大导致jar包也大的解决办法》随着项目的复杂度和依赖关系的增加,打包后的JAR包可能会变得很大,:本文主要介绍javacv依赖太大导致jar包也大的解决办法,文中通过代码介绍的... 目录前言1.检查依赖2.更改依赖3.检查副依赖总结 前言最近在写项目时,用到了Javacv里的获取视频

深度剖析SpringBoot日志性能提升的原因与解决

《深度剖析SpringBoot日志性能提升的原因与解决》日志记录本该是辅助工具,却为何成了性能瓶颈,SpringBoot如何用代码彻底破解日志导致的高延迟问题,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言第一章:日志性能陷阱的底层原理1.1 日志级别的“双刃剑”效应1.2 同步日志的“吞吐量杀手”

SysMain服务可以关吗? 解决SysMain服务导致的高CPU使用率问题

《SysMain服务可以关吗?解决SysMain服务导致的高CPU使用率问题》SysMain服务是超级预读取,该服务会记录您打开应用程序的模式,并预先将它们加载到内存中以节省时间,但它可能占用大量... 在使用电脑的过程中,CPU使用率居高不下是许多用户都遇到过的问题,其中名为SysMain的服务往往是罪魁

详解Java中三种状态机实现方式来优雅消灭 if-else 嵌套

《详解Java中三种状态机实现方式来优雅消灭if-else嵌套》这篇文章主要为大家详细介绍了Java中三种状态机实现方式从而优雅消灭if-else嵌套,文中的示例代码讲解详细,感兴趣的小伙伴可以跟... 目录1. 前言2. 复现传统if-else实现的业务场景问题3. 用状态机模式改造3.1 定义状态接口3

Java.lang.InterruptedException被中止异常的原因及解决方案

《Java.lang.InterruptedException被中止异常的原因及解决方案》Java.lang.InterruptedException是线程被中断时抛出的异常,用于协作停止执行,常见于... 目录报错问题报错原因解决方法Java.lang.InterruptedException 是 Jav

解决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... 目录报错原因在程序中绕过服务器的安全验证注意点最后多说一句报错原因一般出现这种问题是因为目标服务器