欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 强大的滚动控件 RecyclerView

程序员文章站 2022-05-14 19:32:04
...

       ListView 由于其强大的功能,在过去的 Andorid 开发中使用非常广泛,直到今天仍然还有很多人在使用着,不过 ListView 也有自己的缺陷,例如需要优化来提升运行效率,还有就是只能够纵向移动,我们要想实现横向移动就实现不了,ListView 的扩展性也不好

       为此 Android 提供了一个更强大的控件 -- RecyclerView 它可以说是一个增强版的 ListView 不仅可以实现和 ListView 同样的效果,还优化了 ListView 存在的各种不足,现 RecyclerView 也是官方非常推荐使用的滚动控件,大部分的开发人员也都从 listView 转向了 RecyclerView,今天我们就来详细了解一下 RecyclerView


Android 强大的滚动控件 RecyclerView


一、RecyclerView 的基本用法


首先,我们新建一个 RecyclerView 项目,并让 Andorid Studio 自动给我们创建好活动

       RecyclerView 属于新增的控件,为了让 RecyclerView 在所有 Android 版本上都能使用,Andorid 团队将 RecyclerView 定义在了 support 库当中,因此,想要使用 RecyclerView 这个控件,首先需要在项目的 build.gradle 中添加相应的依赖库才行,这里如果大家对 build.gradle 的使用还不是太熟悉,建议大家先阅读

  Android 详解 build.gradle 文件 

       在上面的文章中我们对 build.gradle 的使用进行了非常详细的讲解,接下来我们来进行依赖,打开 app/build.gradle 文件,在 dependenies 闭包中添加如下内容:


dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.1.0'
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:recyclerview-v7:25.1.0'
}

       大家看到最后一行代码,就是我们刚刚添加进来的,上面的都是 Android Studio 自动生成的,添加完记得 Sync Now 来进行同步,然后修改 Activity_main.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>

       在布局中加入 RecyclerView 控件也是非常简单的,先为 RecyclerView 指定一个 id,然后设置了宽和高,这些大家都非常熟悉了,需要注意的是,由于 RecyclerView 不是内置在系统 SDK 当中的,所以需要把完整的包路径写出来


二、定制 RecyclerView 界面


       这里和 listView 一样,我们可以对 RecyclerView 界面进行定制,让它来显示更加丰富的界面,以便来适应我们工程开发中的需求,这里首先我们来准备一组图片,来对应我们将要展示的水果名称,接着定义一个实体类,作为 RecyclerView 适配器的适配类型,新建 Fruit,代码如下:


public class Fruit {

    private String name;
    private int imageId;

    public Fruit(String name, int imageId) {
        this.name = name;
        this.imageId = imageId;
    }

    public String getName() {
        return name;
    }

    public int getImageId() {
        return imageId;
    }
}

Fruit 类中只有两个字段,name 表示水果的名字,imageId 表示对应水果的图片资源 id

然后需要为 RecyclerView 的子项指定一个我们自定义的布局,在 layout 目录下新建 recyclerview_item.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">

    <ImageView
        android:id="@+id/imageView"
        android:layout_marginLeft="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/textView"
        android:layout_gravity="center"
        android:layout_marginLeft="30dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

这里布局也非常简单,定义了一个 ImageView 用于显示水果的图片,又定义了一个 TextView 用于显示水果的名称

       接下来我们需要为 RecyclerView 准备一个适配器,新建 FruitAdapter 类,让这个适配器继承自 RecyclerView.Adapter,并将泛型指定为 FruitAdpter.ViewHolder,其中,ViewHolder 是我们在 FruitAdapter 中定义的一个内部类,代码如下:


/**
 * 数据适配器
 * Created by qiudengjiao on 2017/9/1.
 */

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {


    private List<Fruit> mFruitList;

    static class ViewHolder extends RecyclerView.ViewHolder {

        ImageView imageView;
        TextView textView;

        public ViewHolder(View itemView) {
            super(itemView);

            imageView = (ImageView) itemView.findViewById(R.id.imageView);
            textView = (TextView) itemView.findViewById(R.id.textView);
        }
    }


    public FruitAdapter(List<Fruit> fruitList) {

        this.mFruitList = fruitList;
    }

    @Override
    public FruitAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item, parent, false);
        ViewHolder viewHolder = new ViewHolder(view);

        return viewHolder;
    }

    @Override
    public void onBindViewHolder(FruitAdapter.ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.imageView.setImageResource(fruit.getImageId());
        holder.textView.setText(fruit.getName());
    }

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

       这里我们首先定义了一个内部类 ViewHolder,ViewHolder 要继承自 RecyclerView.ViewHolder,然后 ViewHolder 的构造函数中要传入一个 View 参数,这个参数通常就是 RecyclerView 子项的最外层布局,那么我们就可以通过 findViewById() 方法来获取到布局中的 ImageView 和 TextView 的实例了

       接着往下看,FruitAdater 中也有一个构造函数,这个方法用于要把展示的数据源传进来,并赋值给一个全局变量 mFruitList,我们后续的操作将都在这个数据源上进行

       继续往下看,由于 FruitAdapter 是继承自 RecyclerView.ViewHolder 的,那么就必须重写 onCreateViewHolder()、onBindviewHolder() 和 getItemCount() 这 3 个方法


onCreateViewHolder():用于创建 ViewHolder 的实例,我们在这个方法中将 recyclerview_item.xml 布局加载进来,然后创建一个 ViewHolder() 实例,并把加载出来的布局传入到构造函数当中,最后将 ViewHolder 的实例返回


onBindviewHolder():用于对 RecyclerView 的子项的数据进行赋值,会在每个子项被滚动到屏膜内时执行,这里我们通过 position 参数得到当前项的 Fruit 实例,然后再将数据设置到 ViewHolder 的 ImageView 和 TextView 当中即可


getItemCount():用于告诉 RecyclerView 一共有多少子项,直接返回数据源的长度


适配器准备好以后,我们就可以使用 RecyclerView 了,修改 MainActivity 中的代码如下:


public class MainActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initFruits();

        initRecyclerView();

    }

    private void initRecyclerView() {

        //获取 RecyclerView 实例
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        //创建 LinearLayoutManager 对象 这里是线性布局
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        //LayoutManager 用来设定 RecyclerView 布局的方式
        recyclerView.setLayoutManager(linearLayoutManager);
        //创建 FruitAdapter 的实例
        FruitAdapter adapter = new FruitAdapter(fruitList);
        //给 RecyclerView 设置适配器
        recyclerView.setAdapter(adapter);
    }

    /**
     * 初始化水果数据
     */
    private void initFruits() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit("Apple", R.mipmap.apple);
            fruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.mipmap.banana);
            fruitList.add(banana);
            Fruit orange = new Fruit("Orange", R.mipmap.orange);
            fruitList.add(orange);
            Fruit pear = new Fruit("Pear", R.mipmap.pear);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.mipmap.grape);
            fruitList.add(grape);
            Fruit starwberry = new Fruit("Starwberry", R.mipmap.starwberry);
            fruitList.add(starwberry);
            Fruit cherry = new Fruit("Cherry", R.mipmap.cherry);
            fruitList.add(cherry);
        }
    }
}

       这里我们通过 initFruits() 方法初始化了水果的数据,接着在 initRecyclerView() 中获取到 RecyclerView 的实例,然后创建一个LinearLayoutManager 对象,并将它设置到 RecelerView 当中,LayoutManager 用于指定 RecyclerView 的布局方式,这里使用的LinearLayoutManager 是线性布局的意思,接下来我们创建了 FruitAdapter 的实例,并将水果数据传入到了 FruitAadpter 的构造函数中,最后调用了 RecyclerView 的 setAdater() 方法来完成适配器的设置,这样 RecyclerView 和数据之间的关联就完成了,现在运行程序,效果图如下:


Android 强大的滚动控件 RecyclerView

       

       可以看到我们使用 RecyclerView 实现了和 ListView 同样能实现的效果,但逻辑上却更加的清晰,当然这只是 RecyclerView 的基本用法而已,接下来我们来实现那些 ListView 实现不了的效果

三、实现横向滚动和瀑布流布局


       我们知道 ListView 的扩展性并不好,它只能实现纵向滚动的效果,如果想进行横向移动,ListView 就做不到了,但是 RecyclerView 却可以做得到,并且实现还非常的简单,接下来我们就看来如何实现:

       首先我们要对 recyclerview_item.xml 布局进行修改,因为目前这个布局里的元素是进行水平排列的,适用于纵向滚动的场景,所以如果要实现横向滚动的话,应该把 recyclerview_item.xml 里的元素改成垂直排列比较合理,修改 recyclerview_item.xml 布局如下:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="80dp"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="10dp" />

</LinearLayout>

       可以看到,我们将 LinearLayout 改成垂直方向排列,并把宽设置成了 80dp,这里将宽设置成固定值是因为每种水果的文字长度不一致,如果用 wrap_parent 的话,RecyclerView 的子项就会有长有短,将会非常不美观,接下来我们修改 MainActivity 中的代码如下:


public class MainActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initFruits();

        initRecyclerView();

    }

    private void initRecyclerView() {

        //获取 RecyclerView 实例
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        //创建 LinearLayoutManager 对象 这里是线性布局
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        //LayoutManager 用来设定 RecyclerView 布局的方式
        linearLayoutManager.setOrientation(linearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(linearLayoutManager);
        //创建 FruitAdapter 的实例
        FruitAdapter adapter = new FruitAdapter(fruitList);
        //给 RecyclerView 设置适配器
        recyclerView.setAdapter(adapter);
    }

    /**
     * 初始化水果数据
     */
    private void initFruits() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit("Apple", R.mipmap.apple);
            fruitList.add(apple);
            Fruit banana = new Fruit("Banana", R.mipmap.banana);
            fruitList.add(banana);
            Fruit orange = new Fruit("Orange", R.mipmap.orange);
            fruitList.add(orange);
            Fruit pear = new Fruit("Pear", R.mipmap.pear);
            fruitList.add(pear);
            Fruit grape = new Fruit("Grape", R.mipmap.grape);
            fruitList.add(grape);
            Fruit starwberry = new Fruit("Starwberry", R.mipmap.starwberry);
            fruitList.add(starwberry);
            Fruit cherry = new Fruit("Cherry", R.mipmap.cherry);
            fruitList.add(cherry);
        }
    }
}

       可以看到我们仅仅修改了 initRecyclerView() 方法中的部分内容,调用 LinearLayoutManager 的 setOrientation() 方法来设置布局的排列方向,默认是纵向列的,我们传入了 LinearLayoutManager.HORIZONTAL 表示让布局横向排列,这样 RecyclerView 就可以横向滚动了,重新运行程序,效果图如下:


Android 强大的滚动控件 RecyclerView


       这种实现在 ListView 中就很难实现,在 RecyclerView 中却很简单的就实现了,这主要是因为 RecyclerView 将这个工作交给了 LayoutManager,LayoutManager 中制定了一套可扩展的布局排列接口,子类只需要按照接口的规范来实现,就能制定出各种不同的方式了

       除了 LinearLayoutManager 之外,RecyclerView 还给我们提供了 GridLayoutManager 和 StaggerdGridLayoutManager 这种内置的布局排列方式,GridLayoutManager 可以用于实现;网格布局,StaggerdGridLayoutManager 可用于实现瀑布流布局,这里我们来实现一个瀑布流布局

首先还是修改 recyclerview_item.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:layout_margin="5dp"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:layout_marginTop="10dp" />

</LinearLayout>

       这里我们做了几处小的调整,首先将 LinearLayout 的宽度由 80dp 改成了 match_parent,因为瀑布流布局的宽度应该是根据布局的列数来自动适配的,而不是一个固定值,另外我们使用 layout_margin 属性来让子项之间互留一点距离,这样就不至于所有子项都紧贴在一起,接下来我们来修改 MainActivity 中的代码:


public class MainActivity extends AppCompatActivity {

    private List<Fruit> fruitList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initFruits();

        initRecyclerView();

    }

    private void initRecyclerView() {

        //获取 RecyclerView 实例
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        //创建 LinearLayoutManager 对象 这里是线性布局
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        //LayoutManager 用来设定 RecyclerView 布局的方式
        StaggeredGridLayoutManager staggeredGridLayoutManager =
                new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(staggeredGridLayoutManager);
        //创建 FruitAdapter 的实例
        FruitAdapter adapter = new FruitAdapter(fruitList);
        //给 RecyclerView 设置适配器
        recyclerView.setAdapter(adapter);
    }

    /**
     * 初始化水果数据
     */
    private void initFruits() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit(getRandomlengthName("Apple"), R.mipmap.apple);
            fruitList.add(apple);
            Fruit banana = new Fruit(getRandomlengthName("Banana"), R.mipmap.banana);
            fruitList.add(banana);
            Fruit orange = new Fruit(getRandomlengthName("Orange"), R.mipmap.orange);
            fruitList.add(orange);
            Fruit pear = new Fruit(getRandomlengthName("Pear"), R.mipmap.pear);
            fruitList.add(pear);
            Fruit grape = new Fruit(getRandomlengthName("Grape"), R.mipmap.grape);
            fruitList.add(grape);
            Fruit starwberry = new Fruit(getRandomlengthName("Starwberry"), R.mipmap.starwberry);
            fruitList.add(starwberry);
            Fruit cherry = new Fruit(getRandomlengthName("Cherry"), R.mipmap.cherry);
            fruitList.add(cherry);
        }
    }


    private String getRandomlengthName(String name) {
        Random random = new Random();
        int length = random.nextInt(20) + 1;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(name);
        }

        return builder.toString();
    }
}

       这里我们们看到,和创建横向滚动的布局类似,我们在 initRecyclerView() 方法中我们创建了一个 StaggeredGridLayoutManager 的实例,StaggeredGridLayoutManager 的构造函数接受两个参数,第一个参数用于指定布局的列数,传入 3 表示会把布局分为 3 列,第二个参数用于指定布局的排列方向,传入 StaggeredGridLayoutManager.VERTICAL 表示会让布局纵向排列,最后再把创建好的实例设置到 RecyclerView 当中就可以了


       没错,仅仅修改了一行代码,我们就已经实现了瀑布流的布局效果了,不过瀑布流布局需要各个子布局的高度不一致,才能看出明显的效果,为此我们使用了一个小的技巧,接下来我们来看看 getRandomlengthName() 这个方法,这个方法使用 Random 对象来创建一个 1 到 20 之间的随机数,然后将参数传入的字符窜重复随机遍,在 initFruits() 方法中,每个水果的名字都改成调用 getRandomlengthName() 这个方法来生成,这样就能保证个水果名字的长短不一致,子项的高度也就各不相同,接下来我们来运行程序来看看效果:


Android 强大的滚动控件 RecyclerView


这样我们就实现了瀑布流式的布局


四、RecyclerView 的点击事件


      RecyclerView 必须要有点击事件才可以,不然的话也没有什么实际的用途,不过和 ListView 不同的是,RecyclerView 并没有提供类似于 ListView 的 setOnItemClickListener() 这样的注册监听方法,而是需要我们自己给具体的子项的 View 去注册点击事件,相比于ListView 来说要复杂一些,这里大家可能就有疑问了,为什么 RecyclerView 在各方面都要优于 ListView,但却在点击事件上没有处理好呢,其实不是这样的,ListView 上的点击事件处理并不人性化,setOnItemClickListener() 方法注册的是子项的点击事件,但是我们如果想点击的是子项里具体的某一个按钮,虽然也能实现,但实现起来就比较麻烦了,所以 RecyclerView 索性直接摒弃了子项点击事件的监听器,所有的点击事件都由具体的 View 去注册,就再没有这个困扰了


下面我们来学习一下如何在 RecyclerView 中注册点击事件,修改 FruitAdapter 中的代码如下:


/**
 * 数据适配器
 * Created by qiudengjiao on 2017/9/1.
 */

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {


    private List<Fruit> mFruitList;

    static class ViewHolder extends RecyclerView.ViewHolder {

        View fruitView;

        ImageView imageView;
        TextView textView;

        public ViewHolder(View itemView) {
            super(itemView);
            fruitView = itemView;
            imageView = (ImageView) itemView.findViewById(R.id.imageView);
            textView = (TextView) itemView.findViewById(R.id.textView);
        }
    }


    public FruitAdapter(List<Fruit> fruitList) {

        this.mFruitList = fruitList;
    }

    @Override
    public FruitAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item, parent, false);
        final ViewHolder viewHolder = new ViewHolder(view);

        //给最外层布局设置点击事件
        viewHolder.fruitView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = viewHolder.getAdapterPosition();
                Fruit fruit = mFruitList.get(position);
                Toast.makeText(v.getContext(), "你点击了View" + fruit.getName(), Toast.LENGTH_SHORT).show();
            }
        });

        //给 imageView 设置点击事件
        viewHolder.imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = viewHolder.getAdapterPosition();
                Fruit fruit = mFruitList.get(position);
                Toast.makeText(v.getContext(), "你点击了image" + fruit.getName(), Toast.LENGTH_SHORT).show();
            }
        });
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(FruitAdapter.ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.imageView.setImageResource(fruit.getImageId());
        holder.textView.setText(fruit.getName());
    }

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

       我们先是修改了 ViewHolder,在 ViewHolder 中添加了 fruitView 变量来保存子项最外层布局的实例,然后在 onCreateViewHolder() 方法中注册点击事件就可以了,注意我们这里分别对最外层布局和 ImageView 注册了点击事件,RecyclerView 的强大之处也在这里,它可以轻松实现子项中任意控件和布局的点击事件,我们在两个点击事件中先获取了用户点击的 position,然后通过 position 拿到相应的 Fruit 实例,再使用 Toast 分别弹出不同的内容以示区别,运行程序,点击香蕉的图片部分,如下图所示:


Android 强大的滚动控件 RecyclerView


点击香蕉文字部分如下图所示:


Android 强大的滚动控件 RecyclerView


可以看到,分别响应了我们的点击事件,到这里我们今天的内容就分享完了,大家周末愉快

参考:郭神第一行代码