似曾相识的 RecyclerView

一.经常使用方法java

  RecyclerView 与 ListView、GridView 相似,都是能够显示同一种类型 View 的集合的控件。
    首先看看最简单的用法,四步走:android

  ①接入 build.gradle 文件中加入git

compile 'com.android.support:recyclerview-v7:24.0.0'

  ②建立对象github

RecyclerView recyclerview = (RecyclerView) findViewById(R.id.recyclerview);

  ③设置显示规则缓存

recyclerview.setLayoutManager(

new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));

  RecyclerView 将全部的显示规则交给一个叫 LayoutManager 的类去完成了。
  LayoutManager 是一个抽象类,系统已经为咱们提供了三个默认的实现类,分别是 LinearLayoutManager、 GridLayoutManager 、 StaggeredGridLayoutManager,从名字咱们就能看出来了,分别是,线性显示、网格显示、瀑布流显示。固然你也能够经过继承这些类来扩展实现本身的 LayougManager。ide

  ④ 设置适配器布局

recyclerview.setAdapter(adapter);

  适配器,同 ListView 同样,用来设置每一个item显示内容的。 性能

  一般,咱们写 ListView 适配器,都是首先继承 BaseAdapter,实现四个抽象方法,建立一个静态ViewHolder , getView() 方法中判断 convertView 是否为空,建立仍是获取 viewholder 对象。gradle

  而 RecyclerView 也是相似的步骤,首先继承RecyclerView.Adapter<VH>类,实现三个抽象方法,建立一个静态的 ViewHolder。不过 RecyclerView 的 ViewHolder 建立稍微有些限制,类名就是上面继承的时候泛型中声明的类名(好像反了,应该是上面泛型中的类名应该是这个holder的类名);而且 ViewHolder 必须继承自RecyclerView.ViewHolder类。动画

 1 public class DemoAdapter extends RecyclerView.Adapter<DemoAdapter.VH> {
 2     private List<Data> dataList;
 3     private Context context;
 4 
 5     public DemoAdapter(Context context, ArrayList<Data> datas) {
 6         this.dataList = datas;
 7         this.context = context;
 8     }
 9 
10     @Override
11     public VH onCreateViewHolder(ViewGroup parent, int viewType) {
12         return new VH(View.inflate(context, android.R.layout.simple_list_item_2, null));
13     }
14 
15     @Override
16     public void onBindViewHolder(VH holder, int position) {
17         holder.mTextView.setText(dataList.get(position).getNum());
18     }
19 
20     @Override
21     public int getItemCount() {
22         return dataList.size();
23     }
24 
25     public static class VH extends RecyclerView.ViewHolder {
26         TextView mTextView;
27         public VH(View itemView) {
28             super(itemView);
29             mTextView = (TextView) itemView.findViewById(android.R.id.text1);
30         }
31     }

  一些不经常使用的方法:

  • 瀑布流与滚动方向

    前面已经介绍过,RecyclerView实现瀑布流,能够经过一句话设置:recycler.setLayoutManager(new StaggeredGridLayoutManager(2, VERTICAL))就能够了。
    其中 StaggeredGridLayoutManager 第一个参数表示列数,就好像 GridView 的列数同样,第二个参数表示方向,能够很方便的实现横向滚动或者纵向滚动。 
    使用 demo 能够查看:Github 【RecyclerView简单使用

  • 添加删除 item 的动画

  同 ListView 每次修改了数据源后,都要调用 notifyDataSetChanged() 刷新每项 item 相似,只不过RecyclerView 还支持局部刷  新 notifyItemInserted(index);、 notifyItemRemoved(position)notifyItemChanged(position)

  在添加或删除了数据后,RecyclerView 还提供了一个默认的动画效果,来改变显示。同时,你也能够定制本身的动画效果:模仿 DefaultItemAnimator 或直接继承这个类,实现本身的动画效果,并调用recyclerview.setItemAnimator(new DefaultItemAnimator()); 设置上本身的动画。 
  使用 demo 能够查看:Github 【RecyclerView默认动画

  • LayoutManager的经常使用方法

  findFirstVisibleItemPosition() 返回当前第一个可见 Item 的 position
  findFirstCompletelyVisibleItemPosition() 返回当前第一个彻底可见 Item 的 position
  findLastVisibleItemPosition() 返回当前最后一个可见 Item 的 position
  findLastCompletelyVisibleItemPosition() 返回当前最后一个彻底可见 Item 的 position. 
  scrollBy() 滚动到某个位置。

  • adapter封装

  其实很早以前写过一篇关于 RecyclerView 适配器的封装,因此这再也不赘述了,传送门:RecyclerView的通用适配器
  使用 demo 能够查看:Github 【RecyclerView通用适配器演示

 

二.工做原理与ListView比较

 

类名 做用
RecyclerView.LayoutManager 负责Item视图的布局的显示管理
RecyclerView.ItemDecoration 给每一项Item视图添加子View,例如能够进行画分隔线之类
RecyclerView.ItemAnimator 负责处理数据添加或者删除时候的动画效果
RecyclerView.Adapter 为每一项Item建立视图
RecyclerView.ViewHolder 承载Item视图的子布局

一、LayoutManager工做原理

java.lang.Object  
   ↳ android.view.View  
        ↳ android.view.ViewGroup  
            ↳ android.support.v7.widget.RecyclerView

  首先是 RecyclerView 继承关系,能够看到,与 ListView 不一样,他是一个 ViewGroup。既然是一个 View,那么就不可少的要经历 onMeasure()onLayout()onDraw() 这三个方法。 实际上,RecyclerView 就是将onMeasure()onLayout() 交给了 LayoutManager 去处理,所以若是给 RecyclerView 设置不一样的 LayoutManager 就能够达到不一样的显示效果,由于onMeasure()onLayout()都不一样了嘛。

二、ItemDecoration 工做原理

  ItemDecoration 是为了显示每一个 item 之间分隔样式的。它的本质实际上就是一个 Drawable。当 RecyclerView 执行到 onDraw() 方法的时候,就会调用到他的 onDraw(),这时,若是你重写了这个方法,就至关因而直接在 RecyclerView 上画了一个 Drawable 表现的东西。 而最后,在他的内部还有一个叫getItemOffsets()的方法,从字面就能够理解,他是用来偏移每一个 item 视图的。当咱们在每一个 item 视图之间强行插入绘画了一段 Drawable,那么若是再照着本来的逻辑去绘 item 视图,就会覆盖掉 Decoration 了,因此须要getItemOffsets()这个方法,让每一个 item 日后面偏移一点,不要覆盖到以前画上的分隔样式了。

三、ItemAnimator

  每个 item 在特定状况下都会执行的动画。说是特定状况,其实就是在视图发生改变,咱们手动调用notifyxxxx()的时候。一般这个时候咱们会要传一个下标,那么从这个标记开始一直到结束,全部 item 视图都会被执行一次这个动画。

四、Adapter工做原理

  首先是适配器,适配器的做用都是相似的,用于提供每一个 item 视图,并返回给 RecyclerView 做为其子布局添加到内部。
可是,与 ListView 不一样的是,ListView 的适配器是直接返回一个 View,将这个 View 加入到 ListView 内部。而 RecyclerView 是返回一个 ViewHolder 而且不是直接将这个 holder 加入到视图内部,而是加入到一个缓存区域,在视图须要的时候去缓存区域找到 holder 再间接的找到 holder 包裹的 View。

五、ViewHolder

  每一个 ViewHolder 的内部是一个 View,而且 ViewHolder 必须继承自RecyclerView.ViewHolder类。 这主要是由于 RecyclerView 内部的缓存结构并非像 ListView 那样去缓存一个 View,而是直接缓存一个 ViewHolder ,在 ViewHolder 的内部又持有了一个 View。既然是缓存一个 ViewHolder,那么固然就必须全部的 ViewHolder 都继承同一个类才能作到了。

六、缓存与复用的原理

  RecyclerView 的内部维护了一个二级缓存,滑出界面的 ViewHolder 会暂时放到 cache 结构中,而从 cache 结构中移除的 ViewHolder,则会放到一个叫作 RecycledViewPool 的循环缓存池中。
  顺带一说,RecycledView 的性能并不比 ListView 要好多少,它最大的优点在于其扩展性。可是有一点,在 RecycledView 内部的这个第二级缓存池 RecycledViewPool 是能够被多个 RecyclerView 共用的,这一点比起直接缓存 View 的 ListView 就要高明了不少,但也正是由于须要被多个 RecyclerView 公用,因此咱们的 ViewHolder 必须继承自同一个基类(即RecyclerView.ViewHolder)。
  默认的状况下,cache 缓存 2 个 holder,RecycledViewPool 缓存 5 个 holder。对于二级缓存池中的 holder 对象,会根据 viewType 进行分类,不一样类型的 viewType 之间互不影响。

 

三 .源码解析

一、onMeasure

  既然是一个 View,咱们先从onMeasure()开始看。
  以前咱们就说了 RecyclerView 的 measure 和 layout 都是交给了 LayoutManager 去作的,来看一下为何:

1 if (mLayout.mAutoMeasure) {
2     final int widthMode = MeasureSpec.getMode(widthSpec);
3     final int heightMode = MeasureSpec.getMode(heightSpec);
4     final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
5             && heightMode == MeasureSpec.EXACTLY;
6     mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
7 } else {
8     mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
9 }

  不管是否启用 mAutoMeasure 最终都会执行到 mLayout.onMeasure() 方法中,而这个 mLayout 就是一个 LayoutManager 对象。

  咱们挑选 LinearLayoutManager 来看
  发现它并无onMeasure()方法,LinearLayoutManager 直接继承自 LayoutManager,因此又回到了父类 LayoutManager 中。

 1 void defaultOnMeasure(int widthSpec, int heightSpec) {
 2     // calling LayoutManager here is not pretty but that API is already public and it is better
 3     // than creating another method since this is internal.
 4     final int width = LayoutManager.chooseSize(widthSpec,
 5             getPaddingLeft() + getPaddingRight(),
 6             ViewCompat.getMinimumWidth(this));
 7     final int height = LayoutManager.chooseSize(heightSpec,
 8             getPaddingTop() + getPaddingBottom(),
 9             ViewCompat.getMinimumHeight(this));
10 
11     setMeasuredDimension(width, height);
12 }

  有一句很是奇葩的注释:在这里直接调用 LayoutManager 静态方法并不完美,由于自己就是在类内部,更好的办法调用一个单独的方法。但反正这段代码也已经公开了,大家本身看着办。。。。。。
  若是这不是历史遗留问题,那确定是临时工写的,你写的时候都意识到这问题了,你还把一大堆类都写在一个类里面,形成了 RecyclerView 一个类有一万多行代码。我猜你是为了类之间跨类调用方便一点,但是你就不能设置一个包访问权限,全部类成员方法都包内调用吗,一个类干了六个类的活,网上竟然还有人说这是高内聚的表现。

  接着是chooseSize()方法,很简单,直接根据测量值和模式返回了最适大小。

 1 public static int chooseSize(int spec, int desired, int min) {
 2     final int mode = View.MeasureSpec.getMode(spec);
 3     final int size = View.MeasureSpec.getSize(spec);
 4     switch (mode) {
 5         case View.MeasureSpec.EXACTLY:
 6             return size;
 7         case View.MeasureSpec.AT_MOST:
 8             return Math.min(size, Math.max(desired, min));
 9         case View.MeasureSpec.UNSPECIFIED:
10         default:
11             return Math.max(desired, min);
12     }
13 }

  紧接着是对子控件 measure ,调用了:dispatchLayoutStep2() 调用了相同的方法,子控件的 measure 在 layout 过程当中讲解

二、onLayout

  而后咱们来看 layout 过程. 在onLayout()方法中间接的调用到了这么一个方法:dispatchLayoutStep2(),在它之中又调用到了mLayout.onLayoutChildren(mRecycler, mState); 
  咱们重点看这个onLayoutChildren()方法。 
  这个方法在 LayoutManager 中的实现是空的,那么想必是在子类中实现了吧。仍是找LinearLayoutManager ,跟上面 measure 过程同样,调用了dispatchLayoutStep2() 跟进去发现这么一个方法:

fill(recycler, mLayoutState, state, false);

  onLayoutChildren() 中有一个很是重要的方法:fill()
  recycler,是一个全局的回收复用池,用于对每一个itemview回收以及复用提供支持。稍后会详细讲这个。

 1 while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
 2     layoutChunk(recycler, state, layoutState, layoutChunkResult);
 3     layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
 4 
 5     if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
 6         layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
 7         if (layoutState.mAvailable < 0) {
 8             layoutState.mScrollingOffset += layoutState.mAvailable;
 9         }
10         recycleByLayoutState(recycler, layoutState);
11     }
12 }

  fill() 做用就是根据当前状态决定是应该从缓存池中取 itemview 填充 仍是应该回收当前的 itemview。 
  其中,layoutChunk() 负责从缓存池 recycler 中取 itemview,并调用View.addView() 将获取到的 ItemView 添加到 RecyclerView 中去,并调用 itemview 自身的 layout 方法去布局 item 位置。
  同时在这里,还调用了measureChildWithMargins()来测绘子控件大小以及设置显示位置。这一步,咱们到下面的 draw 过程还要讲。
  而这所有的添加逻辑都放在一个 while 循环里面,不停的添加 itemview 到 recyclerview 里面,直到塞满全部可见区域为止。

三、onDraw

1 @Override
2 public void onDraw(Canvas c) {
3     super.onDraw(c);
4     final int count = mItemDecorations.size();
5     for (int i = 0; i < count; i++) {
6         mItemDecorations.get(i).onDraw(c, this, mState);
7     }
8 }

  在 onDraw() 中,除了绘制本身之外,还多调了一个mItemDecorations 的 onDraw() 方法,这个mItemDecorations 就是前面吐槽的分隔线的集合。
以前在讲 RecyclerView 的五虎上将的时候就讲过这个 ItemDecoration。 当时咱们还重写了一个方法叫getItemOffsets()目的是为了避免让 itemview 挡住分隔线。那他是在哪调用的呢? 
  还记得 layout 时说的那个measureChildWithMargins()吗,就是在这里:

1  public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
2     final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
3     widthUsed += insets.left + insets.right;
4     heightUsed += insets.top + insets.bottom;
5 
6     if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
7         child.measure(widthSpec, heightSpec);
8     }
9 }

  在 itemview measure 的时候,会把偏移量也计算进来,也就是说:其实 ItemDecoration 的宽高是计算在 itemview 中的,只不过 itemview 自己绘制区域没有那么大,留出来的地方正好的透明的,因而就透过 itemview 显示出了 ItemDecoration。那么就颇有意思了,若是我故意在 ItemDecoration 的偏移量中写成0,那么 itemview 就会挡住 ItemDecoration,而在 itemview 的增长或删除的时候,会短暂的消失(透明),这时候就又能够透过 itemview 看到 ItemDecoration 的样子。使用这种组合还能够作出意想不到的动画效果。

相关文章
相关标签/搜索