Android RecyclerView与ListView比较

RecyclerView 概述

RecyclerView 集成自 ViewGroup 。RecyclerView是Android-support-V7版本中新增的一个Widgets,官方对于它的介绍是:RecyclerView是ListView的升级版本,更加先进和灵活。

Android L 之后,Google 提供了RecyclerView视图化控件,5.0之前如果想要使用的话,可以添加V7包以向下兼容,提供更全面的API和更灵活的布局管理。

RecyclerView 做了什么

  • 类似ListView;
  • 类似GridView;
  • 横向ListView;
  • 横向GridView;
  • 瀑布流式布局

RecyclerView 组成

  • RecyclerView.LayoutManager
  • RecyclerView.Recyler
  • RecyclerView.Adapter
  • RecyclerView.ViewHolder
  • RecyclerView.ItemDecoration
  • RecyclerView.ItemAnimator

机制:layoutmanager 从Recycle 中获取已经绑定数据的 Item 显示,并将不再需要的Item 丢给Recycler 回收;Adapter 负责生成新Item 并将其绑定好数据,供Recyle获取;Recycler 就是子 Item 的一个缓存池。

RecyclerView.LayoutManager -- 管理子View布局的一个组件

主要负责:布局子视图、滚动子视图在滚动过程中根据子视图在布局中所处的位置,决定何时添加子视图和删除子视图。
涉及到的API:

  • 获取布局尺寸
    setRecyclerView()、setMeasureSpecs()、setMeasuredDimensionFromChildren()
  • 可以设置和获取方向的水平或垂直
    canScrollHorizontally()、canScrollVertically()、setOrientation()、getOrientation()
  • 滑动状态改变时RecyclerView调用方法通知LayoutManager
    onScrollStateChanged(()
  • 增、删、移动子View
    addView()、removeView()、moveView()、removeAllViews()、removeViewAt()
  • 获取指定位置的View
    getPosition()、getChildAt()、findViewByPosition()
  • 获取可见子View及全部子View的个数
    getChildCount()、getItemCount()
  • 移除数据后调用了recycler进行数据的缓存
    detachView()、detachAndScrapAttachedViews()、removeAndRecycleAllViews()、scrapOrRecycleView()

RecyclerView.Adapter 负责数据、Item的生成和数据的绑定

Adapter 有几个抽象方法需要子类实现:

  • 返回一个ViewHolder 封装实例
    onCreateViewHolder()
  • 根据ViewHolder对应的View进行数据绑定
    onBindViewHolder()
  • 获取总数
    getItemCount()
  • 获取不同Type类型的View,为添加 header 和 footer 预留接口
    getItemViewType()
  • 指定位置的 item 内容发生了变化
    notifyItemChanged()
  • 在指定的位置处插入一个 Item
    notifyItemInserted()
  • 指定位置的两个 Item 进行交换
    notifyItemMoved(int, int)

RecyclerView.Recyler 负责 Item 的缓存

即提供新的,也回收旧的(强大就强大在View的循环回收利用)

  • RecyclerView 的二级缓存:
    有两个缓存:Scrap 和 Recycle ,Scrap 中文是废料的意思。Recycle 对应是回收的意思。
    Scrap 缓存是指里面缓存的View 是接下来需要用到的,不需要新绑定数据,是一个轻量级的缓存集合,而Recycle 的缓存的 View 为里面的数据需要重新绑定,都放在RecyclerViewPool 池中,都需要通过 Adapter 重新绑定数据。
  • RecyclerView 缓存的两种方式:
    Detach 和 Remove ,Detach 的View 放在Scrap 缓存中,Remove 掉的View 放在 RecyclerViewPool缓存池中。
  • 使用场景:
    反复去将View移除并且马上又要添加进去时,选择Detach 方式,通过方法 detachAndScrapView()实现。使用频率很低,屏幕中不显示的时候使用Remove 的方式,通过方法 removeAndRecycleView()实现。
  • 复用流程:
    当我们去获取一个新的View时,首先去检查Scrap 缓存是否有对应的 position 的View ,如果有直接用;如果没有,则从RecyclerViewPool缓存池中取,并且会回调Adapter 的onBindViewHolder 方法(如果Recycle 缓存为空,还会调用onCreateViewHolder方法),最后再将绑定好新数据的View返回。

相关方法

与Recycler相关:

  • 获取缓存最大阀值,阀值为2
    setItemViewCacheSize()
  • 从缓存中取Item
    getViewForPosition()
  • 获取Scrap 缓存列表
    getScrapList()
  • 从Layoutmanager 回收Item
    recyclerView()
  • 回收后,缓存类型的内部处理逻辑
    recyclerViewHolderInternal()
  • 根据Adapter 变化,转换Item 缓存如pool
    onAdapterChanged()
  • 调用此方法返回一个RecyclerViewPool 实例
    getRecyclerView()
  • 缓存如pool
    addViewHolderToRecyclerViewPool()

RecyclerViewPool 相关:

  • 缓存的不同类型ViewType的数量是不限的,但是每个viewType 的具体ViewHolder 最多为5个
    setMaxRecycledViews(int viewType,int max)
  • 存入viewHolder
    putRecycledView()
  • 。。。
    getRecycledView()

RecyclerViw 与 listView 比较

  • Item 回收/复用方面:后者是以convertView 作为回收单位,需要手动添加ViewHolder ,而前者则是以ViewHolder作为回收单位,convertView 被内置到了ViewHolder 中作为 ViewHolder 的成员变量,前者内置了Recycle 、多级缓存。
  • 样式丰富方面:前者通过支持水平、垂直和变革列表及其他更复杂形式,而后者只支持具体某一种
  • 效果增强方面:前者内置了ItemDecoration 和 ItemAnimator ,可以自定义绘制 itemView 之间的一些特殊UI 或Item 项数据变化时的动画效果,而yoga后者实现比较麻烦。
  • 代码内聚方面:前者将功能密切相关的类携程内部类,如ViewHolder,Adapter。而后者没有。

adapter用法

  • ListView 的 adapter
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;    
} 
  • RecyclerView 的 adapter
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,以方便使用。

布局效果对比

作为一枚控件,要引起开发者使用的欲望自然先是从显示效果看起(看脸的世界),ListView 大家对效果已经很熟悉了,这里直接跳过,而作为 RecyclerView,它能带给效果要比 ListView 强大得多,如下图

 
Android RecyclerView与ListView比较
 

Android 默认提供的 RecyclerView 就能支持 线性布局网格布局瀑布流布局 三种(这里我们暂且不提代码细节,后文再说),而且同时还能够控制横向还是纵向滚动。怎样,从效果上足以碾压 ListView 有木有。

  • 横向滚动的ListView开源控件是不是可以不用再找了?对,你没看错!
  • 瀑布流效果的开源控件是不是可以不用再找了?对,你没看错!
  • 连横向滚动的GridView都不用找了!对,你没看错!

到此,展示效果上的差距一目了然。

API 使用对比

当然,一个控件我们不能完全只看效果,关键还是要看实用性,看看有没有方便我们调用的 API提高我们的开发效率。所以,接下来我们就从各个方面来看看 RecyclerView 和 ListView 在提供的API调用上的一些实践比较。

基础使用

ListView 的基础使用大家再熟悉不过,其使用的关键点主要如下:

  • 继承重写 BaseAdapter 类
  • 自定义 ViewHolder 和 convertView 一起完成复用优化工作

由于 ListView 已经老生常谈,所以此处就不去写示例代码了。 RecyclerView 基础使用关键点同样有两点:

  • 继承重写 RecyclerView.Adapter 和 RecyclerView.ViewHolder
  • 设置布局管理器,控制布局效果

示例代码大致如下:


// 第一步:继承重写 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 在基础使用上的区别主要有如下几点:

  • ViewHolder 的编写规范化了
  • RecyclerView 复用 Item 的工作 Google 全帮你搞定,不再需要像 ListView 那样自己调用 setTag
  • RecyclerView 需要多出一步 LayoutManager 的设置工作

布局效果

在最开始就提到,RecyclerView 能够支持各种各样的布局效果,这是 ListView 所不具有的功能,那么这个功能如何实现的呢?其核心关键在于 RecyclerView.LayoutManager 类中。从前面的基础使用可以看到,RecyclerView 在使用过程中要比 ListView 多一个 setLayoutManager 步骤,这个 LayoutManager 就是用于控制我们 RecyclerView 最终的展示效果的。

 
Android RecyclerView与ListView比较
 

而 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));//设置内容为空时显示的视图

 
Android RecyclerView与ListView比较
 

而 RecyclerView 并没有提供此类 API,所以,这些工作需要自己来干。虽说这类逻辑并不复杂,但是作为一个有追求的程序猿,能偷懒还是要想着偷懒的嘛...

HeaderView 和 FooterView

在 ListView 的设计中,存在着 HeaderView 和 FooterView 两种类型的视图,并且系统也提供了相应的 API 来让我们设置

 
Android RecyclerView与ListView比较
 

使用 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 的效果

 
Android RecyclerView与ListView比较
 

RecyclerView.Adapter 则我们提供了 notifyItemChanged 用于更新单个 Item View 的刷新,我们可以省去自己写局部更新的工作。

 
Android RecyclerView与ListView比较
 

实现效果如下

 
Android RecyclerView与ListView比较
 

动画效果

如果你细心观察上面 ListView 和 RecyclerView 局部更新 Item 的效果,你会发现相比 ListView 而言, RecyclerView 在做局部刷新的时候有一个渐变的动画效果。这也是 RecyclerView 相对非常值得一提的地方,作为 ListView 自身并没有为我们提供封装好的 API 来实现动画效果切换。所以,如果要给 ListView 的 Item 加动画,我们只能自己通过属性动画来操作 Item 的视图。 Github 也有很多封装得好好的开源库给我们用,如:ListViewAnimations 就封装了大量的效果供我们玩耍,童鞋们可以自行学习一下

 
Android RecyclerView与ListView比较
 

ListViewAnimations 主要大致实现方式是通过装饰者模式来扩充 Adapter ,并结合属性动画 Animator 来添加动画效果。相比之下,RecyclerView 则为我们提供了很多基本的动画 API ,如下方的增删移改

 
Android RecyclerView与ListView比较
 

简单的调用即可实现相应的效果,用起来方便很多,视觉交互上也会更好些

 
Android RecyclerView与ListView比较
 

如果你对动画效果有追求,觉得系统提供的并不能满足你的需求,也可以通过相应接口实现自己的动画效果,方式也非常简单,继承 RecyclerView.ItemAnimator 类,并实现相应的方法,再调用 RecyclerView 的 setItemAnimator(RecyclerView.ItemAnimator animator) 方法设置完即可实现自定义的动画效果。

 
Android RecyclerView与ListView比较
 

系统也为我们提供了两个默认的动画实现:SimpleItemAnimator 和 DefaultItemAnimator。而 RecyclerView 在不手动调用 setItemAnimator 的情况下,则默认用了内置的 DefaultItemAnimator 。

 
Android RecyclerView与ListView比较
 

当然编写自定义的 ItemAnimator 也是需要一定工作量的,这里同样为大家介绍一个针对 RecyclerView 开源的动画库:recyclerview-animators。其内部封装了大量的动画效果给供我们调用。

 
Android RecyclerView与ListView比较
 

如果想要学习怎么写一个自定义 ItemAnimator ,上面介绍的开源库的代码同样不容错过。哦,对了,如果谈到动画效果,还有一个很关键的类不得不提,那就是 ItemTouchHelper 。

 
Android RecyclerView与ListView比较
 

ItemTouchHelper 是系统为我们提供的一个用于滑动和删除 RecyclerView 条目的工具类,用起来也是非常简单的,大致两步:

  • 创建 ItemTouchHelper 实例,同时实现 ItemTouchHelper.SimpleCallback 中的抽象方法,用于初始化 ItemTouchHelper
  • 调用 ItemTouchHelper 的 attachToRecyclerView 方法关联上 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 会被回调。我们再到其中实现相应的业务操作即可。最终效果如下

 
Android RecyclerView与ListView比较
 

想想我们以前用 ListView 的时候要怎么做,RecyclerView 真的爽多了。

监听 Item 的事件

ListView 为我们准备了几个专门用于监听 Item 的回调接口,如单击、长按、选中某个 Item 等

 
Android RecyclerView与ListView比较
 

说实话,其实我并不大喜欢这样的设计,如 setOnItemClickListener ,在我们不添加 HeaderView 和 FooterView 的时候,我们可以通过回调参数中的 position 去拿到数据源列表中对应 Item 的数据。

 
Android RecyclerView与ListView比较
 

但是,添加了 HeaderView 和 FooterView 之后就不一样了,ListView 会把 HeaderView 和 FooterView 算入 position 内。假设你原先在 onItemClick 回调方法中写了 mDataList.get(position) 这样的业务代码并且这段代码运行良好许久,但在某天你突然加了个 HeaderView 后,这段代码就开始变的有问题了,此时因为 HeaderView 占用的位置算入了 position 之内,所以 position 的最大值实际上是大于 mDataList 包含元素的个数值的,因此代码会报数组越界的错误。当然,我们可以去避免这种问题的发生,就是不通过 position 来获取数据,二是通过回调方法中的 id 。

 
Android RecyclerView与ListView比较
 

这样就不会受到添加 HeaderView 和 FooterView 的影响了,这个 id 的值就是来自我们编写好的 Adapter 中的 getItemId 函数中返回的 id,使用 IDE 生成此函数时,默认是返回0,需要将 position 作为 Item 的 id 返回。

 
Android RecyclerView与ListView比较
 

并同时在 onItemClick 中判断 id 是否值为 -1,因为 HeaderView 和 FooterView 的返回值就是 -1。前面讲到我并不大喜欢 setOnItemClickListener 这种设计,除了由这些因素的影响外,更关键的是个人认为针对 Item 的事件实际上写在 getView 方法中会更加合适,如 setOnItemClickListener 我更喜欢用在 getView 中为每个 convertView 设置 setOnClickListener 的方式去取代它。

而再来看看 RecyclerView ,它并没有像 ListView 提供太多关于 Item 的某种事件监听,唯一的就是 addOnItemTouchListener

 
Android RecyclerView与ListView比较
 

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),实现上相对是比较复杂的,此处就不去拓展说明,其关键在于 NestedScrollingChildNestedScrollingParent 两个接口,以及系统对这两个接口的实现类 NestedScrollingChildHelperNestedScrollingParentHelper 大家可以查阅相关的资料。可能说起来太抽象了,这里拿一个简单的示例效果来说明好了,如下方是用 CollapsingToolbarLayout 和 RecyclerView 搭配的效果:

 
Android RecyclerView与ListView比较
 

一开始上面一大块区域就是 CollapsingToolbarLayout ,下方的列表是 RecyclerView ,当然 RecyclerView 向上滑动时,CollapsingToolbarLayout 能够同时网上收缩,直到只剩下顶部的 Toolbar。之所以能够实现这种效果,就是完全依赖于嵌套滚动机制,如果没有这套机制,按照原有的触摸事件分发逻辑, RecyclerView 内部已经把 Touch 事件消耗掉了,完全无法引起顶部的 CollapsingToolbarLayout 产生联动收缩的效果。我们可以查看 RecyclerView 的代码实现,发现它已经实现了 NestedScrollingChild 接口

 
Android RecyclerView与ListView比较
 

如果在其他代码布局都不变的情况下,我们把 RecyclerView 替换成 ListView ,则无法产生上面图中的动态效果,因为 ListView 并不支持嵌套滚动机制,事件在 ListView 内部已经被消耗且无法传递出来,大家可以自行尝试验证一下。对下方 AppBarLayout 的使用也是同理。

 
Android RecyclerView与ListView比较
 

关于 AppBarLayout 和 CollapsingToolbarLayout,它们并不是什么第三方控件,而是 Android 官方提供的 MaterialDesign 设计风格的控件,大家可以在官方文档中搜索到它们的资料,如果你用过 Android 原生系统,你可以在通讯录等系统内置应用看到它们的身影。如果你想使用类似 AppBarLayout 、 CollapsingToolbarLayout 这种需要嵌套滚动的机制才能达到效果的控件,那么 RecyclerView 将是你的不二之选,因为 ListView 在此根本无法发挥作用。同样的,ScrollView 也是不支持嵌套滚动机制,但是你可以使用 NestedScrollView 。



作者:D_clock爱吃葱花
链接:https://www.jianshu.com/p/f592f3715ae2
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

 

Android RecyclerView与ListView比较

上一篇:vue中使用axios post上传头像/图片并实时显示到页面


下一篇:嵌入式【杂记--手机芯片与pc】