11.ListView

ListView是Android中最常用的控件之一,几乎所有的应用程序都会用到它。用于展示大量的数据。

ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚动出屏幕。

比如查看手机联系人列表,浏览微博的最新消息等等。ListView的用法很多。

1、ListView的简单用法

首先新建一个ListViewTest项目,并让Android Studio自动创建活动。修改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">

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

在布局中添加ListView控件的方法还是非常简单的,先为ListView指定了一个id,然后将宽度和高度都设置为match_parent,这样ListView也就占据了整个布局的空间。

那么,ListView中的数据怎么添加呢?添加ListView中的数据的方法有很多中,下面我们来逐一的学习一下。

(1)通过ListView控件的android:entries属性添加数据。

首先,修改strings.xml字符串资源文件,使用<string-array>元素来包含一些<item>子元素,用于定义生肖名称。代码如下:

<resources>
    ……
    <string-array name="list_animals">
        <item>子鼠</item>
        <item>丑牛</item>
        <item>寅虎</item>
        <item>卯兔</item>
        <item>辰龙</item>
        <item>巳蛇</item>
    </string-array>
</resources>

在字符串资源文件中,增加<string-array>节点,定义name为list_animals,在该节点下增加<item>项目节点。

然后,修改ListView控件,增加android:entries属性,属性值为@array/list_animals:

<ListView
    android:id="@+id/lvAnimals"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:entries="@array/list_animals" >
</ListView>

运行程序,效果如图显示:

但是这种使用字符串资源填充ListView的方法,只能添加固定的数据,而往往ListView中显示的数据我们要从文件、数据库或者网络上获得,这就需要我们能够动态向ListView中添加数据。

(2)通过适配器添加数据

首先,删除ListView控件中的android:entries属性,再修改MainActivity中的代码,如下所示:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity {
    private String[] animals = {"子鼠", "丑牛", "寅虎", "卯兔", "辰龙", "巳蛇", "午马", "未羊", "申猴", "酉鸡", "戌狗", "亥猪"};

    @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, animals);
        ListView lvAnimals = (ListView) findViewById(R.id.lvAnimals);
        lvAnimals.setAdapter(adapter);
    }
}

ListView是用于展示大量数据的,我们在这里为了测试,使用了一个animals数组存放了一些数据,实际开发项目时,只需通过代码改变数组中的数据就可以。

不过,数组中的数据是无法直接传递给ListView的,我们还需要借助适配器来完成。

Android中提供了很多适配器的实现类,其中我认为最好用的就是ArrayAdapter。它可以通过泛型(参数化类型,类似于方法中的变量参数)来指定要适配的数据类型,然后在构造方法中把要适配的数据传入即可。

ArrayAdapter有多个构造方法的重载,你应该根据实际情况选择最合适的一种。

这里由于我们提供的数据都是字符串,因此将ArrayAdapter的泛型指定为String,然后在ArrayAdapter的构造方法中依次传入当前上下文ListView子项布局的id,以及要适配的数据

注意我们使用了android.R.layout.simple_list_item_1作为ListView子项布局的id,这是一个Android内置的布局文件,里面只有一个TextView,可用于简单地显示一段文本。这样适配器对象就构建好了。

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

运行一下程序,效果如图所示。可以通过滚动的方式来查看屏幕外的数据。

另外,Android还内置了几种子项目布局样式:

  • android.R.layout.simple_list_item_single_choice:单选按钮
  • android.R.layout.simple_list_item_multiple_choice:多选按钮
  • android.R.layout.simple_list_item_checked:checkbox

    

                                          single_choice样式                                        multiple_choice样式                                                 checked样式

以上这些都是一个子项目里显示一个文本,如果是两个文本呢?

Android还内置了android.R.layout.simple_list_item_2布局样式:一行title,一行text。

修改MainActivity.java:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MainActivity extends AppCompatActivity {
    private String[] animals = {"子鼠", "丑牛", "寅虎", "卯兔", "辰龙", "巳蛇", "午马", "未羊", "申猴", "酉鸡", "戌狗", "亥猪"};
    private String[] englishName = {"Rat charm", "Ox patient", "Tiger sensitive", "Rabbit articulate", "Dragon healthy", "Snake deep", "Horse popular", "Goat elegant", "Monkey clever", "Rooster deep thinkers", "Dog loyalty", "Pig chivalrous"};
    private List<Map<String, String>> list = new ArrayList<Map<String, String>>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        for (int i = 0; i < animals.length; i++) {
            Map<String, String> keyValuePair = new HashMap<String, String>();
            keyValuePair.put("Name", animals[i]);
            keyValuePair.put("English", englishName[i]);
            list.add(keyValuePair);
        }
        ListAdapter adapter = new SimpleAdapter(this, list, android.R.layout.simple_list_item_2, 
new String[]{"Name", "English"}, new int
[]{android.R.id.text1, android.R.id.text2}); ListView lvAnimals = (ListView) findViewById(R.id.lvAnimals); lvAnimals.setAdapter(adapter); } }

这里的数据源list中的每一个元素是一个Map映射(十二生肖中文名称和英文名称的对应),数据形式如下:

index

Name

EnglishName

0

子鼠

Rat charm

1

丑牛

Ox patient

2

寅虎

Tiger sensitive

3

卯兔

Rabbit articulate

4

辰龙

Dragon healthy

5

巳蛇

Snake deep

6

午马

Horse popular

7

未羊

Goat elegant

8

申猴

Monkey clever

9

酉鸡

Rooster deep thinkers

10

戌狗

Dog loyalty

11

亥猪

Pig chivalrous

这里由于要显示数据源是上面所示的list,所以适配器更换为SimpleAdapter,它的构造方法有五个参数:第一个参数还是上下文,第二个参数是要适配的数据,第三个参数是子项布局文件的id,第四个参数是数据中Map的键名数组,第五个参数是子项布局文件中控件id数组。

第四个参数和第五个参数的顺序,决定哪个数据显示到哪个控件上去。

控件id:android.R.id.text1和android.R.id.text2来源于哪里呢?

查看android.R.layout.simple_list_item_2布局源代码:

 

2、定制ListView的界面

只能显示文本的ListView实在是太单调了,我们要对ListView添加一些图片元素来美化它。

首先,需要准备好一组图片,分别对应上面提供的每一种生肖。

接着定义一个实体类,代表生肖,作为ListView适配器的适配类型(泛型)。

新建类Animal,代码如下所示:

public class Animal {
    private String name;
    private int imageId;

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

    public String getName() {
        return name;
    }

    public int getImageId() {
        return imageId;
    }
}

Animal类中只有两个字段:String型的name表示生肖名称,int型的imageId表示生肖对应图片的资源id。

然后为ListView的子项指定一个自定义的布局文件,在layout目录下新建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/ivAnimal"
        android:layout_width="80dp"
        android:layout_height="80dp" />

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

在这个布局中,我们定义了一个ImageView用于显示生肖的图片,又定义了一个TextView用于显示生肖的名称。

接下来需要创建一个自定义的适配器,这个适配器继承自ArrayAdapter,并将泛型指定为Animal类。新建类AnimalAdapter,代码如下所示:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.List;

public class AnimalAdapter extends ArrayAdapter<Animal> {
    private int resourceId;

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

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        Animal animal = getItem(position);
        View view = LayoutInflater.from(getContext()).inflate(resourceId, null);
        ImageView ivAnimal = (ImageView) view.findViewById(R.id.ivAnimal);
        TextView tvName = (TextView) view.findViewById(R.id.tvName);
        ivAnimal.setImageResource(animal.getImageId());
        tvName.setText(animal.getName());
        return view;
    }
}

AnimalAdapter重写了父类的一组构造方法,用于将上下文、ListView子项布局的id和数据都传递进来。

另外又重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用,其中,参数position标识我们现在正在绘制ListView中第几个子项。

在getView方法中,首先通过getItem()方法得到当前项的Animal实例(getItem()方法在父类ArrayAdapter中定义的就是从构造方法的第3个参数objects对象中获得当前子项的),然后使用LayoutInflater来为这个子项加载我们传入的布局,接着调用View的findViewById()方法分别获取到ImageView和TextView的实例,并分别调用它们的setImageResource()和setText()方法来设置显示的图片和文字,最后将布局返回。

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

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ListView;

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

public class MainActivity extends AppCompatActivity {
    private List<Animal> lstAnimal = new ArrayList<Animal>();
    private int[] images = {R.drawable.rat, R.drawable.ox, R.drawable.tiger, R.drawable.rabbit, R.drawable.dragon, R.drawable.snake, R.drawable.horse, R.drawable.goat, R.drawable.monkey, R.drawable.rooster, R.drawable.dog, R.drawable.pig};
    private String[] animals = {"子鼠", "丑牛", "寅虎", "卯兔", "辰龙", "巳蛇", "午马", "未羊", "申猴", "酉鸡", "戌狗", "亥猪"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initAnimals();
        AnimalAdapter adapter = new AnimalAdapter(this, R.layout.item, lstAnimal);
        ListView lvAnimals = (ListView) findViewById(R.id.lvAnimals);
        lvAnimals.setAdapter(adapter);
    }

    private void initAnimals() {
        for (int i = 0; i < animals.length; i++) {
            Animal animal = new Animal(animals[i], images[i]);
            lstAnimal.add(animal);
        }
    }
}

 

可以看到,我们增加了一个数组,用于存储十二生肖对应的图片id。然后,添加了一个initAnimals()方法,用于初始化十二生肖的数据。在Animal类的构造方法中使用for循环将生肖的名字和对应的图片id传入,然后把创建好的生肖对象添加到Animal列表中。接着我们在onCreate()方法中创建了AnimalAdapter对象,并将AnimalAdapter作为适配器传递给了ListView。这样定制ListView界面的任务就完成了。

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

  

 【扩展练习】定制界面增加生肖的英文名称显示,如上有图。

(1)修改item.xml中的内容,增加一个TextView控件。

<?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/ivAnimal"
        android:layout_width="80dp"
        android:layout_height="80dp" />

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

    <TextView
        android:id="@+id/tvEnglishName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"
        android:textColor="#0000ff" />

</LinearLayout>

(2)修改Animal.java文件,为Animal类增加一个属性englishName。

public class Animal {
    private String name;
    private int imageId;
    private String englishName;

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

    public String getName() {
        return name;
    }

    public int getImageId() {
        return imageId;
    }

    public String getEnglishName() {
        return englishName;
    }
}

(3)修改MainActivity.java文件,在initAnimals()方法中调用生肖对象的构造方法时,增加englishName属性值的传入

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ListView;

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

public class MainActivity extends AppCompatActivity {
    private List<Animal> lstAnimal = new ArrayList<Animal>();
    private int[] images = {R.drawable.rat, R.drawable.ox, R.drawable.tiger, R.drawable.rabbit, R.drawable.dragon, R.drawable.snake, R.drawable.horse, R.drawable.goat, R.drawable.monkey, R.drawable.rooster, R.drawable.dog, R.drawable.pig};
    private String[] animals = {"子鼠", "丑牛", "寅虎", "卯兔", "辰龙", "巳蛇", "午马", "未羊", "申猴", "酉鸡", "戌狗", "亥猪"};
    private String[] englishName = {"Rat charm", "Ox patient", "Tiger sensitive", "Rabbit articulate", "Dragon healthy", "Snake deep", "Horse popular", "Goat elegant", "Monkey clever", "Rooster deep thinkers", "Dog loyalty", "Pig chivalrous"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initAnimals();
        AnimalAdapter adapter = new AnimalAdapter(this, R.layout.item, lstAnimal);
        ListView lvAnimals = (ListView) findViewById(R.id.lvAnimals);
        lvAnimals.setAdapter(adapter);
    }

    private void initAnimals() {
        for (int i = 0; i < animals.length; i++) {
            Animal animal = new Animal(animals[i], images[i], englishName[i]);
            lstAnimal.add(animal);
        }
    }
}

(4)修改AnimalAdapter.java文件,增加对于tvEnglishName控件的初始化和调用它的setText()设置生肖英文名称。

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.List;

public class AnimalAdapter extends ArrayAdapter<Animal> {
    private int resourceId;

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

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        Animal animal = getItem(position);
        View view = LayoutInflater.from(getContext()).inflate(resourceId, null);
        ImageView ivAnimal = (ImageView) view.findViewById(R.id.ivAnimal);
        TextView tvName = (TextView) view.findViewById(R.id.tvName);
        TextView tvEnglishName = (TextView) view.findViewById(R.id.tvEnglishName);
        ivAnimal.setImageResource(animal.getImageId());
        tvName.setText(animal.getName());
        tvEnglishName.setText(animal.getEnglishName());
        return view;
    }
}

3、提高ListView的运行效率

之前我们提过ListView这个控件很难用,是因为它的运行效率可以优化。目前我们ListView的运行效率是很低的,因为在AnimalAdapter的getView()方法中每次都将布局重新加载了一遍,调用LayoutInflater.from(getContext()).inflate(resourceId, null)方法,当ListView快速滚动的时候这就会成为性能的瓶颈,有可能会出现卡顿的现象。

我们可以通过在getView()方法第二行中增加相应的Log输出来观察规律:

Log.d("AnimalAdapter", "position = " + position + ", Animal = " + animal.getName() + ", image = " + animal.getImageId());

上下滑动ListView,观察log信息。

通过分析,我们发现getView()方法中有一个convertView参数,这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用。

修改AnimalAdapter中的代码,如下所示:

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.List;

public class AnimalAdapter extends ArrayAdapter<Animal> {
    private int resourceId;

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

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        Animal animal = getItem(position);
        Log.d("AnimalAdapter", "position = " + position + ", Animal = " + animal.getName() + ", image = " + animal.getImageId());
        View view;
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(resourceId, null);
        } else {
            view = convertView;
        }
        ImageView ivAnimal = (ImageView) view.findViewById(R.id.ivAnimal);
        TextView tvName = (TextView) view.findViewById(R.id.tvName);
        TextView tvEnglishName = (TextView) view.findViewById(R.id.tvEnglishName);
        ivAnimal.setImageResource(animal.getImageId());
        tvName.setText(animal.getName());
        tvEnglishName.setText(animal.getEnglishName());
        return view;
    }
}

可以看到,现在我们在getView()方法中进行了判断,如果convertView为空,则使用LayoutInflater去加载布局,如果不为空则直接对convertView进行重用。

这样就大大提高了ListView的运行效率,在快速滚动的时候也可以表现出更好的性能。

不过,现在的代码还是可以继续优化的,虽然现在已经不会再重复去加载布局,但是每次在getView()方法中还是会调用View的findViewById()方法来获取一次控件的实例,这也会花费大量的时间。

我们可以借助一套重复利用的机制----“Recycler”(反复循环器)来提高效率。定义一个内部类ViewHolder,在这个类中定义几个控件对象,用于对控件的实例进行缓存,当convertView为空的时候,创建一个ViewHolder对象,并将控件的实例都存放在ViewHolder里,然后调用View的setTag()方法,将ViewHolder对象存储在View中。当convertView不为空的时候则调用View的getTag()方法,把ViewHolder重新取出。这样所有控件的实例都缓存在了ViewHolder里,就没有必要每次都通过findViewById()方法来获取控件实例了。修改AnimalAdapter中的代码,如下所示:

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.List;

public class AnimalAdapter extends ArrayAdapter<Animal> {
    private int resourceId;

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

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        Animal animal = getItem(position);
        Log.d("AnimalAdapter", "position = " + position + ", Animal = " + animal.getName() + ", image = " + animal.getImageId());
        View view;
        ViewHolder viewHolder;
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).inflate(resourceId, null);
            viewHolder = new ViewHolder();
            viewHolder.ivAnimal = (ImageView) view.findViewById(R.id.ivAnimal);
            viewHolder.tvName = (TextView) view.findViewById(R.id.tvName);
            viewHolder.tvEnglishName = (TextView) view.findViewById(R.id.tvEnglishName);
            view.setTag(viewHolder); // 将ViewHolder存储在View中
        } else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag(); // 重新获取ViewHolder
        }
        viewHolder.ivAnimal.setImageResource(animal.getImageId());
        viewHolder.tvName.setText(animal.getName());
        viewHolder.tvEnglishName.setText(animal.getEnglishName());
        return view;
    }

    // 定义一个内部类,用来保存控件对象
    class ViewHolder {
        ImageView ivAnimal;
        TextView tvName;
        TextView tvEnglishName;
    }
}

LayoutInflater是指定视图工具,它就像是一个压力泵(充气机),能够把布局文件压缩成一个视图,呈现出来。

它的作用类似于findViewById(),不同点是LayoutInflater是用来找layout下xml布局文件,并且实例化!而findViewById()是找具体xml下的具体widget控件。

我们来看一下优化过的getView()方法,其中有两句话貌似作用不是很明确:

view.setTag(viewHolder);

viewHolder = (ViewHolder) view.getTag();

我们都知道Listview中有很多的item,数量少的话,我们每次在绘制ListView的每一行的时候,都需要重新findViewById来查找并且创建一系列item里面所需要的view。那么如果我们的ListView有几亿个item呢,不但findViewById会花费大量的时间,如果每一个item都重新inflate的话,那我们的内存空间也受不了。这里就需要用到刚才说的“Recycler”(反复循环器)。

 

实际上一个完整的ListView第一次出现时,每个Item都是null的,ListView创建的item数,只能够充满屏幕为止。假设一屏上最多显示7个item。当我们滑动ListView的时候,旧的item滑出去,新的item滑进来,那么这个滑进来滑出去的过程,在程序的内部是怎么实现的呢。getView()方法的第二个参数convertView相当于一个缓存器“Recycler”(反复循环器),在程序刚开始运行,第一屏ListView的所有item出现在屏幕中时,convertView为零。随后,如果我们向上滑动,item1滑出屏幕,item8滑入屏幕,此时convertView将之前滑出去的视图的信息记录下来,当有新的item滑进来,如果他们所用的都是同一个xml布局文件压缩成的view视图,那么我们就不再去重新inflate一个视图,直接利用刚刚滑出去的那个item的视图,更新一下数据就可以了。这样的话,就能够实现重复利用,不用再多花费时间和空间。当item2也滑出去的时候,item9滑进来,item9得到的将是item2之前的视图,然后更新一下数据即可。以后一直按照这样的规律:item10 > item3, item11 > item4。

虽然我们已经重复利用了之前绘制的视图,但是在更新数据之前,我们总得通过findViewById来找到相应的控件进行数据更新操作。为了能够再次优化这一部分,我们在第一屏ListView的item创建的时候,就执行setTag()方法,它为每一个视图绑定一个viewHolder对象,每一个item视图对应这么一个viewHolder对象,当第一屏的ListView绘制完成的时候,每一个视图都携带一个查找完成的viewHolder对象,这个viewholder对象的成员里面已经存好了对应视图内的控件(如TextView,ImageView)。当convertView不为0时(已经开始滑动),重复利用已经创建的view视图的时候,使用getTag()方法获取绑定的ViewHolder对象,这样就避免了findViewById对控件的层层查询,而是快速定位到控件。

4、ListView的点击事件

ListView的滚动毕竟只是满足了我们视觉上的效果,可是如果ListView中的子项不能点击的话,这个控件就没有什么实际的用途了。

因此,接下来我们就来学习一下ListView如何才能响应用户的点击事件。

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

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;

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

public class MainActivity extends AppCompatActivity {
    private List<Animal> lstAnimal = new ArrayList<Animal>();
    private int[] images = {R.drawable.rat, R.drawable.ox, R.drawable.tiger, R.drawable.rabbit, R.drawable.dragon, R.drawable.snake, R.drawable.horse, R.drawable.goat, R.drawable.monkey, R.drawable.rooster, R.drawable.dog, R.drawable.pig};
    private String[] animals = {"子鼠", "丑牛", "寅虎", "卯兔", "辰龙", "巳蛇", "午马", "未羊", "申猴", "酉鸡", "戌狗", "亥猪"};
    private String[] englishName = {"Rat charm", "Ox patient", "Tiger sensitive", "Rabbit articulate", "Dragon healthy", "Snake deep", "Horse popular", "Goat elegant", "Monkey clever", "Rooster deep thinkers", "Dog loyalty", "Pig chivalrous"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initAnimals();
        AnimalAdapter adapter = new AnimalAdapter(this, R.layout.item, lstAnimal);
        ListView lvAnimals = (ListView) findViewById(R.id.lvAnimals);
        lvAnimals.setAdapter(adapter);
        lvAnimals.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Log.d("MainActivity", "onItemClick: position = " + position + ", id = " + id);
                Animal animal = lstAnimal.get(position);
                Toast.makeText(MainActivity.this, animal.getName(), Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void initAnimals() {
        for (int i = 0; i < animals.length; i++) {
            Animal animal = new Animal(animals[i], images[i], englishName[i]);
            lstAnimal.add(animal);
        }
    }
}

可以看到,我们使用了setOnItemClickListener()方法来为ListView注册了一个监听器,当用户点击了ListView中的任何一个子项时就会回调onItemClick()方法,在这个方法中可以通过position参数判断出用户点击的是哪一个子项,然后获取到相应的生肖,并通过Toast将生肖的名字显示出来。

onItemClick()方法参数:

  • parent:哪个AdapterView,识别是哪个listview
  • view:你点击的Listview的某一项的内容,来源于adapter
  • position:是adapter的某一项的位置,如点击了listview第2项,而第2项对应的是adapter的第2个数值,那此时position的值就为1了。
  • id:值为点击了Listview的哪一项对应的数值,点击了listview第2项,那id就等于1。一般和position相同。

重新运行程序,并点击一下西瓜,效果如图所示。

 【扩展练习】点击ListView中的子项目弹出对话框,询问用户是否了解该生肖?

修改MainActivity.java文件:

import androidx.appcompat.app.AppCompatActivity;

import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;

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

public class MainActivity extends AppCompatActivity {
    private List<Animal> lstAnimal = new ArrayList<Animal>();
    private int[] images = {R.drawable.rat, R.drawable.ox, R.drawable.tiger, R.drawable.rabbit, R.drawable.dragon, R.drawable.snake, R.drawable.horse, R.drawable.goat, R.drawable.monkey, R.drawable.rooster, R.drawable.dog, R.drawable.pig};
    private String[] animals = {"子鼠", "丑牛", "寅虎", "卯兔", "辰龙", "巳蛇", "午马", "未羊", "申猴", "酉鸡", "戌狗", "亥猪"};
    private String[] englishName = {"Rat charm", "Ox patient", "Tiger sensitive", "Rabbit articulate", "Dragon healthy", "Snake deep", "Horse popular", "Goat elegant", "Monkey clever", "Rooster deep thinkers", "Dog loyalty", "Pig chivalrous"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initAnimals();
        AnimalAdapter adapter = new AnimalAdapter(this, R.layout.item, lstAnimal);
        ListView lvAnimals = (ListView) findViewById(R.id.lvAnimals);
        lvAnimals.setAdapter(adapter);
        lvAnimals.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Log.d("MainActivity", "onItemClick: position = " + position + ", id = " + id);
                Animal animal = lstAnimal.get(position);
                androidx.appcompat.app.AlertDialog.Builder dialog = new androidx.appcompat.app.AlertDialog.Builder(MainActivity.this);
                dialog.setTitle(animal.getName());
                dialog.setIcon(animal.getImageId());
                dialog.setMessage("你了解吗?");
                dialog.setPositiveButton("了解", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(MainActivity.this, "你来讲讲", Toast.LENGTH_SHORT).show();
                    }
                });
                dialog.setNegativeButton("不了解", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(MainActivity.this, "快去看书", Toast.LENGTH_SHORT).show();
                    }
                });
                dialog.setNeutralButton("一般", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(MainActivity.this, "还得努力", Toast.LENGTH_SHORT).show();
                    }
                });
                dialog.show(); // 展示对话框
            }
        });
    }

    private void initAnimals() {
        for (int i = 0; i < animals.length; i++) {
            Animal animal = new Animal(animals[i], images[i], englishName[i]);
            lstAnimal.add(animal);
        }
    }
}

5、ListView的其他事件

除了刚才讲过的点击事件之外,我们还可以为ListView的子项目添加长按事件,我们来通过长按子项目,弹出一个单选对话框。

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

import androidx.appcompat.app.AppCompatActivity;

import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;

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

public class MainActivity extends AppCompatActivity {
    private List<Animal> lstAnimal = new ArrayList<Animal>();
    private int[] images = {R.drawable.rat, R.drawable.ox, R.drawable.tiger, R.drawable.rabbit, R.drawable.dragon, R.drawable.snake, R.drawable.horse, R.drawable.goat, R.drawable.monkey, R.drawable.rooster, R.drawable.dog, R.drawable.pig};
    private String[] animals = {"子鼠", "丑牛", "寅虎", "卯兔", "辰龙", "巳蛇", "午马", "未羊", "申猴", "酉鸡", "戌狗", "亥猪"};
    private String[] englishName = {"Rat charm", "Ox patient", "Tiger sensitive", "Rabbit articulate", "Dragon healthy", "Snake deep", "Horse popular", "Goat elegant", "Monkey clever", "Rooster deep thinkers", "Dog loyalty", "Pig chivalrous"};
    private int selected; // 全局变量
    private Animal animal; // 全局变量
    private String[] enjoys = {"了解", "一般", "不了解"}; // 全局变量

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initAnimals();
        AnimalAdapter adapter = new AnimalAdapter(this, R.layout.item, lstAnimal);
        ListView lvAnimals = (ListView) findViewById(R.id.lvAnimals);
        lvAnimals.setAdapter(adapter);
        lvAnimals.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Log.d("MainActivity", "onItemClick: position = " + position + ", id = " + id);
                Animal animal = lstAnimal.get(position);
                androidx.appcompat.app.AlertDialog.Builder dialog = new androidx.appcompat.app.AlertDialog.Builder(MainActivity.this);
                dialog.setTitle(animal.getName());
                dialog.setIcon(animal.getImageId());
                dialog.setMessage("你了解吗?");
                dialog.setPositiveButton("了解", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(MainActivity.this, "你来讲讲", Toast.LENGTH_SHORT).show();
                    }
                });
                dialog.setNegativeButton("不了解", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(MainActivity.this, "快去看书", Toast.LENGTH_SHORT).show();
                    }
                });
                dialog.setNeutralButton("一般", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(MainActivity.this, "还得努力", Toast.LENGTH_SHORT).show();
                    }
                });
                dialog.show(); // 展示对话框
            }
        });

        lvAnimals.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
                animal = lstAnimal.get(position);
                androidx.appcompat.app.AlertDialog.Builder dialog = new androidx.appcompat.app.AlertDialog.Builder(MainActivity.this);
                dialog.setTitle("你了解" + animal.getName() + "吗?");
                dialog.setIcon(animal.getImageId());
                dialog.setCancelable(false);
                dialog.setSingleChoiceItems(enjoys, 0, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        selected = which;
                    }
                });
                dialog.setPositiveButton("确认", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(MainActivity.this, "你对" + animal.getName() + "的了解程度是:" + enjoys[selected], Toast.LENGTH_SHORT).show();
                    }
                });
                dialog.setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                });
                dialog.show(); // 展示对话框
                return true;
            }
        });
    }

    private void initAnimals() {
        for (int i = 0; i < animals.length; i++) {
            Animal animal = new Animal(animals[i], images[i], englishName[i]);
            lstAnimal.add(animal);
        }
    }
}

运行程序如图所示。

    

【课后练习】制作一个下列样式的应用程序列表

 

posted @ 2022-09-13 22:55  熊猫Panda先生  阅读(480)  评论(0编辑  收藏  举报