• 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);
            }
        }
    }

    运行程序如图所示。

        

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

  • 相关阅读:
    Ubuntu16.04搭建NetCore2.2运行环境
    Centos7安装Redis5.0.3
    Centos7常用命令
    logstash6.5.4同步mysql数据到elasticsearch 6.4.1
    Windows下安装Elasticsearch6.4.1和Head,IK分词器
    【selenium】-自动化测试的前提
    【软件测试基础】回顾总结
    【软件测试基础】其它测试分类
    【软件测试基础】文档测试
    【软件测试基础】兼容性测试
  • 原文地址:https://www.cnblogs.com/lihuawei/p/16636945.html
Copyright © 2020-2023  润新知