天天看点

RecyclerView 基本用法及另类加分割线技巧

1 RecyclerView 介绍

1.1 RecyclerView 的优点

我们先来看看谷歌官方的介绍:

A flexible view for providing a limited window into a large data set.

翻译成中文大致就是:

用来在有限的窗口上展示大量数据的灵活视图

关键字在于灵活,以前展示数据主要用的控件是 ListView,通过使用 RecyclerView,我们不仅可以很方便的实现类似 ListView 的效果,同样稍微改下代码便可以实现 GridView 和瀑布流的效果。还可以通过定制,实现条目增加或删除时的动画。总体来看,通过设置不同的 LayoutManager,ItemDecoretion,ItemAnimator 可以实现各种炫酷的效果。

另外,以前使用 ListView 时,为了性能复用 view 时需要自己定义 ViewHolder,而在 RecyclerView 中谷歌已经封装好了 ViewHolder,使用起来非常的方便。

1.2 RecyclerView 的不足

尽管 RecyclerView 有着如此众多的优点,但它也有一些使用起来较为麻烦的地方,其实我用了“不足”这个词而不是“缺点”,因为 RecyclerView 具有高度的解耦性,在带来更多功能的同时,势必代码量会加大一点。刚接触 RecyclerView 时主要有两个地方与 ListView 有所不同:

  1. Item 的点击事件需要自己实现
  2. Item 之间的分割线需要自己实现

一个一个来说,其实点击事件其实从另一方面理解倒不如说是好处,比如 Item 中如果有子控件需要响应点击事件,这点 ListView 中要实现还是要花不少功夫的,这里不讨论其实现。可能由于上述场景越来越多,谷歌就干脆把决策权让给开发者,自己去实现点击事件吧。

另外一个比较麻烦的地方在于 Item 之间默认是没有分割线的,对于网格布局形式或者瀑布流形式我们可以通过 margin 属性实现 item 的分离,效果也不错,但对于类似于 ListView 这样的线性布局,还是有分割线会美观一些。一般来说,实现分割线通常做法主要是自定义一个 ItemDecoration,本文后面会提出一个比较另类的写法,也可以实现分割线的效果,而且写法简单,具体请参考后文。

实际上,如果只是单纯的有数据需要展示出来,不需要太好的视觉效果,用 ListView 也是一个不错的选择,因为写法简单。而通过结合 CardView 和 RecyclerView,我们可以做出更好的 Item 间隔效果,比起单纯的一条线,使用 CardView 后的效果更加符合 MaterialDesign 的设计规范,具体如何使用请看我后续的博客。本文主要是针对 RecyclerView 的基本用法并实现 ListView 效果展开。

2 RecyclerView的基本用法

RecyclerView 的基本用法与 ListView 相差无几,大致就是如下几个步骤:

  • 与 ListView 不同的是,因为来自 v7 包,需引入 gradle 依赖
  • 布局文件添加 RecyclerView 控件
  • 自定义 Adaper 及 ViewHolder
  • Activity 中为 RecyclerView 设置 Adapter

好的,说了半天,现在是代码时间。

1,首先,我们要在 build.gradle 文件引入 RecyclerView 的依赖库:

2,接下来,去布局文件中添加 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="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v7.widget.RecyclerView>

</LinearLayout>
           

3,自定义 MyAdapter 继承自 RecyclerView.Adapter<>,泛型是要实现的 ViewHolder,通常定义成 MyAdapter 内部类 MyViewHolder,这个类要继承自 RecyclerView.ViewHolder,下面是 MyAdapter 的代码:

class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

        private List<String> mData;

        // 通过构造方法传入数据
        public MyAdapter(List<String> data) {
            this.mData = data;
        }

        @Override
        public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_recyclerview, parent, false);
            return new MyViewHolder(view);
        }

        @Override
        public void onBindViewHolder(MyAdapter.MyViewHolder holder, int position) {
            holder.tv.setText(mData.get(position));
        }

        @Override
        public int getItemCount() {
            return mData.size();
        }

        class MyViewHolder extends RecyclerView.ViewHolder {
            TextView tv;

            public MyViewHolder(View itemView) {
                super(itemView);
                tv = (TextView) itemView.findViewById(R.id.tv_content);
            }
        }
    }
           

RecyclerView.Adapter 是一个抽象类,我们必须实现其

onBindViewHolder()

getItemCount()

方法。

getItemCount()

方法返回的是 Item 个数,这个与 ListView 类似。

onBindViewHolder()

方法返回的则是一个 RecyclerView.ViewHolder 对象,我们这里返回自己的定义的 MyViewHolder。MyViewHolder 通过构造方法传入每个 Item 的布局文件,其单项 Item 局部文件

item_recyclerview.xml

代码如下:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp" />
</FrameLayout>
           

很简单,因为我们的数据就是简单的文本,所以采用 TextView。

4,好了,各项准备工作完成,我们只需要在 MainActivity 调用如下几行代码即可:

List<String> data = new ArrayList<>();
for (int i = ; i < ; i++) {
    if (i < ) {
        data.add("这是第 " +  + (i + ) + " 条数据");
    } else {
        data.add("这是第 " + (i + ) + " 条数据");
    }
}

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new MyAdapter(data));
           

有几点说明下,必须调用

setLayoutManager()

方法,否则是不会显示数据的。这里我们传入一个 LinearLayoutManager,看名字就知道,它类似于 LinearLayout,可以设置布局方向为水平或垂直,默认的实现就是垂直布局,所以通过上面简单的使用,我们已经简单的实现了类似于 Listview  的效果。看下效果如下:

RecyclerView 基本用法及另类加分割线技巧

这样就简单实现了 Listview 的效果,如果你想实现网格布局或者瀑布流布局,只需要将 LinearLayoutManager 换成 GridLayoutManager 或者 StaggeredGridLayoutManager 即可。

3 另类分割线实现

可以看到,与 Listview 唯一的区别在于 Item 之间并没有分割线,前文说过,通常添加分割线是采用自定义 ItemDecoration 实现,这里我们采用较为简单的一种写法。非常简单只需要修改

item_recyclerview.xml

如下:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/darker_gray">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="1dp"
        android:background="@android:color/background_light"
        android:gravity="center"
        android:padding="10dp"
        android:textColor="@android:color/black" />
</FrameLayout>
           

我们先来看下效果:

RecyclerView 基本用法及另类加分割线技巧

那么这是怎么实现的呢?其实很简单,主要就是利用了两个属性,

background

layout_marginBottom

,总结起来按照以下步骤使用即可:

  • Item 最外层套一层布局 FrameLayout,设置其 background 属性为你需要的分割线颜色
  • 将你自己的 Item 背景设置为需要的颜色
  • 根据需要设置 magin 属性

这种方法有一定局限性,比如只适合较为简单的布局,像瀑布流这种复杂布局,这种方法就不适用了,但我个人觉得一般瀑布流布局也不需要什么分割线了,毕竟都是不规则块。如果要采用自定义 ItemDecoration 添加分割线的可以参照张鸿洋的博客Android RecyclerView 使用完全解析 体验艺术般的控件

4 小细节分析

这里有一个细节,在 inflate xml 的时候这里我们并没有用

View.inflate();

而是用了

LayoutInflater.from().inflate();

如果采用前者,会使得 Item 的

match_parent

属性失效。表现出来就是,Item 的子布局设置的某些属性比如居中看上去都失效了,实际上是因为

match_parent

失效变成了

wrap_content

。具体可以用个例子来演示下,比如我们修改

item_recyclerview.xml

代码如下:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/holo_green_dark">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        android:background="@android:color/holo_red_dark"
        android:gravity="center"
        android:padding="10dp"
        android:textColor="@android:color/black" />
</FrameLayout>
           

LayoutInflater.from().inflate();

方法也就是正常情况,如下左图所示。用

View.inflate();

方法的情况如下右图所示。

RecyclerView 基本用法及另类加分割线技巧
RecyclerView 基本用法及另类加分割线技巧

从上图可以看出,确实是 FrameLayout 的

match_parent

属性失效了,与 TextView 无关。这也是我刚开始学习 RecyclerView 时遇到的一个坑,感兴趣的同学可以去试试。详细的相关分析可以参考这篇博客:RecyclerView中的item的match_parent属性失效问题解决方案