1.RecyclerView RecyclerView是Android 5.0推出的,是support-v7包中的新组件,它被用来代替ListView和GridView,并且能够实现瀑布流的布局,更加高级并且更加灵活,提供更为高效的回收复用机制,同时实现管理与视图的解耦合。RecyclerView提供了一种插拔式的体验,高度的解耦,异常的灵活,只需设置其提供的不同的LayoutManager,ItemAnimator和ItemDecoration,就能实现不同的效果。
首先我们看一下官方对它的介绍: A flexible view for providing a limited window into a large data set. 很简单,就一句话「为大量数据集提供一个有限的展示窗口的灵活视图」
RecyclerView的优点: ①支持局部刷新。 ②可以自定义item增删时的动画。 ③能够实现item拖拽和侧滑删除等功能。 ④默认已实现View的复用,而且回收机制更加完善。
2.RecyclerView的用法 ①首先要在build.gradle文件中添加引用 compile ‘com.android.support:recyclerview-v7:26.1.0’ ②主页面布局添加RecyclerView控件 <android.support.v7.widget.RecyclerView android:id="@+id/rv_list" android:layout_width=“match_parent” android:layout_height=“match_parent” /> ③item布局,为RecyclerView内的元素设定xml样式 <?xml version="1.0" encoding="utf-8"?>
< LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android” android:layout_width=“match_parent” android:layout_height=“wrap_content” android:orientation=“horizontal”> < TextView android:id="@+id/tv_content" android:layout_width=“match_parent” android:layout_height=“50dp” android:gravity=“center” android:text=“数据” /> < /LinearLayout> ④创建适配器继承RecyclerView.Adapter (1)创建适配器类继承自RecyclerView.Adapter,泛型传入RecyclerView.ViewHolder类。 (2)创建内部类即RecyclerView.ViewHolder类的子类,并初始化item的控件。 (3)重写RecyclerView.Adapter类的相关方法。 public class MyRecycleViewAdapter extends RecyclerView.Adapter<MyRecycleViewAdapter.MyHolder> { private List mList;//数据源 MyRecycleViewAdapter(List list) { mList = list; }
//创建ViewHolder并返回,后续item布局里控件都是从ViewHolder中取出 @Override public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_one, parent, false); MyHolder holder = new MyHolder(view); return holder; }
//通过方法提供的ViewHolder,将数据绑定到ViewHolder中 @Override public void onBindViewHolder(MyHolder holder, int position) { holder.textView.setText( mList.get(position).toString()); }
//获取数据源总的条数 @Override public int getItemCount() { return mList.size(); }
//自定义的ViewHolder,内部类,绑定控件 class MyHolder extends RecyclerView.ViewHolder{ TextView textView; public MyHolder(View itemView) { super(itemView); textView = itemView.findViewById(R.id.tv_content); } } } ⑤MainActivity.java中使用RecyclerView (1)获取RecyclerView对象 。 (2)初始化数据 。 (3)适配器实例化 。 (4)设置LayoutManager (5)设置Adapter 。 public class MainActivity extends AppCompatActivity { private RecyclerView mRecycleView; private MyRecycleViewAdapter mAdapter; private LinearLayoutManager mLinearLayoutManager;//布局管理器 private List mList;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mList = new ArrayList(); mRecycleView = findViewById(R.id.rv_list); //初始化数据 initData(mList); //创建布局管理器(可水平或竖直) mLinearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); mAdapter = new MyRecycleViewAdapter(mList); //设置布局管理器 mRecycleView.setLayoutManager( mLinearLayoutManager); mRecycleView.setAdapter(mAdapter); }
public void initData(List list) { for (int i = 1; i <= 40; i++) { list.add(“第” + i + “条数据”); } } } 使用方法挺简单的,几点需要注意: ①Adapter 使用时需要创建adapter类,该类继承于RecyclerView.Adapter< VH>,其中VH是adapter类中创建的一个继承于RecyclerView.ViewHolder的静态内部类。 适配器类主要有3个方法和1个自定义ViewHolder组成: (1)onCreateViewHolder: 创建ViewHolder并返回,后续item布局里控件都是从ViewHolder中取出。 (2)onBindViewHolder:通过方法提供的ViewHolder,将数据绑定到ViewHolder中。 (3)getItemCount:获取数据源总的条数。 (4)MyHolder :这是RecyclerView.ViewHolder的实现类,用于初始化item布局中的子控件。注意,在这个类的构造方法中需要传递item布局的View给父类 。 ②LayoutManager 布局管理器,通过不同的布局管理器来控制item的排列顺序,负责item元素的布局和复用。RecycleView提供了三种布局管理器: (1)LinearLayoutManager:线性布局,以垂直或水平滚动列表方式显示项目。 (2)GridLayoutManager:网格布局,在网格中显示项目。 (3)StaggeredGridLayoutManager:瀑布流布局,在分散对齐网格中显示项目。 使用方法: mRecycleView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false)); 如遇到特殊需求,也可以通过继承RecyclerView.LayoutManager来自定义LayoutManager,重写它的方法来实现所需要的效果。 ⑥事件监听 RecyclerView并没有提供现成的点击事件监听,需要我们自己去实现。可以在RecyclerView的Adapter中自定义一个接口,并创建一个供其他类设置监听的方法。 public class MyRecycleViewAdapter extends RecyclerView.Adapter<MyRecycleViewAdapter.MyHolder> { private List mList;//数据源 private OnItemClickListener onItemClickListener;
//供外部调用设置监听 public void setOnItemClickListener(OnItemClickListener onItemClickListener) { this.onItemClickListener = onItemClickListener; }
//自定义的接口 public interface OnItemClickListener { void onItemClick(View view, int position); }
//通过方法提供的ViewHolder,将数据绑定到ViewHolder中 @Override public void onBindViewHolder(final MyHolder holder, int position) { holder.textView.setText( mList.get(position).toString()); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (onItemClickListener != null) { onItemClickListener.onItemClick(v, holder.getAdapterPosition() + 1); } } }); } } 以上省略了部分与该内容无关的代码。当定义好接口后,我们在onBindViewHolder()方法中为holder.itemView(itemView是列表中的每一个item项)设置了点击事件监听,然后在onClick()中判断是否有用户传递过onItemClickListener实例进来,有的话会调用他的onItemClick(),将点击事件转移到我们的自定义接口上,传给外面的调用者。调用者代码如下: mAdapter.setOnItemClickListener(new MyRecycleViewAdapter.OnItemClickListener() { @Override public void onItemClick(View view, int position) { Toast.makeText(getApplicationContext(), “第” + position + “条数据”, Toast.LENGTH_SHORT).show(); } }); ⑦ItemAnimator 动画 RecyclerView可以通过mRecyclerView.setItemAnimator(ItemAnimator animator)来设置添加和移除时的动画效果。ItemAnimator是一个抽象类,RecyclerView为我们提供了一个ItemAnimator的实现类,即DefaultItemAnimator。 //设置动画效果 mRecycleView.setItemAnimator(new DefaultItemAnimator()); 在adapter中添加两个方法,用于添加和移除Item。这里要注意的是,更新数据集要用notifyItemInserted(position)与notifyItemRemoved(position) ,而不是notifyDataSetChanged(),否则没有动画效果。 // 添加数据 public void addItem() { mList.add(0, "new "); notifyItemInserted(0); }
//移除数据 public void removeItem(int position) { mList.remove(position); notifyItemRemoved(position); } 效果是按下底部“添加”按钮会在顶部插入数据,点击列表中的Item则删除该条数据。 如果我们对这种动画效果不满意,也可以去自定义各种动画效果。目前github上有许多开源的项目,例如RecyclerViewItemAnimators,我们可以直接去引用或学习它的动画效果。
3.RecyclerView的缓存机制 RecyclerView在大量数据时依然可以顺畅的滑动,这得益于它优秀的缓存机制。 我们知道,RecyclerView本身是一个ViewGroup,因此在滑动时就避免不了添加或移除子View(子View通过RecyclerView#Adapter中的onCreateViewHolder创建),如果每次使用子View都要去重新创建,肯定会影响滑动的流畅性,所以RecyclerView通过Recycler来缓存的是ViewHolder(内部包含子View),这样在滑动时可以复用子View,某些条件下还可以复用子View绑定的数据。所以本质上来说,RecyclerView之所以能够实现顺畅的滑动效果,是因为缓存机制,因为缓存减少了重复绘制View和绑定数据的时间,从而提高了滑动时的性能。
我们先了解下Recycler的缓存结构是怎样的,先了解两个专业词汇: ①Scrap (view):在布局期间进入临时分离状态的子视图。废弃视图可以重复使用,而不会与父级RecyclerView完全分离,如果不需要重新绑定,则不进行修改,如果视图被视为脏,则由适配器修改。(这里的脏怎么理解呢?就是指那些在展示之前必须重新绑定的视图,比如一个视图原来展示的是“张三”,之后需要展示“李四”了,那么这个视图就是脏视图,需要重新绑定数据后再展示的。) ②Recycle (view):先前用于显示适配器特定位置的数据的视图可以放置在高速缓存中以供稍后重用再次显示相同类型的数据。这可以通过跳过初始布局或构造来显着提高性能。
首先我们先看一个RV(RecyclerView在后文简称RV)的内部类Recycler。 public final class Recycler { final ArrayList< ViewHolder> mAttachedScrap = new ArrayList<>(); ArrayList< ViewHolder> mChangedScrap = null; final ArrayList< ViewHolder> mCachedViews = new ArrayList< ViewHolder>(); RecycledViewPool mRecyclerPool; private ViewCacheExtension mViewCacheExtension; …… 省略 …… } 就是这个类掌握着RV的缓存大权,从上面的代码片段我们可以看到这个类声明了五个成员变量。我们一个个的来说一下: ①mAttachedScrap:我们可以看到这个变量是个存放ViewHolder对象的ArrayList,这一级缓存是没有容量限制的,只要符合条件的我来者不拒,全收了。前面讲两个专业术语的时候提到了Scrap,这个就属于Scrap中的一种,这里的数据是不做修改的,不会重新走Adapter的绑定方法。 ②mChangedScrap:这个变量和上边的mAttachedScrap是一样的,唯一不同的从名字也可以看出来,它存放的是发生了变化的ViewHolder,如果使用到了这里的缓存的ViewHolder是要重新走Adapter的绑定方法的。 ③mCachedViews:这个变量同样是一个存放ViewHolder对象的ArrayList,但是这个不同于上面的两个里面存放的是dettach掉的视图,它里面存放的是已经remove掉的视图,已经和RV分离的关系的视图,但是它里面的ViewHolder依然保存着之前的信息,比如position、和绑定的数据等等。这一级缓存是有容量限制的,默认是2。 ④mRecyclerPool:这个变量呢本身是一个类,跟上面三个都不一样。这里面保存的ViewHolder不仅仅是removed掉的视图,而且是恢复了出厂设置的视图,任何绑定过的痕迹都没有了,想用这里缓存的ViewHolder那是铁定要重新走Adapter的绑定方法了。而且我们知道RV支持多布局,所以这里的缓存是按照itemType来分开存储的,我们来大致的看一下它的结构: public static class RecycledViewPool { private static final int DEFAULT_MAX_SCRAP = 5; static class ScrapData { ArrayList< ViewHolder> mScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; …… 省略 …… } SparseArray< ScrapData> mScrap = new SparseArray<>(); …… 省略后面代码 …… } 首先我们看到一个常量‘DEFAULT_MAX_SCRAP’,这个就是缓存池定义的一个默认的缓存数,当然这个缓存数我们是可以自己设置的。而且这个缓存数量不是指整个缓存池只能缓存这么多,而是每个不同itemType的ViewHolder的缓存数量。 接着往下看,我们看到一个静态内部类ScrapData,这里我们只看跟缓存相关的两个变量,先说mMaxScrap,前面的常量赋值给了它,这也就印证了我们前面说的这个缓存数量是对应每一种类型的ViewHolder的。再来看这个mScrapHeap变量,熟悉的一幕又来了,同样是一个缓存ViewHolder对象的ArrayList,它的容量默认是5. 最后我们看到mScrap这个变量,它是一个存储我们上面提到的ScrapData类的对象的SparseArray,这样我们这个RecyclerPool就把不同itemType的ViewHolder按类型分类缓存了起来。 ⑤mViewCacheExtension:这一级缓存是留给开发者自由发挥的,官方并没有默认实现,它本身是null。
综上所述,RecyclerView的缓存分为四级,优先级从高到底依次为: ①一级缓存mAttachedScrap&mChangedScrap :缓存当前还在屏幕中的ViewHolder。 mAttachedScrap存储的是当前屏幕中的ViewHolder,mAttachedScrap的对应数据结构是ArrayList,在调用LayoutManager的onLayoutChildren方法时对views进行布局,此时会将RecyclerView上的Views全部暂存到该集合中,该缓存中的ViewHolder的特性是,如果和RV上的position或者itemId匹配上了那么可以直接拿来使用的,无需调用onBindViewHolder方法。 mChangedScrap和mAttachedScrap属于同一级别的缓存,不过mChangedScrap的调用场景是notifyItemChanged和notifyItemRangeChanged,只有发生变化的ViewHolder才会放入到mChangedScrap中。mChangedScrap缓存中的ViewHolder是需要调用onBindViewHolder方法重新绑定数据的。 ②二级缓存mCachedViews:缓存滑动时即将与RecyclerView分离的ViewHolder,默认最大2个。 mCachedViews缓存滑动时即将与RecyclerView分离的ViewHolder,按子View的position或id缓存,默认最多存放2个。mCachedViews对应的数据结构是ArrayList,但是该缓存对集合的大小是有限制的。 该缓存中ViewHolder的特性和mAttachedScrap中的特性是一样的,只要position或者itemId对应就无需重新绑定数据。开发者可以调用setItemViewCacheSize(size)方法来改变缓存的大小,该层级缓存触发的一个常见的场景是滑动RecyclerView。当然调用notify()也会触发该缓存。 ③三级缓存ViewCacheExtension:自定义实现的缓存。 ViewCacheExtension是需要开发者自己实现的缓存,基本上页面上的所有数据都可以通过它进行实现。 ④四级缓存RecycledViewPool :根据ViewType来缓存ViewHolder,每个ViewType的数组大小为5,可以动态的改变。 ViewHolder缓存池,本质上是一个SparseArray,其中key是ViewType(int类型),value存放的是 ArrayList< ViewHolder>,默认每个ArrayList中最多存放5个ViewHolder。
注意: (1)mAttachedScrap它表示存储的是当前还在屏幕中ViewHolder。实际上是从屏幕上分离出来的ViewHolder,但是又即将添加到屏幕上去的ViewHolder。比如说,RecyclerView上下滑动,滑出一个新的Item,此时会重新调用LayoutManager的onLayoutChildren方法,从而会将屏幕上所有的ViewHolder先scrap掉(含义就是废弃掉),添加到mAttachedScrap里面去,然后在重新布局每个ItemView时,会从优先mAttachedScrap里面获取,这样效率就会非常的高。这个过程不会重新onBindViewHolder (2)mCachedViews默认大小为2,不过通常是3,3由默认的大小2 + 预取的个数1。所以在RecyclerView在首次加载时,mCachedViews的size为3(这里以LinearLayoutManager的垂直布局为例)。通常来说,可以通过RecyclerView的setItemViewCacheSize方法设置大小,但是这个不包括预取大小;预取大小通过LayoutManager的setItemPrefetchEnabled方法来控制。
4.源码分析
|