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); } } }
运行程序如图所示。
【课后练习】制作一个下列样式的应用程序列表