如何应对Android面试官->RecyclerView回收复用LayoutManager,实战探探划一下

前言

上章我们讲了右半部分,本章我们讲解左半部分;

如何复用原理

我们在滑动的时候,才会触发 RecyclerView  的回收复用,所以我们从 RecyclerView 的 onTouchEvent 方法入手;我们来看下滑动的时候,是怎么和 LayoutManager 关联起来的;

我们进入 onTouchEvent 的 ACTION_MOVE 看下:

public boolean onTouchEvent(MotionEvent e) {
    //
    ...
    // 省略部分代码
    case MotionEvent.ACTION_MOVE:
        if(scrollByInternal(xxxx)){}
        break;
}

我们进入 scrollByInternal( canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0, e) 这个方法看下:

boolean scrollByInternal(int x, int y, MotionEvent ev) {
    //
    ...
    // 省略部分代码
    scrollStep(x, y, mResuableIntPair);
}

 我们进入这个 scrollStep 方法看下:

根据滑动方向,分别调用了 LayoutManager 不同的方法,我们选择其中一个进入看下:

我们选择 LinearLayoutManager 的 scrollVerticalcallBy 方法看下:

public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,        
        RecyclerView.State state) {    
    if (mOrientation == HORIZONTAL) {        
        return 0;    
    }    
    return scrollBy(dy, recycler, state);
}

这里直接调用了 scrollBy 方法,我们进入这个方法看一下:

我们进入这个 fill 方法看下:

可以看到,我们在一个 while 循环中多次调用 layoutChunk 方法,这个 layoutChunk 方法就是获取 view 填充我们的 RecyclerView 的,我们进入这个方法看下:

从缓存中获取 View 并添加到 RecyclerView 中,我们进入这个 next 方法看下:

View next(RecyclerView.Recycler recycler) {    
    if (mScrapList != null) {        
        return nextViewFromScrapList();    
    }    
    final View view = recycler.getViewForPosition(mCurrentPosition);    
    mCurrentPosition += mItemDirection;    
    return view;
}

从 Recycler 中根据位置获取一个 View,我们进入这个 getViewForPosition 看下:

View getViewForPosition(int position, boolean dryRun) {    
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

拿到 ViewHolder 之后,直接获取它的 itemView 并返回,所有的将 ViewHolder 从缓存取出来复用的逻辑都在这里,我们来看下 ViewHolder 是如何复用的:

这里一共包含了四级缓存,对应着四级复用:

  1. mChangeScrp 和 mAttachedScrp;用来缓存还在屏幕内的 ViewHolder
  2. mCachedViews;用来缓存移除屏幕外地 ViewHolder
  3. mViewCacheExtension;开发给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存
  4. RecyclerViewPool;ViewHolder 缓存池

第一次缓存复用

// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {    
    holder = getChangedScrapViewForPosition(position);    
    fromScrapOrHiddenOrCache = holder != null;
}

与动画相关的,通过位置从 mChangeScrp 中获取;

第二次缓存复用

// 1) Find by position from scrap/hidden list/cache
if (holder == null) {    
    holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}

针对位置的,通过位置从 mAttachedScrap 和 mCachedViews 中获取;

holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),        
    type, dryRun);

还是针对 mAttachedScrap 和 mCachedViews 中获取的,通过 ViewType 和 ItemId来区分,所以这个也属于第二次缓存复用;

第三次缓存复用

if (holder == null && mViewCacheExtension != null) {    
    // We are NOT sending the offsetPosition because LayoutManager does not    
    // know it.    
    final View view = mViewCacheExtension            
            .getViewForPositionAndType(this, position, type);    
    if (view != null) {        
        holder = getChildViewHolder(view);
    }
    // 省略部分代码
    ...
    // 
}

开发给开发中的自定义扩展缓存,需要开发者自己管理 View 的创建和缓存,一般用不到;

第四次缓存复用

holder = getRecycledViewPool().getRecycledView(type);

从缓存池中获取;

复用的流程我们已经梳理通了,那么拿到复用的 ItemView 之后,又是如何调用到 onBindViewHolder 以及如何调用的 onCreateViewHolder 呢?我们继续分析:

如果四级缓存中都没有可以复用的 ViewHolder 的话,那么就需要进行 ViewHolder 的创建流程了;

if (holder == null) { 
    holder = mAdapter.createViewHolder(RecyclerView.this, type);
}

创建之后,就是进行 ViewHolder 的绑定流程了;

bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);

最终就会调用到 onBindViewHolder 方法;

整体时序图如下:

如何缓存原理

缓存发生了 RecyclerView 的 onLayout 方法中,我们进入看一下:

protected void onLayout(boolean changed, int l, int t, int r, int b) {    
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);    
    dispatchLayout();    
    TraceCompat.endSection();    
    mFirstLayoutComplete = true;
}

我们进入 dispatchLayout 方法看下:

我们进入这个 dispatchLayoutStep2 方法看下,这个方法最终调用到了

mLayout.onLayoutChildren(mRecycler, mState);

我们进入 LinearLayoutManager 的 onLayoutChildren 方法看下,这个方法最终调用到了 detachAndScrapAttachedViews 这个方法,我们进入这个方法看下:

public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {    
    final int childCount = getChildCount();    
    for (int i = childCount - 1; i >= 0; i--) {        
        final View v = getChildAt(i);        
        scrapOrRecycleView(recycler, i, v);    
    }
}

这里调用了 recycler.recycleViewHolderInternal(viewHolder); 和 recycler.scrapView(view); 我们分别看下;

recycler.recycleViewHolderInternal(viewHolder) 主要用来处理 mCacheView 和 RecyclerViewPool 的缓存;

如果 ViewHolder remove、update 等发生变化的时候,不执行缓存逻辑;

recycleCachedViewAt 处理的就是 mCacheView;

// 如果 mCacheView 当前的大小大于等于 mViewCacheMax(默认的mCacheView的大小)
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
   recycleCachedViewAt(0);
   cachedViewSize--;
}
void recycleCachedViewAt(int cachedViewIndex) {    
    if (DEBUG) {        
        Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);    
    }    
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);    
    if (DEBUG) {        
        Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);    
    }    
    addViewHolderToRecycledViewPool(viewHolder, true);    
    mCachedViews.remove(cachedViewIndex);
}

addViewHolderToRecycledViewPool(viewHolder, true); 和 mCachedViews.remove(cachedViewIndex); 这两个方法执行之后,会将 ViewHolder 添加到 RecycledViewPool 中(调用 addViewHolderToRecycledViewPool 方法),同时从 mCachedViews 中移除,也就是说 RecyclerViewPool 中的数据是从 mCachedView 中来的;

当 mCachedViews 中存满之后(默认存放2个),就会把第 0 个位置的 View 添加到 RecyclerViewPool 中并从自身移除掉,第 1 个位置的 ViewHolder 移动到第 0 个位置,新进来的放到第 1 个位置;

我们接下来看下 addViewHolderToRecycledViewPool 方法的实现;

我们进入 putRecycledView 方法中看下:

先获取 viewType,然后根据 viewType 获取 ScrapData,然后获取它的 scrapHeap 集合;也就是我们的 ViewHolder 是存放在 ScrapData 中了;

我们来看下 getScrapDataForType 的方法实现:

if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
    return;
} 

数据满了之后,直接 return 不进行缓存,也就是同一种 ViewType 的 ViewHolder 只保存 5 个;

scrap.resetInternal();

清空 ViewHolder,也就是缓存池中保存的只是 ViewHolder 类型,不保存数据;

我们接下来看下 recycler.scrapView(view);

这里处理了 mAttachedScrap 和 mChangedScrap 用来缓存 ViewHolder;

整体时序图如下:

自定义LayoutManager

我们如果想实现探探的左滑右滑效果,需要自定义 LayoutManager;

public class SlideLayoutManager extends RecyclerView.LayoutManager {

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    // 布局
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        // ViewHodler回收复用
        detachAndScrapAttachedViews(recycler);

        int bottomPosition;
        int itemCount = getItemCount();
        if (itemCount < CardConfig.MAX_SHOW_COUNT) {
            bottomPosition = 0;
        } else {
            // 布局了四张卡片
            bottomPosition = itemCount - CardConfig.MAX_SHOW_COUNT;
        }

        for (int i = bottomPosition; i < itemCount; i++) {
            // 复用
            View view = recycler.getViewForPosition(i);
            addView(view);
            
            measureChildWithMargins(view, 0, 0);

            int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);
            int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);

            // 布局 -> draw -> onDraw ,onDrawOver, onLayout
            layoutDecoratedWithMargins(view, widthSpace / 2, heightSpace / 2,
                    widthSpace / 2 + getDecoratedMeasuredWidth(view),
                    heightSpace / 2 + getDecoratedMeasuredHeight(view));

            int level = itemCount - i - 1;
            if (level > 0) {
                if (level < CardConfig.MAX_SHOW_COUNT - 1) {
                    view.setTranslationY(CardConfig.TRANS_Y_GAP * level);
                    view.setScaleX(1 - CardConfig.SCALE_GAP * level);
                    view.setScaleY(1 - CardConfig.SCALE_GAP * level);
                } else {
                    // 最下面的那个view 与前一个view 布局一样
                    view.setTranslationY(CardConfig.TRANS_Y_GAP * (level - 1));
                    view.setScaleX(1 - CardConfig.SCALE_GAP * (level - 1));
                    view.setScaleY(1 - CardConfig.SCALE_GAP * (level - 1));
                }
            }
        }
    }
}

一个简单的自定义 LayoutManager, generateDefaultLayoutParams 直接抄系统的实现即可;

最终实现的效果如下:

仿 探探 的效果,gif 好卡.....;

简历润色

深度理解 RecyclerView 的缓存复用原理,可深度定制 LayoutManager;

下一章预告

带你玩转 ViewPager,实现炫酷 Banner;

欢迎三连

来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SQL实践:利用tag检索文件的多种情况讨论(二)

在上一篇文章SQL实践&#xff1a;利用tag检索文件的多种情况讨论中&#xff0c;我们介绍了在使用外键的方式为数据关联tag后&#xff0c;如何筛选&#xff1a; 如何筛选包含某一个tag的数据如何筛选包含且只包含某一个tag的数据如何筛选包含多个指定tag的数据 这篇文章主要是…

LiveGBS流媒体平台GB/T28181功能-基础配置接入控制白名单黑名单配置控制设备安全接入设备单独配置接入密码

LiveGBS基础配置接入控制白名单黑名单配置控制设备安全接入设备单独配置接入密码 1、白名单配置应用场景2、接入控制2.1、白名单2.2、黑名单 3、搭建GB28181视频直播平台 1、白名单配置应用场景 LiveGBS国标流媒体服务&#xff0c;支持白名单配置。 可在设备注册前&#xff0…

机器学习_梯度下降

文章目录 什么是梯度梯度下降梯度下降有什么用 什么是梯度 计算梯度向量其几何意义&#xff0c;就是函数变化的方向&#xff0c;而且是变化最快的方向。对于函数f(x)&#xff0c;在点(xo,yo)&#xff0c;梯度向量的方向也就是y值增加最快的方向。也就是说&#xff0c;沿着梯度…

使用 Elasticsearch 和 LlamaIndex 进行高级文本检索:句子窗口检索

2023 年是检索增强生成 (RAG) 的一年&#xff0c;人们探索了许多用例&#xff0c;并使用该技术开发了数百种产品。 从 Q/A 聊天机器人到基于上下文的代理&#xff0c;RAG 的使用一直是 LLM 申请快速增长的主要因素。 支持不断发展的社区以及 Langchain 和 LlamaIndex 等强大框架…

Controller层自定义注解拦截request请求校验

一、背景 笔者工作中遇到一个需求&#xff0c;需要开发一个注解&#xff0c;放在controller层的类或者方法上&#xff0c;用以校验请求参数中(不管是url还是body体内&#xff0c;都要检查&#xff0c;有token参数&#xff0c;且符合校验规则就放行)是否传了一个token的参数&am…

Java工具类汇总

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; ExcelUtils public class ExcelUtils {/*** 注入的具有排序功能的handle*/private static final SortRowWriteHandler SORT_ROW_WRITE_HANDLER new SortRowWriteHan…

linux 网络文件共享服务

存储类型 DAS 直连式存储 SAN 存储区域网络 NAS 网络附近存储 FTP文件传输协议 文件传输协议 FTP 早期的三个应用级协议之一&#xff0c;基于c/s架构 数据传输格式&#xff1a;二进制&#xff08;默认&#xff09;和文本 tcp 21端口&#xff08;权限&#xff0c;…

centos7配置时间同步网络时间

centos7配置时间同步网络时间 1、安装 NTP 工具。 sudo yum install -y ntp2启动 NTP 服务。 sudo systemctl start ntpd3、将 NTP 服务设置为开机自启动。 sudo systemctl enable ntpd4、验证 date

超5000亿元,2024年国家电网预计电网建设投资总规模

近日&#xff0c;国家电网公司对外透露&#xff0c;2024年将继续加大数智化坚强电网的建设&#xff0c;促进能源绿色低碳转型&#xff0c;推动阿坝至成都东等特高压工程开工建设。围绕数字化配电网、新型储能调节控制、车网互动等应用场景&#xff0c;打造一批数智化坚强电网示…

WEB服务器-Tomcat

3. WEB服务器-Tomcat 3.1 简介 3.1.1 服务器概述 服务器硬件 指的也是计算机&#xff0c;只不过服务器要比我们日常使用的计算机大很多。 服务器&#xff0c;也称伺服器。是提供计算服务的设备。由于服务器需要响应服务请求&#xff0c;并进行处理&#xff0c;因此一般来说…

Relation-Aware Graph Transformer for SQL-to-Text Generation

Relation-Aware Graph Transformer for SQL-to-Text Generation Abstract SQL2Text 是一项将 SQL 查询映射到相应的自然语言问题的任务。之前的工作将 SQL 表示为稀疏图&#xff0c;并利用 graph-to-sequence 模型来生成问题&#xff0c;其中每个节点只能与 k 跳节点通信。由…

shell简单截取curl GET返回的body消息体

目录 需求背景&#xff1a; 示例&#xff1a; 解决方式&#xff1a; 需求背景&#xff1a; 用shell解析 curl命令GET到的消息体&#xff0c;获取body消息体里的某个字段的值,只是个简单的示例&#xff0c;可以在此基础上更改满足自己的需求 示例&#xff1a; curl一个API…

使用CSS计算高度铺满屏幕

前言 今天写项目时出现高度设置百分百却不占满屏幕&#xff0c;第一反应看自己设置的是块级元素还是行级元素。看了几篇博客&#xff0c;发现并不能解决问题。脱离文档流的做法都没考虑&#xff0c;前期模板搭建脱离文档流&#xff0c;后面开发会出现很多问题。 以上图片是我…

【EI会议征稿通知】2024年第三届能源互联网及能源交互技术国际会议(EIEIT 2024)

2024年第三届能源互联网及能源交互技术国际会议(EIEIT 2024) 2024 3rd International Conference on the Energy Internet and Energy Interactive Technology 随着EIEIT前2届的成功举办&#xff0c;我们很荣幸地宣布&#xff0c;2024年第三届能源互联网及能源交互技术国际学术…

HCIA——12题目-1章选择

学习目标&#xff1a; 计算机网络 1.掌握计算机网络的基本概念、基本原理和基本方法。 2.掌握计算机网络的体系结构和典型网络协议&#xff0c;了解典型网络设备的组成和特点&#xff0c;理解典型网络设备的工作原理。 3.能够运用计算机网络的基本概念、基本原理和基本方法进行…

FPGA之LUT

由于FPGA需要被反复烧写,它实现组合逻辑的基本结构不可能像ASIC那样通过固定的与非门来完成,而只能采用一种易于反复配置的结构。查找表可以很好地满足这一要求,目前主流FPGA都采用了基于SRAM工艺的查找表结构。LUT本质上就是一个RAM.它把数据事先写入RAM后,每当输入一个信号就…

【Mybatis】说一下 mybatis 的一级缓存和二级缓存

​ &#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Mybatis ⛳️ 功不唐捐&#xff0c;玉汝于成 ​ 目录 前言 正文 一级缓存&#xff08;Local Cache&#xff09;&#xff1a; 范围&#xff1a; 生命周期&#xff1a; 默认开启&…

PyTorch Tutorial 2.0

这里是对于PyTorch Tutorial-CSDN博客的补充&#xff0c;但是与其相关的NLP内容无关&#xff0c;只是一些基础的PyTorch用法的记录&#xff0c;主要目的是能够自己生成一些模拟的数据集。先介绍随机数的目的是因为based on随机数方法。 当然在看随机数的方法的时候&#xff0c…

彻底解决charles抓包https乱码的问题

最近做js逆向&#xff0c;听说charles比浏览器抓包更好用&#xff0c;结果发现全是乱码&#xff0c;根本没法用。 然后查询网上水文&#xff1a;全部都是装证书&#xff0c;根本没用&#xff01; 最后终于找到解决办法&#xff0c;在这里记录一下&#xff1a; 乱码的根本原因…

c++可调用对象、function类模板与std::bind

函数调用与函数调用运算符 先写一个简单的函数&#xff0c;如下&#xff1a; /*函数的定义*/ int func(int i) {cout<<"这是一个函数\t"<<i<<endl; }void test() {func(1);//函数的调用 } 通过这个普通的函数可以看到&#xff0c;调用一个函数很…