最清晰的 RecyclerView 使用及源码解析

首先在学习 RecyclerView 的源码之前,建议先阅读 ListView 的源码分析,之后学 RecyclerView 会更快更轻松。
传送门在这里 http://www.jianshu.com/p/ad621b49735e

一、使用

导包: compile 'com.android.support:recyclerview-v7:23.4.0'

1、创建 Adapter

  1. 新建类继承 RecyclerViewAdapter<MyRecyclerViewAdapter.MyViewHolder>,
    泛型指定为 MyRecyclerViewAdapter 的内部类 MyRecyclerViewAdapter.MyViewHolder,
  2. 快捷键创建自定义内部类 MyViewHolder 类
  3. 快捷键修改自定义的MyViewHolder 类继承自 V7 包的 RecyclerView.ViewHolder
  4. 重写MyViewHolder 的一个参数的构造方法
  5. 快捷键导入 RecyclerViewAdapter的抽象方法

1) ViewHolder 类创建步骤

  1. 首先声明需要显示的控件
  2. 在构造方法中,通过构造方法的参数 View ,由 View.findViewById() 方法初始化各控件

2) Adapter 类实现步骤

  1. 重写 onCreateViewHolder() ,调用 MyViewHolder 的参数为 View 的构造方法创建
    MyViewHolder 对象,该 View 对象通过 LayoutInflater 加载 item 布局的 xml 文件得到,然后将 MyViewholder 作为返回值返回。
  2. 重写 onBindViewHolder() ,调用方法中的 ViewHolder 对象,以及由参数 position
    得到要现实的数据, 将数据绑定至 ViewHolder
  3. 重写 getItemCount() , 返回数据集合的 size

2、RecycleView 配置步骤

  1. 初始化 RecyclrView 对象
  2. 为 RecyclerView 设置布局管理器,new 一个 LinearLayoutManager可以通过 setOrientation 设置水平或垂直滚动,默认的是垂直滚动的
  3. 初始化 RecyclerViewAdapter
  4. 为 RecyclerView 设置 Adapter

1) RecyclerView 设置 ItemClickListener/ItemLongClickListener

  1. 在 Adapter 中定义 Listener 接口,定义监听方法,在 Adapter 中定义 Listener 对象和 setListener 的方法
  2. 在 onBindViewHolder() 方法中判断如果 Listener 对象如果不为空就为 viewHolder.itemView
    添加 onClickListener() 方法,在 onClick() 方法中调用 Listener 接口中的监听方法
  3. 如果需要添加监听器,就在 RecyclerViewAdapter 对象初始化后调用 setListener() 方法

2) RecyclerView 的布局管理器

  1. LinearLayoutManager ListView 型 可以设置水平或垂直滑动
    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
  1. GridLayoutMAnager GridView 型 可以设置水平或垂直排列,最后一个参数表示是否反向排列,即数据从尾到头
    GridLayoutManager layoutManager = new GridLayoutManager(this, 4, GridLayoutManager.HORIZONTAL, false);
  1. StaggeredGridLayoutManager 瀑布流 onBindViewHolder() 方法中为每一个 item 设置不同高度后,即实现瀑布流效果,可以在一个长度为与数据集合长度相同的集合中保存每一个 item 的高度,然后在 onBindViewHolder 方法中为 item 设置高度时就可以在这个集合中直接取值。
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL);

3) RecyclerView item 添加移除动画

RecyclerViewAdapter 的 notifyItemInserted(position) 和 notifyItemRemoved(position) 方法默认添加了添加
和移除 item 时的动画,注意!!!在添加数据时要将 item 高度的集合同时添加数据,要不然会报 数组越界异常

源码: https://github.com/renxuelong/RecycleViewDemo

二、RecyclerView 源码分析

1. setAdapter 方法

RecyclerView 的 setAdapter 方法中同 ListView 一样,会使用观察者模式,将 RecyclerView 作为观察者,Adapter 作为被观察者,在 Adapter 中一个集合添加 RecyclerView,在调用 Adapter 的 notify 系列方法时,会调用 RecyclerView 中的相应方法实现刷新数据

2. setLayoutManager 方法

不同于 ListView 自己实现 item 的排列和显示,RecyclerView 将这一任务交给了 LayoutManager ,由 LayoutManager 负责 item 的排列,更加解耦,同一个 RecyclerView 可以显示不同的效果

RecyclerView 的 setLayoutManager 方法中,会调用 requestLayout 方法,我们知道 requestLayout 会申请重新测量、布局、绘制整个过程,在 layout 过程中会调用 onLayout 方法,onLayout 方法中,会调用 dispatchLayout 方法,其中会调用对应 LayoutManager 的 onLayoutChildren 方法

// RecyclerView
public void setLayoutManager(LayoutManager layout) {
    ...
    mLayout = layout;
    requestLayout();
}

//RecyclerView
private void dispatchLayoutStep1() {
    ...
    mLayout.onLayoutChildren(mRecycler, mState);
    ...
}

由于 LayoutManager 的 onLayoutChildren 方法是空方法,其子类具体实现了该方法,以 LinearLayoutManager 为例

// LinearLayoutManager
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State         state) {
    ...
    fill(recycler, mLayoutState, state, false);
    ...
}

// LinearLayoutManager
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
            
    // 计算可用宽高
    
    // 开启循环向其中添加 item
    // 1.通过 layoutChunk 方法添加一个 item 
    // 2. 计算偏移量
    // 3. 计算剩余空间,如果还有剩余空间则继续循环,如果超出空间则跳出循环
}

// LinearLayoutManager
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    
    // 1. 获取要显示的 item
    View view = layoutState.next(recycler);
    
    // 2. 获取 LayoutParams
    LayoutParams params = (LayoutParams) view.getLayoutParams();        
    
    // 3. 将 item 添加到 RecyclerView addView --> recyclrView.add()
    addView(view);        
            
    // 4. 测量 item 
    measureChildWithMargins(view, 0, 0);
    
    // 5. 根据 item 的大小、装饰大小、布局方向(水平垂直)、RecyclerView 中已经占用空间 等信息计算 item 在 RecyclerView 中的坐标
    
    // 6. 根据上一步得到的坐标布局 item
    layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
                right - params.rightMargin, bottom - params.bottomMargin);
}

// LayoutManager
public void layoutDecorated(View child, int left, int top, int right, int bottom) {
    final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
    
    // 布局子 View 确定子 View 在 父 View 的坐标
    child.layout(left + insets.left, top + insets.top, right - insets.right,
            bottom - insets.bottom);
}

有上面代码以及注释可知,在 LayoutManager 的 onLayoutChildren 中,为 RecyclerView 填充了 item,并通过测量和布局过程确定了每一个 item 在 RecyclerView 中的位置。

上面代码中还有一个很重要的方法,ayoutState.next(recycler) 方法,刚才一笔带过,现在再来看这个获取 item 的重要方法

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

// Recycler 用来存放回收的 ViewHolder 以及提供 item
public final class Recycler {
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>();
    private ArrayList<ViewHolder> mChangedScrap = null;

    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

    private final List<ViewHolder>
            mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
    ...
    
    // 获取 item 的 View 对象的方法
    View getViewForPosition(int position, boolean dryRun) {
        // 1. 从废弃集合中获取需要的 ViewHolder
        // 2. 如果从废弃的集合中没有获取到,则通过 Adapter 创建
        if (holder == null) {
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
        }
        
        // 通过 mAdapter 设置要显示的 item 的 View 的显示数据
        mAdapter.bindViewHolder(holder, holder.mPosition);
        
        final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        final LayoutParams rvLayoutParams;
        if (lp == null) {
            // 设置默认 LayoutParams
            rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else if (!checkLayoutParams(lp)) {
            rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else {
            rvLayoutParams = (LayoutParams) lp;
        }
        rvLayoutParams.mViewHolder = holder;
        rvLayoutParams.mPendingInvalidate = fromScrap && bound;
        // 返回 item 要显示的 View
        return holder.itemView;
        
    }
}

先看第一步,通过 Recycler 对象的 getViewForPosition 方法获取 item,Recycler 中定义了很多集合,用来存放被回收的 ViewHolder 对象。这几集合的功能类似 ListView 的 RecycleBin 中的废弃 item 集合,在 ListView 中是存放废弃的 item 布局,并在 get 方法中将废弃的 item 返回。而在 Recycler 中存放的是废弃的 ViewHolder

从上面源码中可以看出,getViewForPosition 方法会先从废弃的从废弃的 ViewHolder 集合中获取 ViewHolder,如果没有对应的废弃 ViewHolder 则通过 mAdapter.createViewHolder 方法创建 ViewHolder ,还记得这个方法吗,这个方法是需要我们自定义 Adapter 时重写的方法,这个方法中根据当前位置 position 创建了 ViewHolder 并返回。

不管是从废弃的集合中获取到的 ViewHolder 还是从 mAdapter 中创建的,之后都会调用 mAdapter.bindViewHolder 方法,这个方法也是需要我们定义 Adapter 时重写的,根据 ViewHolder 和当前位置 position 的数据初始化 ViewHolder 中 itemView 的显示

最后在 ViewHolder 的 itemView 设置 LayoutParams 后将 holder.itemView 返回,也就是返回当前 item 处需要显示的 View

Recycler 获取 item 要显示 View 的步骤

  1. 从废弃的 ViewHolder 集合中获取
  2. 如果没有对应的废弃 ViewHolder 则通过 mAdapter 创建 ViewHolder
  3. 根据 ViewHolder 和当前位置 position 的数据初始化 ViewHolder 中 itemView 的显示
  4. 返回 View

三、总结

分析了使用 RecyclerView 的使用和原理,我们再来整理一下做一个总结

初始化 RecyclerView 并为其设置 Adapter 之后,就可以为其设置 LayoutManager 来确定 RecyclerView 怎么显示了,之后 LayoutManager 会根据数据量的大小和 RecyclerView 的空间大小通过循环来填充 RecyclerView。每次循环都会从 Recycler 中获取需要显示的 View,Recycler 中通过废弃 ViewHolder 的集合或者 Adapter 的 createViewHolder 方法得到 ViewHolder,然后调用 mAdapter 的 bindViewHolder 为 ViewHolder 中的 itemView 设置要显示的数据。得到要显示的 View 后 LayoutManager 会将该 View
添加到 RecyclerView 中并根据 View 的大小和 RecyclerView 剩余空间、布局方式等确定 View 在 RecyclerView 中的坐标,绘制完成之后界面上就会显示我们添加的 item 了

同 ListView 一样,在滑动产生时会回收已经滑出屏幕的 item 对应的 ViewHoler 到 Recycler 中,这样在成百上千条数据时也不会创建大量的 ViewHolder,保证了程序的性能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 143,639评论 1 302
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 61,591评论 1 258
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 95,050评论 0 213
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 41,169评论 0 180
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 48,976评论 1 258
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,876评论 1 178
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,476评论 2 273
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,229评论 0 167
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 29,095评论 6 234
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,579评论 0 213
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,356评论 2 215
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,698评论 1 232
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,272评论 0 32
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,168评论 2 214
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,605评论 3 210
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,645评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,035评论 0 166
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,609评论 2 232
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,698评论 2 233

推荐阅读更多精彩内容

  • 特别声明: 本文转发自:【江清清的博客】http://blog.csdn.net/developer_jiangq...
    _猜火车_阅读 37,132评论 11 70
  • 这篇文章分三个部分,简单跟大家讲一下 RecyclerView 的常用方法与奇葩用法;工作原理与ListView比...
    LucasAdam阅读 4,296评论 0 27
  • 简介: 提供一个让有限的窗口变成一个大数据集的灵活视图。 术语表: Adapter:RecyclerView的子类...
    酷泡泡阅读 5,024评论 0 16
  • 以前不相信一夜之间就长大的说法,也许是事情不发生自己的身上,就不会明白。 就在两周前,当时我还在学校,和室友在宿舍...
    要不一样的自己阅读 264评论 0 3
  • 出来近四年时间,每到假期回家都有一种日渐冷清的感觉。北方的冬天,树上的叶子落得毛都不剩,村子后面海拔三十米的小山包...
    青杏儿阅读 145评论 0 1