Android--ListView和RecyclerView控件的使用

下面我们来学习一下 ListViewRecyclerView 这两种控件的用法

最常用和最难用的控件--ListView

ListView绝对可以称得上是Android应用程序中最常用的控件之一,比起前面介绍的几种控件,ListView也是相对比较复杂的。

ListView的简单用法

首先来创建一个ListViewTest项目,让Android Studio自动为我们创建好活动,然后修改activity_main.xml中的代码,如下所示

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

<ListView
    android:id="@+id/list_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />

</LinearLayout>

这里设置ListView的宽度和高度都是match_parent,这样ListView就可以占满整个布局的空间。接下来修改MainActivity中的代码,如下所示

public class MainActivity extends AppCompatActivity {
private String[] data={"张三","李四","王五","赵六","陈浮生","陈富贵","竹叶青","陈龙象","陈半仙","王虎胜","张三千"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ArrayAdapter<String>adapter=new ArrayAdapter<String>(
                MainActivity.this,android.R.layout.simple_list_item_1,data);
        ListView listView = findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }
}

ListView是用来展示大量的数据的,所以我们先将数据提供好,这里用一个data数组来测试,里面随便写了一些人名。

不过数据中的数据是无法直接传递给ListView的,我们需要借助适配器来完成。Android中提供了许多的适配器的实现类,不过我认为最好用的就是 ArrayAdapter。它可以通过泛型来指定来适配的数据类型,然后在构造函数中把要适配的数据传入。ArrayAdapter有多个构造函数的重载,应根据实际情况选择最适合的一种。这里由于我们提供的数据是都是,因此将ArrayAdapter的泛型指定为String。ArrayAdapter的构造函数中依次传入当前上下文,ListView子项布局的id,以及要适配的数据。注意,我们使用了 R.layout.simple_list_item_1,data 作为ListView子项布局的id,这是Android内置的一个布局文件,里面只有一个TextView,只用于简单的显示一段文本。这样适配器对象就构建好了。

最后还需要调用ListView的 setAdapter() 方法将适配器对象传递进去,这样ListView和数据之间的关联就建立好了。

现在运行一下程序,效果如下图所示

image

定制ListView界面

现在我们来定制更加丰富的ListView。首先准备一组图片,等会我们要让水果名称旁边都有一个图片

接着定义一个实体类,作为适配器的适配类型。新建类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;
    }
}

这个类中只有两个字段,name是水果的名称,imageId是水果对应图片的id。然后再新建一个布局fruit_item.xml作为ListView的子项布局,代码如下所示

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

    <ImageView
        android:id="@+id/fruit_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

    <TextView
        android:id="@+id/fruit_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"
        />

</LinearLayout>

我们这里定义了一个ImageView用于显示水果对应的图片,TextView用于显示水果的名称,并让TextView在垂直方向上剧中显示

接下来创建一个自定义适配器,这个适配器继承 ArrayAdapter ,泛型指定为Fruit类。代码如下所示


public class FruitAdapter extends ArrayAdapter<Fruit> {

    private int resourceId;

    public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
        super(context, resource, objects);
        resourceId=resource;
    }

    /**
     * 每个子项滚动到屏幕内的时候getView()都会被调用
     */
    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//        获取当前项的Fruit实例
        Fruit fruit = getItem(position);
//        加载我们传入的布局
        View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
//        获取ImageView实例
        ImageView fruitImage = view.findViewById(R.id.fruit_image);
//        获取TextView实例
        TextView fruitName = view.findViewById(R.id.fruit_name);
//        为ImageView设置要显示的图片
        fruitImage.setImageResource(fruit.getImageId());
//        为TextView设置要显示的名字
        fruitName.setText(fruit.getName());
//        将布局返回
        return view;
    }
}

自定义适配器完成了,接下来修改MainActivity中代码,如下所示


public class MainActivity extends AppCompatActivity {
    private String[] data = {"张三", "李四", "王五", "赵六", "陈浮生", "陈富贵", "竹叶青", "陈龙象", "陈半仙", "王虎胜", "张三千"};

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

    public void initFruits()
    {
        for (int i=0;i<2;i++)
        {
            Fruit zs = new Fruit("张三", R.drawable.apple_pic);
            fruitList.add(zs);
            Fruit ls = new Fruit("李四", R.drawable.banana_pic);
            fruitList.add(ls);
            Fruit ww = new Fruit("王五", R.drawable.orange_pic);
            fruitList.add(ww);
            Fruit cl = new Fruit("赵六", R.drawable.watermelon_pic);
            fruitList.add(cl);
            Fruit cfs = new Fruit("陈浮生", R.drawable.pear_pic);
            fruitList.add(cfs);
            Fruit clx = new Fruit("陈龙象", R.drawable.grape_pic);
            fruitList.add(clx);
            Fruit cbx = new Fruit("陈半仙", R.drawable.pineapple_pic);
            fruitList.add(cbx);
            Fruit zsq = new Fruit("张三千", R.drawable.strawberry_pic);
            fruitList.add(zsq);
            Fruit cys = new Fruit("陈圆殊", R.drawable.cherry_pic);
            fruitList.add(cys);
            Fruit whs = new Fruit("王虎胜", R.drawable.mango_pic);
            fruitList.add(whs);

        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//        初始化水果数据
        initFruits();
        FruitAdapter adapter = new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitList);

        ListView listView = findViewById(R.id.list_view);
        listView.setAdapter(adapter);
    }
}

现在重新运行程序,效果如图所示

image

提升ListView的运行效率

目前我们ListView的运行效率是很低的,因为在FruitAdapter的getView()方法中,每次都要重新加载一次布局,当ListView快速滚动的时候,这就成为了性能瓶颈。

getView()方法中有一个convertView参数,这个参数是用来缓存加载好的布局,以便之后可以进行重用。修改FruitAdapter中的代码,如下所示

public class FruitAdapter extends ArrayAdapter<Fruit> {

    private int resourceId;

    public FruitAdapter(@NonNull Context context, int resource, @NonNull List<Fruit> objects) {
        super(context, resource, objects);
        resourceId=resource;
    }

    /**
     * 每个子项滚动到屏幕内的时候getView()都会被调用
     */
    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
//        获取当前项的Fruit实例
        Fruit fruit = getItem(position);
//        加载我们传入的布局
        View view;
        ViewHolder viewHolder;
//        convertView是将我们加载好的布局进行缓存
        if (convertView==null)
        {
             view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
             viewHolder=new ViewHolder();
            viewHolder.imageView = view.findViewById(R.id.fruit_image);
            viewHolder.textView = view.findViewById(R.id.fruit_name);
            view.setTag(viewHolder);
        }else {
            view=convertView;
             viewHolder =(ViewHolder) view.getTag();
        }
        viewHolder.imageView.setImageResource(fruit.getImageId());
        viewHolder.textView.setText(fruit.getName());

//        将视图返回
        return view;
    }
//    定义内部类
    class  ViewHolder{
        ImageView imageView;
        TextView textView;
    }
}

我们在getView()中对convertView进行了判断,如果convertView为null,则用LayoutInflater去加载布局,如果不为null,则直接对convertView进行重用。这就可以大大提高了ListView的运行效率,不过还可以进一步优化,虽然不用再重复加载布局,但是每次在getView()方法中还是会调用View的findViewById()方法来获取一次控件的实例。我们可以借助一个ViewHolder来对这一部分进行性能优化。

我们新增了一个内部类 ViewHolder,用于对控件的实例进行缓存,当 convertView 为null的时候,创建一个viewHolder实例,并将控件实例都存放到viewHolder里,再调用setTag()方法将 ViewHolder存储到View当中,当convertView不为null的时候,调用View的getTag()方法重新取出ViewHolder,这样就不用每次都需要通过findViewById()方法来重新获取控件实例了。

通过这两步优化之后,我们的ListView运行效率就非常不错了。

ListView的点击事件

下面来学习一下ListView如何才能实现用户的点击事件

在MainActivity的onCreate()方法中添加如下代码

   listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                Fruit fruit = fruitList.get(i);
                Toast.makeText(MainActivity.this, fruit.getName(), Toast.LENGTH_SHORT).show();
            }
        });
image

当用户点击了ListView中的任何一个子项时,就会回调onItemClick()方法。这个方法中可以通过i参数判断出用户点击的是哪一个子项,然后获取到相应的水果,并通过Toast将水果名称显示出来。

重新运行程序,效果如下所示

image

更强大的滚动控件

ListView并不是没有缺点,如果我们不使用一些技巧来对ListView进行一些优化的话,那么ListView的运行效率是很差的。还有,ListView的扩展性也不好,它只可以实现纵向滚动,如果要想实现横向滚动,ListView是做不到的。

为此Android提供了一个更强大的滚动控件--RecyclerView,它可以说是增强版的ListView。它不仅可以轻松实现ListView相同的效果,还优化好ListView存在的各种不足。目前官方更加推荐使用RecyclerView。下面我们就来学习一下RecyclerView的用法。

首先创建好一个RecyclerViewTest项目,并让Android Studio自动为我们创建好活动。

RecyclerView的基本用法

和百分比布局类似,RecyclerView也是新增布局,因此需要在项目的build.gradle添加相应的依赖库才行。
打开app/build.gradle文件,在dependencies闭包中添加如下内容

    implementation 'androidx.recyclerview:recyclerview:1.0.0'

添加完后记得点击Sync Now来进行同步,然后修改activity_main.xml中的代码,如下所示

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />

</androidx.constraintlayout.widget.ConstraintLayout>

让RecyclerView占满整个屏幕,因为RecyclerView并不是内置在系统SDK当中,所以需要写完整的包路径。

下面我们来实现和ListView相同的效果,为简单起见,我们直接从ListViewTest项目中将图片、Fruit类、fruit_item.xml文件复制过来。

首先为RecyclerView准备一个适配器,新建FruitAdapter类,让它继承RecyclerView.Adapter,其中泛型指定为FruitAdapter.ViewHolder,ViewHolder是FruitAdapter中定义的一个内部类,代码如下所示


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

    private List<Fruit> mFruitList;

    static class ViewHolder extends RecyclerView.ViewHolder {
        ImageView fruitImage;
        TextView fruitName;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
//            分别获取到布局中的ImageView和TextView实例
            fruitImage = itemView.findViewById(R.id.fruit_image);
            fruitName = itemView.findViewById(R.id.fruit_name);
        }
    }

    public FruitAdapter(List<Fruit> mFruitList) {
        this.mFruitList = mFruitList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//        把fruit_item布局加载进来
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
//        创建ViewHolder实例,并把加载进来的fruit_item传到构造函数
        ViewHolder holder = new ViewHolder(view);
//        返回ViewHolder实例
        return holder;
    }

    /**
     * onBindViewHolder()会给RecyclerView的子项数据赋值,在每一个子项滚到屏幕内会被执行
     */
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
//        通过position参数得到当前项的Fruit实例
        Fruit fruit = mFruitList.get(position);
//        将fruit数据设置到ViewHolder的ImageView和TextView当中
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }

    @Override
    public int getItemCount() {
//        返回数据源的长度,告诉RecyclerView共有多少子项
        return mFruitList.size();
    }
}

这段代码看上去有点长,但是却比ListView更容易理解。这里我们定义了一个内部类ViewHolder,它继承自 RecyclerView.ViewHolder ,在ViewHolder的构造函数当中传入一个View参数,这个参数通过就是RecyclerView的最外层布局,那么我们就可以通过findViewById()方法获取到ImageView和TextView的实例了。

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

继续往下看,由于FruitAdapter继承自RecyclerView.Adapter,所以必须重写 onCreateViewHolder()onBindViewHolder()getItemCount() 这3个方法。

onCreateViewHolder() 方法是用来创建ViewHolder实例的。我们先把fruit_item布局加载进来,然后创建一个ViewHolder实例,并把加载进来的布局传递到ViewHolder构造函数当中,最后把ViewHolder实例返回。

onBindViewHolder() 方法是对RecyclerView的子项数据进行赋值的。会在每个子项滚动到屏幕内的时候执行,通过position参数得到当前项的Fruit实例,然后将数据设置到ViewHolder的ImageView和TextView当中

getItemCount() 方法就非常简单了,用来告诉RecyclerView一共有多少子项,直接返回数据源的长度就可以了。

适配器准备好之后,就可以使用RecyclerView了,修改MainActivity代码,如下所示


public class MainActivity extends AppCompatActivity {

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

    private void initFruits() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit(("Apple"), R.drawable.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit(("Banana"), R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit(("Orange"), R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit(("Watermelon"), R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit(("Pear"), R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit(("Grape"), R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit(("Pineapple"), R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit(("Strawberry"), R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit(("Cherry"), R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit(("Mango"), R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//        初始化水果数据
        initFruits();
//        先获得到RecyclerView的实例
        RecyclerView recyclerView = findViewById(R.id.recycler_view);
//        指定RecyclerView的布局方式,这里LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
//        创建FruitAdapter实例,并把水果的数据传递到FruitAdapter的构造函数当中
        FruitAdapter adapter = new FruitAdapter(fruitList);
//        完成适配器设置,RecyclerView和数据间的关联就建立完成了
        recyclerView.setAdapter(adapter);
    }
}

代码中的注释已经解释得很清楚了,现在可以运行一下程序了,效果如图所示

image

实现横向滚动

首先要对fruit_item布局进行修改,将里面的元素改成垂直排列才比较合理
,修改fruit_item.xml中的代码,如下所示

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

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

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

</LinearLayout>

接下来修改MainActivity中的代码,如下所示

package com.example.recyclerviewtest;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

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

    private void initFruits() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit(("Apple"), R.drawable.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit(("Banana"), R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit(("Orange"), R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit(("Watermelon"), R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit(("Pear"), R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit(("Grape"), R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit(("Pineapple"), R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit(("Strawberry"), R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit(("Cherry"), R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit(("Mango"), R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//        初始化水果数据
        initFruits();
//        先获得到RecyclerView的实例
        RecyclerView recyclerView = findViewById(R.id.recycler_view);
//        指定RecyclerView的布局方式,这里LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
//        设置布局的排列方式,默认是纵向的,下面设置成横向的
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        recyclerView.setLayoutManager(layoutManager);
//        创建FruitAdapter实例,并把水果的数据传递到FruitAdapter的构造函数当中
        FruitAdapter adapter = new FruitAdapter(fruitList);
//        完成适配器设置,RecyclerView和数据间的关联就建立完成了
        recyclerView.setAdapter(adapter);
    }
}

调用LinearLayoutManager的 setOrientation()方法来设置布局的排列方向,默认是纵向的,传入 LinearLayoutManager.HORIZONTAL 来设置布局的排列方式为横向,这样RecyclerView就可以横向滚动了。

重新运行一下程序,效果如下所示

image

实现瀑布流布局

StaggeredGridLayoutManager 可以用于实现瀑布流布局

首先来修改一下fruit_item.xml中的代码,如下所示

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

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

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

</LinearLayout>

这里我们将LinearLayout的宽度由100dp改成match_parent,因为瀑布流布局的宽度是根据布局的列数来自动适配的,而不是一个固定值。

接下来修改MainActivity中的代码,如下所示


public class MainActivity extends AppCompatActivity {

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

    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();
    }
    private void initFruits() {
        for (int i = 0; i < 2; i++) {
            Fruit apple = new Fruit(getRandomLengthName("Apple"), R.drawable.apple_pic);
            fruitList.add(apple);
            Fruit banana = new Fruit(getRandomLengthName("Banana"), R.drawable.banana_pic);
            fruitList.add(banana);
            Fruit orange = new Fruit(getRandomLengthName("Orange"), R.drawable.orange_pic);
            fruitList.add(orange);
            Fruit watermelon = new Fruit(getRandomLengthName("Watermelon"), R.drawable.watermelon_pic);
            fruitList.add(watermelon);
            Fruit pear = new Fruit(getRandomLengthName("Pear"), R.drawable.pear_pic);
            fruitList.add(pear);
            Fruit grape = new Fruit(getRandomLengthName("Grape"), R.drawable.grape_pic);
            fruitList.add(grape);
            Fruit pineapple = new Fruit(getRandomLengthName("Pineapple"), R.drawable.pineapple_pic);
            fruitList.add(pineapple);
            Fruit strawberry = new Fruit(getRandomLengthName("Strawberry"), R.drawable.strawberry_pic);
            fruitList.add(strawberry);
            Fruit cherry = new Fruit(getRandomLengthName("Cherry"), R.drawable.cherry_pic);
            fruitList.add(cherry);
            Fruit mango = new Fruit(getRandomLengthName("Mango"), R.drawable.mango_pic);
            fruitList.add(mango);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//        初始化水果数据
        initFruits();
//        先获得到RecyclerView的实例
        RecyclerView recyclerView = findViewById(R.id.recycler_view);
//        瀑布流布局
//        StaggeredGridLayoutManager()的第一个参数是列数,第二个参数是排列方式
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);
//        创建FruitAdapter实例,并把水果的数据传递到FruitAdapter的构造函数当中
        FruitAdapter adapter = new FruitAdapter(fruitList);
//        完成适配器设置,RecyclerView和数据间的关联就建立完成了
        recyclerView.setAdapter(adapter);
    }
}

由于瀑布流布局需要各个子项的高度不一致才能看出明显的效果,所以这里使用了getRandomLengthName()这个方法来取得不同长度的水果名字。

重新运行程序,效果如下所示

image

RecyclerView点击事件

RecyclerView摒弃了子项点击事件的监听器,所有点击事件都由具体的View去实现。下面我们来具体学习一下在RecyclerView中如何注册点击事件,修改FruitAdapter中的代码,如下所示


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

    private List<Fruit> mFruitList;

    static class ViewHolder extends RecyclerView.ViewHolder {
        ImageView fruitImage;
        TextView fruitName;
        View fruitView;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
//            分别获取到布局中的ImageView和TextView实例
            fruitView=itemView;
            fruitImage = itemView.findViewById(R.id.fruit_image);
            fruitName = itemView.findViewById(R.id.fruit_name);
        }
    }

    public FruitAdapter(List<Fruit> mFruitList) {
        this.mFruitList = mFruitList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
//        把fruit_item布局加载进来
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
//        创建ViewHolder实例,并把加载进来的fruit_item传到构造函数
       final ViewHolder holder = new ViewHolder(view);

       holder.fruitView.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               int position = holder.getAdapterPosition();
               Fruit fruit = mFruitList.get(position);
               Toast.makeText(view.getContext(),"You clicked view"+fruit.getName(),Toast.LENGTH_SHORT).show();
           }
       });

       holder.fruitImage.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               int position = holder.getAdapterPosition();
               Fruit fruit = mFruitList.get(position);
               Toast.makeText(view.getContext(),"You clicked Image"+fruit.getName(),Toast.LENGTH_SHORT).show();
           }
       });
//        返回ViewHolder实例
        return holder;
    }

    /**
     * onBindViewHolder()会给RecyclerView的子项数据赋值,在每一个子项滚到屏幕内会被执行
     */
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
//        通过position参数得到当前项的Fruit实例
        Fruit fruit = mFruitList.get(position);
//        将fruit数据设置到ViewHolder的ImageView和TextView当中
        holder.fruitImage.setImageResource(fruit.getImageId());
        holder.fruitName.setText(fruit.getName());
    }

    @Override
    public int getItemCount() {
//        返回数据源的长度,告诉RecyclerView共有多少子项
        return mFruitList.size();
    }
}

我们先修改ViewHolder,在ViewHolder中添加fruitView变量来保存子项最外层布局的实例(View就是子项最外层布局),然后在onCreateViewHolder()注册点击事件就可以了。这里分别为最外层布局和ImageView都注册了点击事件,RecyclerView中的强大之处就在这里,它可以轻松实现子项中任意控件或布局的点击事件。我们在两个点击事件中先获取了用户点击的position,然后通过position拿到相应的Fruit实例,再分别用Toast弹出不同的内容以示区别。

重新运行程序,效果如下所示

image

内容参考自《第一行代码》

github代码

码云代码

个人网站:www.panbingwen.cn

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 亲,由于个人职业规划的变动,已向公司提出离职申请。初心不变,未来我将继续从事教育行业,兼顾家庭和孩子成长,感恩于您...
    亲爱的卿阅读 892评论 2 3
  • 晋文读书笔记-谈谈金钱 1. 婚姻中造成夫妻冲突的原因, 金钱排名第二,仅次于沟通。 2. 什么是钱? - 钱,各...
    晋文笔记阅读 151评论 0 0
  • 一、目标(5.1-8.1) 完美的伴侣:1.很开心和我在一起相亲相爱 2.智慧,向往佛法,愿意去思考生命深处的东西...
    柔光宝宝阅读 194评论 0 0
  • 天地初开 混沌的世界,造物主竭尽全力的铺陈 宇宙,成为一种道具 亿万颗星子 是顺手扬出的沙尘,飘浮,不落 日月坚守...
    雨荷_0636阅读 293评论 2 1