RecyclerView 集成自 ViewGroup 。RecyclerView是Android-support-V7版本中新增的一个Widgets,官方对于它的介绍是:RecyclerView是ListView的升级版本,更加先进和灵活。html
Android L 以后,Google 提供了RecyclerView视图化控件,5.0以前若是想要使用的话,能够添加V7包以向下兼容,提供更全面的API和更灵活的布局管理。java
机制:layoutmanager 从Recycle 中获取已经绑定数据的 Item 显示,并将再也不须要的Item 丢给Recycler 回收;Adapter 负责生成新Item 并将其绑定好数据,供Recyle获取;Recycler 就是子 Item 的一个缓存池。android
主要负责:布局子视图、滚动子视图在滚动过程当中根据子视图在布局中所处的位置,决定什么时候添加子视图和删除子视图。
涉及到的API:git
Adapter 有几个抽象方法须要子类实现:github
即提供新的,也回收旧的(强大就强大在View的循环回收利用)数组
与Recycler相关:缓存
class MyAdapter extends BaseAdapter { …… @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { holder = new ViewHolder(); …… //初始化convertView和ViewHolder convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } …… } } static class ViewHolder { TextView txvTitle; }
class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> { @Override public int getItemCount() { return 0; } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return null; } @Override public void onBindViewHolder(MyViewHolder holder, int position) { } class MyViewHolder extends RecyclerView.ViewHolder { //TODO 初始化控件 public MyViewHolder(View itemView) { super(itemView); } } }
最基础的adapter,应用中列表会有许多,有多少个列表就会有几个适配器,能够根据需求封装adapter,以方便使用。ide
做为一枚控件,要引发开发者使用的欲望天然先是从显示效果看起(看脸的世界),ListView 你们对效果已经很熟悉了,这里直接跳过,而做为 RecyclerView,它能带给效果要比 ListView 强大得多,以下图函数
Android 默认提供的 RecyclerView 就能支持 线性布局、网格布局、瀑布流布局 三种(这里咱们暂且不提代码细节,后文再说),并且同时还可以控制横向仍是纵向滚动。怎样,从效果上足以碾压 ListView 有木有。工具
到此,展现效果上的差距一目了然。
固然,一个控件咱们不能彻底只看效果,关键仍是要看实用性,看看有没有方便咱们调用的 API提升咱们的开发效率。因此,接下来咱们就从各个方面来看看 RecyclerView 和 ListView 在提供的API调用上的一些实践比较。
ListView 的基础使用你们再熟悉不过,其使用的关键点主要以下:
因为 ListView 已经老生常谈,因此此处就不去写示例代码了。 RecyclerView 基础使用关键点一样有两点:
示例代码大体以下:
// 第一步:继承重写 RecyclerView.Adapter 和 RecyclerView.ViewHolder public class AuthorRecyclerAdapter extends RecyclerView.Adapter<AuthorRecyclerAdapter.AuthorViewHolder> { ... @Override public AuthorViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { ... return viewHolder; } @Override public void onBindViewHolder(AuthorViewHolder holder, int position) { ... } @Override public int getItemCount() { if (mData == null) { return 0; } return mData.size(); } class AuthorViewHolder extends RecyclerView.ViewHolder { ... public AuthorViewHolder(View itemView) { super(itemView); ... } } } mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); mRecyclerAdapter = new AuthorRecyclerAdapter(mData); // 第二步:设置布局管理器,控制布局效果 LinearLayoutManager linearLayoutManager = new LinearLayoutManager(RecyclerDemoActivity.this); linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); mRecyclerView.setLayoutManager(linearLayoutManager); mRecyclerView.setAdapter(mRecyclerAdapter);
从基础使用上看,咱们明显能够看出,RecyclerView 相比 ListView 在基础使用上的区别主要有以下几点:
在最开始就提到,RecyclerView 可以支持各类各样的布局效果,这是 ListView 所不具备的功能,那么这个功能如何实现的呢?其核心关键在于 RecyclerView.LayoutManager 类中。从前面的基础使用能够看到,RecyclerView 在使用过程当中要比 ListView 多一个 setLayoutManager 步骤,这个 LayoutManager 就是用于控制咱们 RecyclerView 最终的展现效果的。
而 LayoutManager 只是一个抽象类而已,系统已经为咱们提供了三个相关的实现类 LinearLayoutManager(线性布局效果)、GridLayoutManager(网格布局效果)、StaggeredGridLayoutManager(瀑布流布局效果)。若是你想用 RecyclerView 来实现本身 YY 出来的一种效果,则应该去继承实现本身的 LayoutManager,并重写相应的方法,而不该该想着去改写 RecyclerView。关于 LayoutManager 的使用有下面一些常见的 API(有些在 LayoutManager 实现的子类中)
canScrollHorizontally();//可否横向滚动 canScrollVertically();//可否纵向滚动 scrollToPosition(int position);//滚动到指定位置 setOrientation(int orientation);//设置滚动的方向 getOrientation();//获取滚动方向 findViewByPosition(int position);//获取指定位置的Item View findFirstCompletelyVisibleItemPosition();//获取第一个彻底可见的Item位置 findFirstVisibleItemPosition();//获取第一个可见Item的位置 findLastCompletelyVisibleItemPosition();//获取最后一个彻底可见的Item位置 findLastVisibleItemPosition();//获取最后一个可见Item的位置
上面仅仅是列出一些经常使用的 API 而已,更多的 API 能够查看官方文档,一般你想用 RecyclerView 实现某种效果,例如指定滚动到某个 Item 位置,可是你在 RecyclerView 中又找不到能够调用的 API 时,就能够跑到 LayoutManager 的文档去看看,基本都在那里。另外还有一点关于瀑布流布局效果 StaggeredGridLayoutManager 想说的,看到网上有些文章写的示例代码,在设置了 StaggeredGridLayoutManager 后仍要去 Adapter 中动态设置 View 的高度,才能实现瀑布流,这种作法是彻底错误的,之因此 StaggeredGridLayoutManager 的瀑布流效果出不来,基本是 item 布局的 xml 问题以及数据问题致使。若是要在 Adapter 中设置 View 的高度,则彻底违背了 LayoutManager 的设计理念了。
ListView 提供了 setEmptyView 这个 API 来让咱们处理 Adapter 中数据为空的状况,只需轻轻一 set 就能搞定一切。代码设置和效果以下
mListView = (ListView) findViewById(R.id.listview);
mListView.setEmptyView(findViewById(R.id.empty_layout));//设置内容为空时显示的视图
而 RecyclerView 并无提供此类 API,因此,这些工做须要本身来干。虽然说这类逻辑并不复杂,可是做为一个有追求的程序猿,能偷懒仍是要想着偷懒的嘛...
在 ListView 的设计中,存在着 HeaderView 和 FooterView 两种类型的视图,而且系统也提供了相应的 API 来让咱们设置
使用 HeaderView 和 FooterView 的好处在于,当咱们指向在 ListView 的头部或者底部添加一个 View 的时候(例如:添加一个下拉刷新视图,底部加载更多视图),咱们能够不用影响到 Adapter 的编写,使用起来至关方便。而到了 RecyclerView 中,翻来翻去你都不会看到相似 addFooterView 、 addFooterView 这种 API,是的,没错,压根就没有...这也是 RecyclerView 让我以为很鸡肋的地方,按道理说应该是使用频率很高的 API,竟然都不给我(一脸懵逼)。那有木有解决方法呢,确定有,系统不给就本身动手丰衣足食呗。我想到的方法比较笨,就是在 Adapter 中提供三种类型(Header,Footer以及普通Item)的 Type 和 View,可是这种方法写起来很麻烦,对 Adapter 的影响很大,改动的代码量多而且也容易产生BUG。这里须要吹一下鸿洋老师的解决方案了,你们能够看他的文章:优雅的为RecyclerView添加HeaderView和FooterView 。他的实现思路是经过装饰者模式来扩充 Adapter 的功能,从而实现添加 HeaderView 和 FooterView,而且不影响 Adapter 的编写工做,牛逼的是还能支持多个 HeaderView 和 FooterView (虽然我暂时想不到有什么应用场景,哈哈,不过先记着,之后说不定有用)。这是我目前看到的最同意的方案了,若是你有更 nice 的方案,也欢迎给我留言。
在 ListView 中,说到刷新不少童鞋会记得 notifyDataSetChanged() ,可是说到局部刷新估计有不少童鞋就知道得比较少了。咱们知道在更新了 ListView 的数据源后,须要经过 Adapter 的 notifyDataSetChanged 来通知视图更新变化,这样作比较的好处就是调用简单,坏处就是它会重绘每一个 Item,但实际上并非每一个 Item 都须要重绘。最多见的,例如:朋友圈点赞,点赞只是更新当前点赞的Item,并不须要每一个 Item 都更新。然而 ListView 并无提供局部刷新刷新某个 Item 的 API 给咱们,一样本身自足,套路大体以下方的 updateItemView:
public class AuthorListAdapter extends BaseAdapter { ... @Override public View getView(int position, View convertView, ViewGroup parent) { ... return convertView; } /** * 更新Item视图,减小没必要要的重绘 * * @param listView * @param position */ public void updateItemView(ListView listView, int position) { //换算成 Item View 在 ViewGroup 中的 index int index = position - listView.getFirstVisiblePosition(); if (index >= 0 && index < listView.getChildCount()) { //更新数据 AuthorInfo authorInfo = mAuthorInfoList.get(position); authorInfo.setNickName("Google Android"); authorInfo.setMotto("My name is Android ."); authorInfo.setPortrait(R.mipmap.ic_launcher); //更新单个Item View itemView = listView.getChildAt(index); getView(position, itemView, listView); } } }
便可实现刷新单个 Item 的效果
RecyclerView.Adapter 则咱们提供了 notifyItemChanged 用于更新单个 Item View 的刷新,咱们能够省去本身写局部更新的工做。
实现效果以下
若是你细心观察上面 ListView 和 RecyclerView 局部更新 Item 的效果,你会发现相比 ListView 而言, RecyclerView 在作局部刷新的时候有一个渐变的动画效果。这也是 RecyclerView 相对很是值得一提的地方,做为 ListView 自身并无为咱们提供封装好的 API 来实现动画效果切换。因此,若是要给 ListView 的 Item 加动画,咱们只能本身经过属性动画来操做 Item 的视图。 Github 也有不少封装得好好的开源库给咱们用,如:ListViewAnimations 就封装了大量的效果供咱们玩耍,童鞋们能够自行学习一下
ListViewAnimations 主要大体实现方式是经过装饰者模式来扩充 Adapter ,并结合属性动画 Animator 来添加动画效果。相比之下,RecyclerView 则为咱们提供了不少基本的动画 API ,以下方的增删移改
简单的调用便可实现相应的效果,用起来方便不少,视觉交互上也会更好些
若是你对动画效果有追求,以为系统提供的并不能知足你的需求,也能够经过相应接口实现本身的动画效果,方式也很是简单,继承 RecyclerView.ItemAnimator 类,并实现相应的方法,再调用 RecyclerView 的 setItemAnimator(RecyclerView.ItemAnimator animator) 方法设置完便可实现自定义的动画效果。
系统也为咱们提供了两个默认的动画实现:SimpleItemAnimator 和 DefaultItemAnimator。而 RecyclerView 在不手动调用 setItemAnimator 的状况下,则默认用了内置的 DefaultItemAnimator 。
固然编写自定义的 ItemAnimator 也是须要必定工做量的,这里一样为你们介绍一个针对 RecyclerView 开源的动画库:recyclerview-animators。其内部封装了大量的动画效果给供咱们调用。
若是想要学习怎么写一个自定义 ItemAnimator ,上面介绍的开源库的代码一样不容错过。哦,对了,若是谈到动画效果,还有一个很关键的类不得不提,那就是 ItemTouchHelper 。
ItemTouchHelper 是系统为咱们提供的一个用于滑动和删除 RecyclerView 条目的工具类,用起来也是很是简单的,大体两步:
示例代码大体以下:
//ItemTouchHelper 用于实现 RecyclerView Item 拖曳效果的类 ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() { @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { //actionState : action状态类型,有三类 ACTION_STATE_DRAG (拖曳),ACTION_STATE_SWIPE(滑动),ACTION_STATE_IDLE(静止) int dragFlags = makeFlag(ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);//支持上下左右的拖曳 int swipeFlags = makeMovementFlags(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);//表示支持左右的滑动 return makeMovementFlags(dragFlags, swipeFlags);//直接返回0表示不支持拖曳和滑动 } /** * @param recyclerView attach的RecyclerView * @param viewHolder 拖动的Item * @param target 放置Item的目标位置 * @return */ @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { int fromPosition = viewHolder.getAdapterPosition();//要拖曳的位置 int toPosition = target.getAdapterPosition();//要放置的目标位置 Collections.swap(mData, fromPosition, toPosition);//作数据的交换 notifyItemMoved(fromPosition, toPosition); return true; } /** * @param viewHolder 滑动移除的Item * @param direction */ @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { int position = viewHolder.getAdapterPosition();//获取要滑动删除的Item位置 mData.remove(position);//删除数据 notifyItemRemoved(position); } }); itemTouchHelper.attachToRecyclerView(mRecyclerView);
虽然代码中有注释,但仍是稍稍解释一下,主要重写的是 getMovementFlags 、 onMove 、 onSwiped 三个抽象方法,getMovementFlags 用于告诉系统,咱们的 RecyclerView 究竟是支持滑动仍是拖曳。如上面的示例代码,就是表示着同时支持上下左右四个方向的拖曳和左右两个方向的滑动效果。若是时滑动,则 onSwiped 会被回调,若是是拖曳 onMove 会被回调。咱们再到其中实现相应的业务操做便可。最终效果以下
想一想咱们之前用 ListView 的时候要怎么作,RecyclerView 真的爽多了。
ListView 为咱们准备了几个专门用于监听 Item 的回调接口,如单击、长按、选中某个 Item 等
说实话,其实我并不大喜欢这样的设计,如 setOnItemClickListener ,在咱们不添加 HeaderView 和 FooterView 的时候,咱们能够经过回调参数中的 position 去拿到数据源列表中对应 Item 的数据。
可是,添加了 HeaderView 和 FooterView 以后就不同了,ListView 会把 HeaderView 和 FooterView 算入 position 内。假设你原先在 onItemClick 回调方法中写了 mDataList.get(position) 这样的业务代码而且这段代码运行良好许久,但在某天你忽然加了个 HeaderView 后,这段代码就开始变的有问题了,此时由于 HeaderView 占用的位置算入了 position 以内,因此 position 的最大值其实是大于 mDataList 包含元素的个数值的,所以代码会报数组越界的错误。固然,咱们能够去避免这种问题的发生,就是不经过 position 来获取数据,二是经过回调方法中的 id 。
这样就不会受到添加 HeaderView 和 FooterView 的影响了,这个 id 的值就是来自咱们编写好的 Adapter 中的 getItemId 函数中返回的 id,使用 IDE 生成此函数时,默认是返回0,须要将 position 做为 Item 的 id 返回。
并同时在 onItemClick 中判断 id 是否值为 -1,由于 HeaderView 和 FooterView 的返回值就是 -1。前面讲到我并不大喜欢 setOnItemClickListener 这种设计,除了由这些因素的影响外,更关键的是我的认为针对 Item 的事件实际上写在 getView 方法中会更加合适,如 setOnItemClickListener 我更喜欢用在 getView 中为每一个 convertView 设置 setOnClickListener 的方式去取代它。
而再来看看 RecyclerView ,它并无像 ListView 提供太多关于 Item 的某种事件监听,惟一的就是 addOnItemTouchListener
API 的名字言简意赅,就是监听 Item 的触摸事件。若是你想要拥有 ListView 那样监听某个 Item 的某个操做方法,能够看看这篇文章 RecyclerView没法添加onItemClickListener最佳的高效解决方案 ,做者的实现思路就是经过 addOnItemTouchListener 和系统提供的 GestureDetector 手势判断结合实现的。不过,我仍是更喜欢原先本身用惯的方式,虽然会被人吐槽 new 出了大量的监听器,但我的以为这样封装会更好(哈哈,也换你们吐槽这种方式的其余劣处,看看我是否是须要改改了)。
OK,关于 RecyclerView 和 ListView 一些经常使用的功能和 API 的对比,就大体到此。最后再来谈谈 Android L 开始以后,对 RecyclerView 和 ListView 的使用存在什么影响。
熟悉 Android 触摸事件分发机制的童鞋确定知道,Touch 事件在进行分发的时候,由父 View 向它的子 View 传递,一旦某个子 View 开始接收进行处理,那么接下来全部事件都将由这个 View 来进行处理,它的 ViewGroup 将不会再接收到这些事件,直到下一次手指按下。而嵌套滚动机制(NestedScrolling)就是为了弥补这一机制的不足,为了让子 View 能和父 View 同时处理一个 Touch 事件。关于嵌套滚动机制(NestedScrolling),实现上相对是比较复杂的,此处就不去拓展说明,其关键在于 NestedScrollingChild 和 NestedScrollingParent 两个接口,以及系统对这两个接口的实现类 NestedScrollingChildHelper 和 NestedScrollingParentHelper 你们能够查阅相关的资料。可能提及来太抽象了,这里拿一个简单的示例效果来讲明好了,以下方是用 CollapsingToolbarLayout 和 RecyclerView 搭配的效果:
一开始上面一大块区域就是 CollapsingToolbarLayout ,下方的列表是 RecyclerView ,固然 RecyclerView 向上滑动时,CollapsingToolbarLayout 可以同时网上收缩,直到只剩下顶部的 Toolbar。之因此可以实现这种效果,就是彻底依赖于嵌套滚动机制,若是没有这套机制,按照原有的触摸事件分发逻辑, RecyclerView 内部已经把 Touch 事件消耗掉了,彻底没法引发顶部的 CollapsingToolbarLayout 产生联动收缩的效果。咱们能够查看 RecyclerView 的代码实现,发现它已经实现了 NestedScrollingChild 接口
若是在其余代码布局都不变的状况下,咱们把 RecyclerView 替换成 ListView ,则没法产生上面图中的动态效果,由于 ListView 并不支持嵌套滚动机制,事件在 ListView 内部已经被消耗且没法传递出来,你们能够自行尝试验证一下。对下方 AppBarLayout 的使用也是同理。
关于 AppBarLayout 和 CollapsingToolbarLayout,它们并非什么第三方控件,而是 Android 官方提供的 MaterialDesign 设计风格的控件,你们能够在官方文档中搜索到它们的资料,若是你用过 Android 原生系统,你能够在通信录等系统内置应用看到它们的身影。若是你想使用相似 AppBarLayout 、 CollapsingToolbarLayout 这种须要嵌套滚动的机制才能达到效果的控件,那么 RecyclerView 将是你的不二之选,由于 ListView 在此根本没法发挥做用。一样的,ScrollView 也是不支持嵌套滚动机制,可是你可使用 NestedScrollView 。