RecyclerView与ListView

2018-03-08  本文已影响0人  mouekz

ListView作为一个流布局控件是一个比较老的资格了。
RecyclerView作为Desgin设计里面的一个新空间却有着强大的功能,其支持线性布局、网格布局、瀑布流布局 三种,而且同时还能够控制横向还是纵向滚动。

基础使用

ListView 的基础使用大家再熟悉不过,其使用的关键点主要如下:
继承重写 BaseAdapter 类
自定义 ViewHolder 和 convertView 一起完成复用优化工作
由于 ListView 已经老生常谈,所以此处就不去写示例代码了。

RecyclerView 基础使用关键点同样有两点:
继承重写 RecyclerView.Adapter 和 RecyclerView.ViewHolder
设置布局管理器,控制布局效果。

// 第一步获取控件
recyclerView = findViewById(R.id.home_recycler);
// 设置布局管理器,控制布局效果
LinearLayoutManager manager = new LinearLayoutManager();
recyclerView.setLayoutManager(manager);
//设置适配器
recyclerView.setApapter(new HomeRecyclerView(context ,itemDatas));

// 创建适配器
public class HomeRecyclerAdapter extends RecyclerView.Adapter<HomeRecyclerAdapter.HomeHolderView> {
    // 创建ViewHolder及绑定View
    @Override
    public HomeHolderView onCreateViewHolder(ViewGroup parent, int viewType) {
        return null;
    }

    // 多布局使用
    @Override
    public int getItemViewType(int position) {
        return super.getItemViewType(position);
    }

    // 填充数据,
    @Override
    public void onBindViewHolder(HomeHolderView holder, int position) {

    }
    
    @Override
    public int getItemCount() {
        return 0;
    }

    class HomeHolderView extends RecyclerView.ViewHolder {
        public HomeHolderView(View itemView) {
            super(itemView);
        }
    }
}

从基础使用上看,我们明显可以看出,RecyclerView 相比 ListView 在基础使用上的区别主要有如下几点:
ViewHolder 的编写规范化了
RecyclerView 复用 Item 的工作 Google 全帮你搞定,不再需要像 ListView 那样自己调用 setTag
RecyclerView 需要多出一步 LayoutManager 的设置工作.

布局效果

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


image.png

而 LayoutManager 只是一个抽象类而已,系统已经为我们提供了三个相关的实现类 LinearLayoutManager(线性布局效果)、GridLayoutManager(网格布局效果)、StaggeredGridLayoutManager(瀑布流布局效果)。如果你想用 RecyclerView 来实现自己 YY 出来的一种效果,则应该去继承实现自己的 LayoutManager,并重写相应的方法,而不应该想着去改写 RecyclerView。关于 LayoutManager 的使用有下面一些常见的 API(有些在 LayoutManager 实现的子类中)
API 可以查看官方文档,通常你想用 RecyclerView 实现某种效果,例如指定滚动到某个 Item 位置,但是你在 RecyclerView 中又找不到可以调用的 API 时,就可以跑到 LayoutManager 的文档去看看,基本都在那里。另外还有一点关于瀑布流布局效果 StaggeredGridLayoutManager 想说的,看到网上有些文章写的示例代码,在设置了 StaggeredGridLayoutManager 后仍要去 Adapter 中动态设置 View 的高度,才能实现瀑布流,这种做法是完全错误的,之所以 StaggeredGridLayoutManager 的瀑布流效果出不来,基本是 item 布局的 xml 问题以及数据问题导致。如果要在 Adapter 中设置 View 的高度,则完全违背了 LayoutManager 的设计理念了。

HeaderView 和 FooterView

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


image

使用 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:

image

动画效果

如果你细心观察上面 ListView 和 RecyclerView 局部更新 Item 的效果,你会发现相比 ListView 而言, RecyclerView 在做局部刷新的时候有一个渐变的动画效果。这也是 RecyclerView 相对非常值得一提的地方,作为 ListView 自身并没有为我们提供封装好的 API 来实现动画效果切换。所以,如果要给 ListView 的 Item 加动画,我们只能自己通过属性动画来操作 Item 的视图。 Github 也有很多封装得好好的开源库给我们用,如:ListViewAnimations 就封装了大量的效果供我们使用。
ListViewAnimations 主要大致实现方式是通过装饰者模式来扩充 Adapter ,并结合属性动画 Animator 来添加动画效果。相比之下,RecyclerView 则为我们提供了很多基本的动画 API ,如下方的增删移改

image
也可以通过相应接口实现自己的动画效果,方式也非常简单,继承 RecyclerView.ItemAnimator 类,并实现相应的方法,再调用 RecyclerView 的 setItemAnimator(RecyclerView.ItemAnimator animator) 方法设置完即可实现自定义的动画效果。
image

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


image

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

那就是 ItemTouchHelper 。


image

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

示例代码大致如下:

image

虽然代码中有注释,但还是稍稍解释一下,主要重写的是 getMovementFlags 、 onMove 、 onSwiped 三个抽象方法,getMovementFlags 用于告诉系统,我们的 RecyclerView 到底是支持滑动还是拖曳。如上面的示例代码,就是表示着同时支持上下左右四个方向的拖曳和左右两个方向的滑动效果。如果时滑动,则 onSwiped 会被回调,如果是拖曳 onMove 会被回调。

监听 Item 的事件

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


image

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


image

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


image

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


image

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

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


image

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 大家可以查阅相关的资料。

上一篇下一篇

猜你喜欢

热点阅读