最清晰的 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,保证了程序的性能。

推荐阅读更多精彩内容

  • 特别声明: 本文转发自:【江清清的博客】http://blog.csdn.net/developer_jiangq...
    _猜火车_阅读 6,555评论 9 55
  • 这篇文章分三个部分,简单跟大家讲一下 RecyclerView 的常用方法与奇葩用法;工作原理与ListView比...
    LucasAdam阅读 2,668评论 0 25
  • 起深入浅出这名字的时候我是慎重又慎重的,生怕被人骂标题党,写的什么破玩意还敢说深入浅出。所以还是请大家不要抱着太高...
    渔农阅读 3,381评论 7 32
  • 简介: 提供一个让有限的窗口变成一个大数据集的灵活视图。 术语表: Adapter:RecyclerView的子类...
    酷泡泡阅读 1,638评论 0 13
  • 以前不相信一夜之间就长大的说法,也许是事情不发生自己的身上,就不会明白。 就在两周前,当时我还在学校,和室友在宿舍...
    要不一样的自己阅读 40评论 0 0