原文地址:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0307/4032.html

概述

之前面试的时候经常有人问是否用过RecyclerView,最近项目中也大量用到RecyclerView。对于有点追求的码工来时,当然不会满足于仅仅会使用这一层次,学姐就是一个有追求的妹纸。我先从普通的AdapterViewRecyclerView的比较说起,后面再详细介绍几个关键类。

AdapterView vs. RecyclerView

  • Item复用方面RecyclerView内置了RecyclerViewPool、多级缓存、ViewHolder,而AdapterView需要       手动添加ViewHolder且复用功能也没RecyclerView更加完善

  • 样式丰富方面RecyclerView通过支持水平、垂直和表格列表及其他更复杂形式,而AdapterView只支持具体某一种

  • 效果增强方面RecyclerView内置了ItemDecorationItemAnimator,可以自定义绘制itemView之间的一些特殊UI或item项数据变化时的动画效果,而用AdapterView实现时采取的做法是将这些特殊UI作为itemView的一部分,设置可见不可见决定是否展现,且数据变化时的动画效果没有提供,实现起来比较麻烦

  • 代码内聚方面RecyclerView将功能密切相关的类写成内部类,如ViewHolderAdapter,而AdapterView没有

1. Recycler

(1)Recycler简介

Recycler用于管理已经废弃或与RecyclerView分离的(scrapped or detached)item view,便于重用。

Scrapped view指依附于RecyclerView,但被标记为可移除或可复用的view。

LayoutManager获取Adapter某一项的View时会使用Recycler。当复用的View有效(clean)时,View能直接被复用,反之若View失效(dirty)时,需要重新绑定View。对于有效的View,如果不主动调用request layout,则不需要重新测量大小就能复用

(2)原理解析

在分析Recycler的复用原理之前,我们先了解下如下两个类:

RecycledViewPool

RecyclerViewPool用于多个RecyclerView之间共享View。只需要创建一个RecyclerViewPool实例,然后调用RecyclerView的setRecycledViewPool(RecycledViewPool)方法即可。RecyclerView默认会创建一个RecyclerViewPool实例。

  1.     public static class RecycledViewPool {
    private SparseArray<ArrayList<ViewHolder>> mScrap =
    new SparseArray<ArrayList<ViewHolder>>();
    private SparseIntArray mMaxScrap = new SparseIntArray();
    private int mAttachCount = 0; private static final int DEFAULT_MAX_SCRAP = 5; public void clear() {
    mScrap.clear();
    } public void setMaxRecycledViews(int viewType, int max) {
    mMaxScrap.put(viewType, max);
    final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
    if (scrapHeap != null) {
    while (scrapHeap.size() > max) {
    scrapHeap.remove(scrapHeap.size() - 1);
    }
    }
    } public ViewHolder getRecycledView(int viewType) {
    final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
    if (scrapHeap != null && !scrapHeap.isEmpty()) {
    final int index = scrapHeap.size() - 1;
    final ViewHolder scrap = scrapHeap.get(index);
    scrapHeap.remove(index);
    return scrap;
    }
    return null;
    } int size() {
    int count = 0;
    for (int i = 0; i < mScrap.size(); i ++) {
    ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i);
    if (viewHolders != null) {
    count += viewHolders.size();
    }
    }
    return count;
    } public void putRecycledView(ViewHolder scrap) {
    final int viewType = scrap.getItemViewType();
    final ArrayList scrapHeap = getScrapHeapForType(viewType);
    if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
    return;
    }
    if (DEBUG && scrapHeap.contains(scrap)) {
    throw new IllegalArgumentException("this scrap item already exists");
    }
    scrap.resetInternal();
    scrapHeap.add(scrap);
    } void attach(Adapter adapter) {
    mAttachCount++;
    } void detach() {
    mAttachCount--;
    } /**
    * Detaches the old adapter and attaches the new one.
    * <p>
    * RecycledViewPool will clear its cache if it has only one adapter attached and the new
    * adapter uses a different ViewHolder than the oldAdapter.
    *
    * @param oldAdapter The previous adapter instance. Will be detached.
    * @param newAdapter The new adapter instance. Will be attached.
    * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same
    * ViewHolder and view types.
    */
    void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
    boolean compatibleWithPrevious) {
    if (oldAdapter != null) {
    detach();
    }
    if (!compatibleWithPrevious && mAttachCount == 0) {
    clear();
    }
    if (newAdapter != null) {
    attach(newAdapter);
    }
    } private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
    ArrayList<ViewHolder> scrap = mScrap.get(viewType);
    if (scrap == null) {
    scrap = new ArrayList<ViewHolder>();
    mScrap.put(viewType, scrap);
    if (mMaxScrap.indexOfKey(viewType) < 0) {
    mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
    }
    }
    return scrap;
    }
    }

通过源码我们可以看出mScrap是一个<viewType, List>的映射,`mMaxScrap`是一个<viewType, maxNum>的映射,这两个成员变量代表可复用View池的基本信息。调用`setMaxRecycledViews(int viewType, int max)`时,当用于复用的`mScrap`中viewType对应的ViewHolder个数超过maxNum时,会从列表末尾开始丢弃超过的部分。调用`getRecycledView(int viewType)`方法时从`mScrap`中移除并返回viewType对应的List的末尾项。

ViewCacheExtension

ViewCacheExtension是一个由开发者控制的可以作为View缓存的帮助类。调用Recycler.getViewForPosition(int)方法获取View时,Recycler先检查attached scrap和一级缓存,如果没有则检查ViewCacheExtension.getViewForPositionAndType(Recycler, int, int),如果没有则检查RecyclerViewPool。注意:Recycler不会在这个类中做缓存View的操作,是否缓存View完全由开发者控制。

  1. public abstract static class ViewCacheExtension {
    abstract public View getViewForPositionAndType(Recycler recycler, int position, int type);
    }

现在大家熟悉了RecyclerViewPoolViewCacheExtension的作用后,下面开始介绍Recycler。 如下是Recycler的几个关键成员变量和方法:

private ArrayList<ViewHolder> mAttachedScrap
private ArrayList<ViewHolder> mChangedScrap与RecyclerView分离的ViewHolder列表。 private ArrayList<ViewHolder> mCachedViewsViewHolder缓存列表。 private ViewCacheExtension mViewCacheExtension开发者控制的ViewHolder缓存 private RecycledViewPool mRecyclerPool提供复用ViewHolder池。 public void bindViewToPosition(View view, int position)

将某个View绑定到Adapter的某个位置。

public View getViewForPosition(int position)

获取某个位置需要展示的View,先检查是否有可复用的View,没有则创建新View并返回。具体过程为:  
(1)检查mChangedScrap,若匹配到则返回相应holder  
(2)检查mAttachedScrap,若匹配到且holder有效则返回相应holder  
(3)检查mViewCacheExtension,若匹配到则返回相应holder  
(4)检查mRecyclerPool,若匹配到则返回相应holder  
(5)否则执行Adapter.createViewHolder(),新建holder实例  
(6)返回holder.itemView  
(7)注:以上每步匹配过程都可以匹配position或itemId(如果有stableId)

2. LayoutManager

LayoutManager主要作用是,测量和摆放RecyclerView中itemView,以及当itemView对用户不可见时循环复用处理。 通过设置Layout Manager的属性,可以实现水平滚动、垂直滚动、方块表格等列表形式。其内部类Properties包含了所需要的大部分属性

3. ViewHolder

对于传统的AdapterView,需要在实现的Adapter类中手动加ViewHolderRecyclerView直接将ViewHolder内置,并在原来基础上功能上更强大。ViewHolder描述RecylerView中某个位置的itemView和元数据信息,属于Adapter的一部分。其实现类通常用于保存findViewById的结果。 主要元素组成有:

  1. public static abstract class ViewHolder {
    View itemView;//itemView
    int mPosition;//位置
    int mOldPosition;//上一次的位置
    long mItemId;//itemId
    int mItemViewType;//itemViewType
    int mPreLayoutPosition;
    int mFlags;//ViewHolder的状态标志
    int mIsRecyclableCount;
    Recycler mScrapContainer;//若非空,表明当前ViewHolder对应的itemView可以复用

关于ViewHolder,我最想介绍的是mFlags。  
FLAG_BOUND——ViewHolder已经绑定到某个位置,mPosition、mItemId、mItemViewType都有效  
FLAG_UPDATE——ViewHolder绑定的View对应的数据过时需要重新绑定,mPosition、mItemId还是一致的  
FLAG_INVALID——ViewHolder绑定的View对应的数据无效,需要完全重新绑定不同的数据  
FLAG_REMOVED——ViewHolder对应的数据已经从数据集移除  
FLAG_NOT_RECYCLABLE——ViewHolder不能复用  
FLAG_RETURNED_FROM_SCRAP——这个状态的ViewHolder会加到scrap list被复用。  
FLAG_CHANGED——ViewHolder内容发生变化,通常用于表明有ItemAnimator动画  
FLAG_IGNORE——ViewHolder完全由LayoutManager管理,不能复用  
FLAG_TMP_DETACHED——ViewHolder从父RecyclerView临时分离的标志,便于后续移除或添加回来  
FLAG_ADAPTER_POSITION_UNKNOWN——ViewHolder不知道对应的Adapter的位置,直到绑定到一个新位置  
FLAG_ADAPTER_FULLUPDATE——方法addChangePayload(null)调用时设置

4. Adapter

AdapterView中用到的BaseAdapterListAdapter等作用类似,都是作为itemView和data之间的适配器,将data绑定到某一个itemView上。差别在于,RecyclerViewAdapter内置作为其内部类,我认为将功能密切相关的类以内部类的形式定义使得代码内聚更好,更便于理解与阅读。

5. ItemDecoration

当我们想在某些item上加一些特殊的UI时,往往都是在itemView中先布局好,然后通过设置可见性来决定哪些位置显示不显示。RecyclerView将itemView和装饰UI分隔开来,装饰UI即ItemDecoration,主要用于绘制item间的分割线、高亮或者margin等。其源码如下:

  1. public static abstract class ItemDecoration {
    //itemView绘制之前绘制,通常这部分UI会被itemView盖住
    public void onDraw(Canvas c, RecyclerView parent, State state) {
    onDraw(c, parent);
    } //itemView绘制之后绘制,这部分UI盖在itemView上面
    public void onDrawOver(Canvas c, RecyclerView parent, State state) {
    onDrawOver(c, parent);
    } //设置itemView上下左右的间距
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
    getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
    parent);
    }
    }

6. ItemAnimator

过去AdapterView的item项操作往往是没有动画的。现在RecyclerViewItemAnimator使得item的动画实现变得简单而样式丰富,我们可以自定义item项不同操作(如添加,删除)的动画效果。

7. 触摸事件监听

关于Item项的手势监听事件,如单击和双击没有像其他AdapterView一样分别提供具体接口,但是RecyclerView提供OnItemTouchListener接口和SimpleOnItemTouchListener实现类,大家可以通过继承去实现自己想要的单击双击或其他事件监听。

ps:关于RecyclerView的具体使用,我提供一个链接供大家参考RecyclerView技术栈。今天就先讲这么多,以后有新的体会会继续补充的,各位期待吧~

【转载】RecyclerView源码解析的更多相关文章

  1. RecyclerView源码解析 - 分割线

    猜想:   既然考虑了分割线,那么子View在测量时候肯定要去考虑分割线留出的位置    直接measureChild()方法 猜想: 分割线会调用绘制的方法 onDraw()

  2. RecyclerView 源码分析(一) —— 绘制流程解析

    概述 对于 RecyclerView 是那么熟悉又那么陌生.熟悉是因为作为一名 Android 开发者,RecyclerView 是经常会在项目里面用到的,陌生是因为只是知道怎么用,但是却不知道 Re ...

  3. jQuery整体架构源码解析(转载)

    jQuery整体架构源码解析 最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性, ...

  4. 转载:Bootstrap 源码解析

    Bootstrap 源码解析 前言 Bootstrap 是个CSS库,简单,高效.很多都可以忘记了再去网站查.但是有一些核心的东西需要弄懂.个人认为弄懂了这些应该就算是会了.源码看一波. 栅格系统 所 ...

  5. 【转载】Xutils3源码解析

    Github源码地址:https://github.com/wyouflf/xUtils3 原文地址 :http://www.codekk.com/blogs/detail/54cfab086c476 ...

  6. 【转载】okhttp源码解析

    转自:http://www.open-open.com/lib/view/open1472216742720.html https://blog.piasy.com/2016/07/11/Unders ...

  7. 【转载】FloatingActionButton源码解析

    原文地址:https://github.com/Rowandjj/my_awesome_blog/blob/master/fab_anlysis/README.md loatingActionButt ...

  8. 【转载】Scroller源码解析

    原文地址:https://github.com/Skykai521/AndroidSdkSourceAnalysis/blob/master/article/Scroller%E6%BA%90%E7% ...

  9. Android LayoutInflater源码解析:你真的能正确使用吗?

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 好久没写博客了,最近忙着换工作,没时间写,工作刚定下来.稍后有时间会写一下换工作经历.接下来进入本篇主题,本来没想写LayoutInflater的 ...

随机推荐

  1. js的数据类型--数字

    近期做一些项目的时候发现,自己的js基础还是不够扎实,再看一遍犀牛书,加深自己的理解和印象.所以从这篇文章开始,后面都是关于原生js的一些内容. 这篇文章,我们具体介绍一下js的数据类型其中一种. j ...

  2. jquery 条形码 插件jquery-barcode使用

    转载文章   jquery 条形码 插件jquery-barcode使用 条码官网: http://barcode-coder.com/en/barcode-jquery-plugin-201.htm ...

  3. centos6.8配置FTP普通用户除了家目录外还能访问其他目录

    今天有个需求,使用ftp服务搭建一个文件共享服务器,每个普通用户除了能访问自己家目录的东西,还能访问一个公共的目录.配置步骤如下: 环境如下: 创建用户并配置密码(使用默认家目录/home) user ...

  4. [LA3135]node形式的优先队列

    n个触发器,每个触发器每period秒就产生一个编号为qnum的事件,求前k个事件. n<=1000  k<=10000 node形式的优先队列 主要在于重载小于号,确定优先顺序. #in ...

  5. lua中的继承

    做为一个java出身的程序媛,长时间做Lua,重复一些工作后,特别想用继承.其实很简单.因为我有一大部分的场景,背景长的都一样,所以打算做一个父类. 需要注意的是,如果子类有和父类的同名函数,就会被覆 ...

  6. Fire! (双bfs+预处理)

    题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem ...

  7. python数据处理课程笔记(一)

    一.numpy 1.numpy中所有元素必须是相同的类型 a=[1,2,3,4,'t'] #列表中有str类型,转换为ndarray时所有元素都转换为str类型 arr1=np.array(a) pr ...

  8. bzoj 2434 fail tree+dfs序

    首先比较明显的是我们可以将字符串组建立ac自动机,那么对于询问s1字符串在s2字符串中出现的次数,就是在以s1结尾为根的fail tree中,子树有多少个节点是s2的节点,这样我们处理fail tre ...

  9. cuda中的二分查找

    使用背景 通常,在做高性能计算时,我们需要随机的连接某些点.这些点都具有自己的度量值,显然,度量值越大的值随机到的概率就会越大.因此,采用加权值得方法: void getdegreeSum(DG *g ...

  10. Linux汇编教程01: 基本知识

    在我们开始学习Linux汇编之前,需要简单的了解一下计算机的体系结构.我们不需要特别深入的了解,理解了一些基本概念对与我们理解程序会很有帮助.现在计算机的结构体系都是采用冯诺依曼体系结构的基础上发展过 ...