第5章列表控件 在现实生活中,人们经常会使用QQ、微信等应用程序。在这些应用程序中通常会有一个页面展示多个条目,并且每个条目的布局都是一样的。如果利用前面所学习的知识实现这种布局,需要创建大量相同的布局,使用这种方式并不利于程序维护和扩展。针对上述情况,Android系统提供了功能强大的列表控件,通过列表控件可轻松实现上述需求。 列表控件是Android系统开发中使用最广泛的控件之一,常见的列表控件包括Spinner(下拉列表)、ListView(普通列表)、GridView(网格列表)、RecyclerView(增强列表)等。通过列表控件可展示多项数据,并且开发者可动态配置数据源,列表控件可根据所适配的数据源不同展示不同的内容。Android中主要采用适配器模式帮助建立列表控件和数据源之间的联系。因此在使用列表控件的时候,还需要创建适配器对象并为适配器提供数据源。Adapter(适配器)对象用来指明数据源中的每一项数据在列表控件中如何显示。列表控件调用setAdapter()方法把Adapter对象传递进来,即可将数据显示在列表中。 本章通过对“欢乐购商城”分析,帮助读者学会使用列表控件。通过对不同种类的列表控件综合运用,实现“欢乐购商城”中首页、商品列表、订单列表等页面的功能。 本章要点 (1) 掌握下拉列表Spinner的功能和用法。 (2) 掌握普通列表ListView的功能和用法。 (3) 掌握网格列表GridView的功能和用法。 (4) 掌握增强列表RecyclerView的功能和用法。 视频详解 5.1下拉列表Spinner 在“欢乐购商城”项目中修改收货地址页面使用到了Spinner控件,下面对修改收货地址页面做概要分析。 分析: 在修改用户收货地址页中,需要用户填写地址。在填写地址时需要选择不同的省份市区。由于省份等信息是固定不变的,用户只需从一组数据中选择,因此在这里采用Spinner控件实现,当用户单击此控件时,下拉列表中显示出所有省份信息选项供用户选择,如图51所示。 图51修改地址页面 视频详解 5.1.1Spinner控件 下拉列表Spinner类似于下拉菜单,默认情况下展示列表项中的第一项,单击Spinner控件时会弹出一个包含所有数据的下拉列表。Spinner比较节省空间,常用于固定值选择或者条件筛选等。例如,在用户输入地址信息的时候,在选择省份或者地区时通常可以采用Spinner列表控件提供给用户,让用户从中选择,这样可减少用户的输入和避免用户输入错误信息。 使用列表控件的关键步骤如下。 (1) 在布局文件中添加列表控件,在Java代码文件中通过id属性获取到列表控件。 (2) 准备数据源,数据源可以是数组或者集合。 (3) 构建Adapter对象,指定列表中每一项数据的显示样式,并将数据源数据通过构造函数等方式传递给Adapter对象。 (4) 列表控件调用setAdapter()方法,关联创建好的Adapter对象,展示数据源中的数据。 下面就以选择编程语言为例,讲解Spinner控件的具体用法。 (1) 创建名字为chart0501的应用程序,指定包名为com.example.administrator.chart0501。 (2) 设置布局文件,在res/layout/activity_main.xml文件中添加一个TextView用于显示标题,再添加一个Spinner控件用于显示下拉列表。布局文件的核心代码如下。 程序清单51: chart0501\app\src\main\res\layout\activity_main.xml 1<?xml version="1.0" encoding="utf-8"?> 2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3xmlns:tools="http://schemas.android.com/tools" 4android:layout_width="match_parent" 5android:layout_height="wrap_content" 6android:orientation="horizontal" 7android:gravity="center_vertical" 8tools:context=".MainActivity"> 9<TextView 10android:layout_width="wrap_content" 11android:layout_height="wrap_content" 12android:text="你学习的编程语言:" 13android:textSize="16sp"/> 14<Spinner 15android:id="@+id/spinner"→Spinner添加id属性 16android:layout_width="wrap_content"→为Spinner设置宽度 17android:layout_height="wrap_content"/>→为Spinner设置高度 18</LinearLayout> (3) Spinner列表由若干Item组成,每个Item显示编程语言的名称,因此需要设置Item选项的布局。在res/layout文件夹中创建一个Item界面的布局文件simple_list_item.xml,在该文件中添加一个TextView用来展示每项的内容。注意: 不需要在TextView的外层添加布局,完整的布局文件代码如下。 程序清单52: chart0501\app\src\main\res\layout\simple_list_item.xml 1<?xml version="1.0" encoding="utf-8"?> 2<TextView xmlns:android="http://schemas.android.com/apk/res/android" 3android:layout_width="wrap_content" 4android:layout_height="wrap_content" 5android:padding="5dp" 6android:textSize="16sp"/> (4) 编写界面交互代码,在MainActivity中定义一个数组programLang,存储Spinner下拉列表中显示的编程名称,并创建ArrayAdapter对象,调用Spinner控件的setAdapter()方法,将Adapter与列表控件关联起来,实现数据适配,具体代码如下。 程序清单53: chart0501\java\com\example\administrator\chart0401\MainActivity.java 1public class MainActivity extends AppCompatActivity { 2private String[] programLang = {"Java", "C#", →定义数组保存编程语言 3 "Python", "Javascript", "C++", "C"}; 4private Spinner spinner; 5@Override 6protected void onCreate(Bundle savedInstanceState) { 7super.onCreate(savedInstanceState); 8setContentView(R.layout.activity_main); 9spinner = (Spinner) findViewById(R.id.spinner);→获取Spinner对象 10ArrayAdapter<String> stringArrayAdapter = new ArrayAdapter<String> 11(this, R.layout.simple_list_item, programLang); 12→创建适配器对象 13spinner.setAdapter(stringArrayAdapter); →为Spinner对象绑定适配器对象 14} 15} 上述代码中,第2~3行代码定义一个数组programLang,存储Spinner下拉列表中显示的编程名称。第10行代码定义一个ArrayAdapter对象,并传入三个参数,第一个参数表示当前对象,第二个参数表示每个Item的布局文件,第三个参数表示数据源。第13行代码通过setAdapter()方法为Spinner控件设置适配器。 上述代码的运行效果如图52所示。 图52程序运行效果 视频详解 5.1.2Adapter适配器 在为Spinner控件添加数据的时候用到了数据适配器(Adapter),数据适配器是数据和视图之间的桥梁,类似于一个转换器,将复杂的数据转换成用户设定的样式呈现出来,在Android系统中提供了多种适配器,它们之间的关系如图53所示。 图53Adapter相关的接口 从图53可知,实现Adapter相关接口的基类是BaseAdapter,它实现了按口里的大部分方法,对于少数需要根据具体情景才能确定的方法则没有实现,因此它被声明为不能实例化。针对一些常用情景系统提供了三个子类: ArrayAdapter、SimpleAdapter、CursorAdapter。在使用时需要自定义一个Adapter继承自BaseAdapter,并重写里面的getView()、getCount()、geItem()、getItemId()方法。自定义Adapter的优点是可以使数据按照设定的形式显示,非常灵活,缺点是代码相对较多,需要用户自己重写各个方法。 (1) ArrayAdapter: 默认情况下只能显示文本,如果想显示其他的View控件,比如 ImageView,需要重写getView()方法。通常,ArrayAdapter的数据源是数组或者集合的形式。 ArrayAdapter有多个构造函数,以其中两个举例说明: public ArrayAdapter (Context context, int resource,int textViewResourceId, T[] objects); public ArrayAdapter (Context context, int resource, List<T> objects)。 ArrayAdapter()构造方法中的5个参数含义如下。 ① Context: context上下文对象。 ② resource: Item布局的资源id。 ③ textViewResourceId: Item布局中相应TextView的id。 ④ T[] objects: 需要适配的数组类型的数据。 ⑤ List<T> objects: 需要适配的List类型的数据。 (2) SimpleAdapter: 可用于显示列表项相对复杂的列表,要求所有的列表项结构相同,显示样式相同。SimpleAdapter继承自BaseAdapter并实现BaseAdapter的四个抽象方法并对其进行封装。因此在使用SimpleAdapter进行数据适配时,只需要在构造方法中传入相应的参数即可。 SimpleAdapter的构造方法的具体信息如下。 public SimpleAdapter (Context context, List<? extends Map<String, ?>> data,int resource, string[] from, int[] to) SimpleAdapter()构造方法中的5个参数的含义如下。 ① context: 表示上下文对象。 ② data: 数据集合,data中的每一项对应ListView控件中的条目的数据。 ③ resource: Item布局的资源id。 ④ from: Map集合中的key值。 ⑤ to: Item布局中对应的控件。 (3) BaseAdapter: 本质上是一个抽象类,通常在自定义适配器时会继承BaseAdapter。该类有四个抽象方法,在使用自定义适配器时需要实现这几个抽象方法,如表51所示。 表51BaseAdapter类抽象方法 方法名功 能 描 述 public int getCount()获取Item条目的总数 public object getItem(int position)根据position(位置)获取某个Item对象 public long getItemId(int position)根据position(位置)获取某一个Item的id public View getView(int position, View convertView, ViewGroup parent)获取相应position对应的Item视图 视频详解 5.1.3实战演练——收货地址 通过对Spinner的学习,我们可以对“欢乐购商城”中修改地址页面进行实现。布局页面的代码如下。 程序清单54: chart0502\app\src\main\res\layout\activity_main.xml 1<?xml version="1.0" encoding="utf-8"?> 2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3xmlns:tools="http://schemas.android.com/tools" 4android:layout_width="match_parent" 5android:layout_height="match_parent" 6tools:context=".MainActivity"> 7<EditText 8android:id="@+id/edit_name" 9android:layout_width="match_parent" 10android:layout_height="wrap_content" 11android:layout_marginTop="5dp" 12android:hint="姓名" /> 13<EditText 14android:id="@+id/edit_phone" 15android:layout_width="match_parent" 16android:layout_height="wrap_content" 17android:layout_below="@+id/edit_name" 18android:layout_marginTop="5dp" 19android:hint="电话" /> 20<TextView 21android:id="@+id/tv_proName" 22android:layout_width="wrap_content" 23android:layout_height="30dp" 24android:layout_below="@+id/edit_phone" 25android:layout_marginTop="5dp" 26android:gravity="center" 27android:text="省" /> 28<Spinner 29android:id="@+id/proviceSpinner"→Spinner添加id属性 30android:layout_width="wrap_content"→为Spinner设置宽度 31android:layout_height="30dp"→为Spinner设置高度 32android:layout_alignTop="@+id/tv_proName" 33android:layout_marginLeft="10dp" 34android:layout_toRightOf="@+id/tv_proName"> </Spinner> 35<TextView 36android:id="@+id/tv_cityName" 37android:layout_width="wrap_content" 38android:layout_height="30dp" 39android:layout_alignBottom="@+id/proviceSpinner" 40android:layout_marginTop="5dp" 41android:layout_toRightOf="@+id/proviceSpinner" 42android:text="市" 43android:gravity="center" 44android:textSize="13sp" /> 45<Spinner 46android:id="@+id/citySpinner" 47android:layout_width="wrap_content" 48android:layout_height="30dp" 49android:layout_alignBottom="@+id/tv_cityName" 50android:layout_toRightOf="@+id/tv_cityName"/> 51<TextView 52android:id="@+id/tv_countryName" 53android:layout_width="wrap_content" 54android:layout_height="30dp" 55android:layout_alignBottom="@+id/citySpinner" 56android:layout_marginTop="5dp" 57android:layout_toRightOf="@+id/citySpinner" 58android:gravity="center" 59android:text="县" /> 60<Spinner 61android:id="@+id/countrySpinner" 62android:layout_width="wrap_content" 63android:layout_height="30dp" 64android:layout_alignBottom="@+id/tv_countryName" 65android:layout_toRightOf="@+id/tv_countryName"/> 66<EditText 67android:id="@+id/edit_detailAddress" 68android:layout_width="match_parent" 69android:layout_height="100dp" 70android:layout_below="@+id/tv_proName" 71android:layout_marginTop="10dp" 72android:background="@drawable/textview_borders" 73android:gravity="top" 74android:minLines="5" 75android:textSize="16sp" 76android:text="中华路58号"/> 77</RelativeLayout> spinner_item.xml的布局文件如下。 程序清单55: char0502\app\src\main\res\layout\spinner_item.xml 1<?xml version="1.0" encoding="utf-8"?> 2<TextView xmlns:android="http://schemas.android.com/apk/res/android" 3android:layout_width="match_parent" 4android:layout_height="wrap_content" 5android:padding="5dp" 6android:textSize="14sp" /> MainActivity页面的代码如下。 程序清单56: char0502app\src\main\\res\layout\activity_main.xml 1public class MainActivity extends AppCompatActivity { 2private EditText edit_name; 3private EditText edit_phone; 4private Spinner proPinner; 5private EditText detailAddress; 6private Spinner citySpinner; 7private Spinner countrySpinner; 8private String provinceName; 9private String cityName; 10private String[] partCitys; 11private String[] partCountry; 12private String[] province = {"江西省", "河南省", "湖北省"}; 13@Override 14protected void onCreate(Bundle savedInstanceState) { 15super.onCreate(savedInstanceState); 16setContentView(R.layout.activity_main); 17initView(); 18initData(); 19} 20private void initView() {→创建方法初始化控件 21edit_name = (EditText) findViewById(R.id.edit_name); 22edit_phone = (EditText) findViewById(R.id.edit_phone); 23proPinner = (Spinner) findViewById(R.id.proviceSpinner); 24citySpinner = (Spinner) findViewById(R.id.citySpinner); 25countrySpinner = (Spinner) findViewById(R.id.countrySpinner); 26detailAddress = (EditText) findViewById(R.id.edit_detailAddress); 27} 28private void initData() { 29edit_name.setText("李玲");→为edit_name设置数据 30edit_phone.setText("13789325643");→为edit_phone设置数据 31detailAddress.setText("气象路58号"); 32proPinner.setAdapter(new ArrayAdapter<String>(this, R.layout.spinner_item, province)); →创建适配器对象,并为proPinner绑定该对象 33proPinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { →为proPinner设置项被选择的事件 34@Override 35public void onItemSelected(AdapterView<?> parent, View 36view, int position, long id) { 37provinceName = province[position]; 38switch (position) { →根据选择不同的省份,加载不同的地区 39case 0: 40partCitys = new String[]{"南昌", "九江", "赣州", "上饶"}; 41citySpinner.setAdapter(new ArrayAdapter<String>(MainActivity.this, R.layout.spinner_item, partCitys)); →创建地区的适配器对象,并为citySpinner绑定该对象 42 43break; 44case 1: 45partCitys = new String[]{"郑州", "南阳", "洛阳", "安阳"}; 46citySpinner.setAdapter(new ArrayAdapter<String>(MainActivity.this, 47R.layout.spinner_item, partCitys)); 48break; 49case 2: 50partCitys = new String[]{"武汉", "黄石", "阳新", "鄂州"}; 51citySpinner.setAdapter(new ArrayAdapter<String> (MainActivity.this,R.layout.spinner_item, partCitys)); 52 53break; 54} 55} 56@Override 57public void onNothingSelected(AdapterView<?> parent) { 58} 59}); 60 61 62 63citySpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { →为citySpinner设置项被选择事件 64@Override 65public void onItemSelected(AdapterView<?> parent, View 66view, int position, long id) { 67cityName = partCitys[position]; 68switch (position) { →根据选择不同的地区,加载不同的县 69case 0: 70partCountry = new String[]{"红谷滩新区", "东湖区", "西湖区"}; 71countrySpinner.setAdapter(new →创建县的适配器对象,并为countrySpinner绑定该对象 72ArrayAdapter<String>(MainActivity.this, R.layout.spinner_item, 73partCountry)); 74break; 75case 1: 76partCountry = new String[]{"修水", "永修", "都昌", "德安"}; 77countrySpinner.setAdapter(new ArrayAdapter<String>(MainActivity.this, R.layout.spinner_item, partCountry)); break; 78 79 80case 2: 81partCountry = new String[]{"信丰", "大余", "兴国", "于都"}; 82countrySpinner.setAdapter(new ArrayAdapter<String>( 83MainActivity.this, R .layout.spinner_item, partCountry)); 84break; 85} 86} 87@Override 88public void onNothingSelected(AdapterView<?> parent) { 89} 90}); 91} 92} 上述程序运行效果如图54所示。 图54程序运行效果 视频详解 5.2普通列表ListView “欢乐购商城”项目中订单页面使用到了ListView控件,下面对修改订单页面做概要分析。 分析: 在订单页面需要展示用户的订单信息,用户的订单信息展示的内容的格式是一致的,有订单商品标题、订单状态、总价和创建时间。对于列表展示并且每一项的格式一致适合采用ListView控件实现,如图55所示。 图55订单中心 视频详解 5.2.1ListView控件 ListView是使用非常广泛的一种列表控件,它以垂直列表的形式展示所有的数据项,并且能够根据列表的高度自适应屏幕显示,也是早期Android开发中用于列表界面开发最多的控件。早期的开发者为了拓展ListView,对其进行继承和改写,也为其丰富了许多独特的功能,例如,经典的上拉加载新的列表数据,下拉刷新原有的列表数据等。 下面就以展示植物信息为例,讲解ListView控件的具体用法。 (1) 创建程序,创建名字为chart0502的应用程序。 (2) 导入图片,在Android Studio中切换到Project选项卡,选中程序中的res文件夹,右击选择New→Directory选项,创建一个名为drawablehdpi的文件夹。将程序所需要的图片导入到文件夹。 (3) 设置布局文件,在res/layout/activity_main.xml文件中添加一个ListView控件,并对其相应属性进行设置,布局文件的核心代码如下。 程序清单57: char0503\app\src\main\res\layout\activity_main.xml 1<ListView 2android:id="@+id/listView" 3android:layout_width="match_parent" 4android:layout_height="match_parent" 5android:listSelector="#A9A9A9"></ListView> (4) 设置每个Item的布局,在res/layout文件夹中创建一个Item界面的布局文件list_item.xml,在该文件中添加一个ImageView用来显示植物的图片,添加两个TextView分别用于展示植物的名称和植物的描述。完整的布局文件代码如下。 程序清单58: chart0503\app\src\main\res\layout\list_item.xml 1<?xml version="1.0" encoding="utf-8"?> 2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3android:layout_width="match_parent" 4android:layout_height="match_parent"> 5<ImageView 6android:id="@+id/iv" 7android:layout_width="80dp" 8android:layout_height="80dp" 9android:layout_margin="5dp" 10android:background="@drawable/b" 11android:scaleType="centerCrop" /> 12<TextView 13android:id="@+id/tv_title" 14android:layout_width="match_parent" 15android:layout_height="wrap_content" 16android:layout_alignTop="@id/iv" 17android:layout_toRightOf="@id/iv" 18android:text="植物名称" 19android:textSize="18sp" 20android:textStyle="bold" /> 21<TextView 22android:id="@+id/tv_depict" 23android:layout_width="match_parent" 24android:layout_height="wrap_content" 25android:layout_below="@id/tv_title" 26android:layout_marginTop="10dp" 27android:layout_toRightOf="@id/iv" 28android:text="植物的描述" 29android:textSize="16sp" /> 30</RelativeLayout> (5) 在MainActivity中创建一个内部类MyBaseAdapter继承自BaseAdapter,并且在MyBaseAdapter中实现对ListView控件的适配。完整的代码如下。 程序清单59: chart0503\java\com\example\administrator\chart0503\MainActivity.java 1public class MainActivity extends AppCompatActivity { 2private ListView mListView; 3private int[] icons = {R.drawable.a, R.drawable.b, 4R.drawable.c, R.drawable.d, R.drawable.e, R.drawable.f}; 5private String[] titles = {"白鹃梅", "五色椒", "小檗", 6"李叶绣线菊", "多花兰", "獐耳细辛"}; 7private String[] depicts = 8{"白鹃梅又名茧子花、金瓜果等,是蔷薇科白鹃梅属灌木。", 9"五色椒又名朝天椒,五彩辣椒,为辣椒 变种,味涩", 10"小檗春日黄花簇簇,秋日红果满枝", "蔷薇科、绣线菊属灌木 11,高可达3米。", "多花兰是兰科兰属的一种附生植物", 12"植物獐耳细辛,分布于我国辽宁、安徽、浙江、河南"}; 13@Override 14protected void onCreate(Bundle savedInstanceState) { 15super.onCreate(savedInstanceState); 16setContentView(R.layout.activity_main); 17mListView = (ListView) findViewById(R.id.listView); 18MyBaseAdapter myBaseAdapter = new MyBaseAdapter(); 19mListView.setAdapter(myBaseAdapter); 20} 21 22private class MyBaseAdapter extends BaseAdapter { 23@Override 24public int getCount() { 25return titles.length; 26} 27@Override 28public Object getItem(int position) { 29return titles[position]; 30} 31@Override 32public long getItemId(int position) { 33return position; 34} 35@Override 36public View getView(int position, View convertView, ViewGroup parent) { 37View view = View.inflate(MainActivity.this, R.layout.list_item, null); 38TextView title = (TextView) view.findViewById(R.id.tv_title); 39TextView depict = (TextView) view .findViewById(R.id.tv_depict); 40ImageView image = (ImageView) view .findViewById(R.id.iv); 41image.setBackgroundResource(icons[position]); 42title.setText(titles[position]); 43depict.setText(depicts[position]); 44return view; 45} 46} 47} 图56程序运行效果 上述代码中,在第3~12行代码定义了三个数组: titles、depicts和icons,分别存储植物的名称、植物的描述和植物的图片,并且这三个数组的长度是一致的。 第22~47行创建了一个MyBaseAdapter继承自BaseAdapter类,并重写了BaseAdapter类中的getCount()、getItem()、getItemId()和getView()方法。在getView()方法中通过inflate()方法将layout.list_item布局转换成视图对象。并通过findViewById()获取到layout.list_item布局中的各个控件,最后通过setText()和setBackgroundResource()方式将文本信息和图片信息展示出来。 该程序的运行效果如图56所示。 视频详解 5.2.2提升ListView运行效率 在运行上述代码的时候,当ListView上加载的Item过多并且快速滑动ListView控件的时候,界面会出现卡顿。出现此状况的原因如下。 (1) 当不断滑动ListView控件时,就会不断创建Item对象。ListView控件在屏幕上显示多少个Item,就会在适配器MyBaseAdapter中的getView()中创建多少个Item对象。当滑动ListView控件时,滑出屏幕的Item对象会被销毁,新加载到屏幕上的Item会创建新的对象,因此在滑动的过程中就是在不断地创建和销毁Item对象。 (2) 在getView()中不断执行findViewById()方法初始化控件。每当创建一个Item对象就会加载一个Item布局,在加载布局的过程中会不断调用findViewById()方法初始化控件。这些操作要消耗设备的内存,因此不断滑动ListView就是在不断地初始化控件,当占用内存过多的时候,就会出现程序内存溢出异常。 针对上述问题,需要对ListView控件进行优化,优化的目标是在ListView不断滑动过程中不再重复创建Item对象,减少内存的消耗。具体操作如下。 (1) 在MainActivity类中创建ViewHolder类。 程序清单510: chart0503\java\com\example\administrator\chart0503\MainActivity.java 1public class ViewHolder{ 2TextView tv_title; 3TextView tv_depict; 4ImageView image; 5} (2) 在MyBaseAdapter的getView()方法中第二个参数convertView代表滑出屏幕的Item的缓存对象,当第一次加载ListView的时候创建Item对象,当滑动ListView控件的时候,在加载新的Item对象的时候可以复用缓存的convertView对象。在getView()中进行优化,具体代码如下。 程序清单511: chart0503\java\com\example\administrator\chart0503\MainActivity.java 1public View getView(int position, View convertView, ViewGroup parent) { 2 3 4ViewHolder viewHolder; 5if (convertView == null) { 6convertView = View.inflate(MainActivity.this, R .layout.list_item, null); 7viewHolder = new ViewHolder(); 8viewHolder.tv_title = (TextView) convertView.findViewById(R.id.tv_title); 9viewHolder.tv_depict = (TextView) convertView.findViewById(R.id.tv_depict); 10viewHolder.image = (ImageView) convertView.findViewById(R.id.iv); 11convertView.setTag(viewHolder); } 12else { 13viewHolder = (ViewHolder) convertView.getTag(); 14} 15viewHolder.image.setBackgroundResource(icons[position]); 16viewHolder.tv_title.setText(titles[position]); 17viewHolder.tv_depict.setText(depicts[position]); 18return convertView; 19} 20} 在上述代码中第5~18行中首先判断convertView对象是否为null,如果是null表示是第一次加载Item项,需要使用inflate()创建Item对象并通过findViewById()方法找到控件。创建viewHolder对象,并将Item中的界面控件对象赋值给viewHolder对象的属性,最后通过setTag()和getTag()方法获取缓存在convertView对象中的ViewHolder对象。 视频详解 5.2.3ListView的单击事件 在使用ListView时,当单击某一个Item的时候,可能还需要进行其他操作。例如,在展示商品信息的时候,ListView可展示商品概要信息,当单击某一商品时,需要跳转到另外一个页面展示商品的详细信息,此时就需要触发ListView中Item的单击事件。在ListView中常用的单击事件分为普通的单击事件和长按单击事件,下面以代码的形式说明如何使用单击事件。 在onCreate( )方法中添加以下代码。 程序清单512: chart0503\java\com\example\administrator\chart0503\MainActivity.java 1mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 2@Override 3public void onItemClick(AdapterView<?> parent, View view, int position, 4long id) { 5String content = "植物名称:" +titles[position] +"。描述:" 6+ depicts[position]; 7Toast.makeText(MainActivity.this, 8content, Toast.LENGTH_LONG).show(); 9} 10}); 上述代码中第1行使用setOnItemClickListener()方法为ListView注册一个项被单击的监听器。当用户单击ListVew中的任意Item的时候就会回调onItemClick()方法,在这个方法中通过position参数判断用户单击的是哪一个子项,然后获取到相应数组对应的项。最后通过Toast将植物的信息展示出来。 代码运行效果如图57所示。 对于ListView中的每个Item,当长按某一个Item的时候可以触发长按事件,下面以代码的形式说明如何使用长按事件。 在onCreate() 方法中添加以下代码。 程序清单513: chart0503\java\com\example\administrator\chart0503\MainActivity.java 1mListView.setOnItemLongClickListener(new AdapterView .OnItemLongClickListener() { 2@Override 3public boolean onItemLongClick(AdapterView<?> 4parent, View view, int position,long id) { 5AlertDialog alertDialog = new AlertDialog 6.Builder(MainActivity.this).setMessage 7("确定要删除吗?") 8.setPositiveButton("确定", null) 9.setNegativeButton("取消", null) 10.create(); 11alertDialog.show(); 12return true; 13} 14}); 在上述代码中第1行使用 setOnItemLongClickListener()方法为ListView注册一个监听器。当用户长按ListVew中的任意Item的时候就会回调onItemLongClick()方法,在这个方法中通过创建对话框,提示用户是否确定要删除。 第5~13行代码创建一个对话框,通过调用AlterDialog的静态内部类Builder的方法创建AlterDialog对象。通过调用Builder类的setMessage()方法设置提示的内容,通过setPositiveButton()和setPositiveButton()设置对话框的“确定”和“取消”按钮。通过调用AlertDialog对象的show()方法显示该对话框。 上述代码运行效果如图58所示。 图57程序运行效果 图58程序运行效果 视频详解 5.2.4实战演练——订单中心 通过对ListView控件的学习,我们可以对“欢乐购商城”中的订单页面进行实现。布局页面代码如下。 程序清单514: chart0504\app\src\main\res\layout\activity_main.xml 1<?xml version="1.0" encoding="utf-8"?> 2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3xmlns:tools="http://schemas.android.com/tools" 4android:layout_width="match_parent" 5android:layout_height="match_parent" 6tools:context=".MainActivity"> 7<ListView 8android:id="@+id/order_ListView" 9android:layout_width="match_parent" 10android:layout_height="match_parent"/> 11</RelativeLayout> 其中Item的布局文件如下。 程序清单515: chart0504\ListView\res\layout\order_list_items.xml 1<?xml version="1.0" encoding="utf-8"?> 2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3android:layout_width="match_parent" 4android:layout_height="match_parent"> 5 6<TextView 7android:id="@+id/goods_title" 8android:layout_width="match_parent" 9android:layout_height="wrap_content" 10android:layout_margin="8dp" 11android:text="云南天然干花花束真花勿忘我家居客厅摆设超大花束" 12android:textSize="15sp" 13android:textStyle="bold" /> 14 15<TextView 16android:id="@+id/order_status" 17android:layout_width="wrap_content" 18android:layout_height="wrap_content" 19android:layout_below="@id/goods_title" 20android:layout_marginLeft="5dp" 21android:text="订单状态:已结算" /> 22 23<TextView 24android:id="@+id/order_value" 25android:layout_width="wrap_content" 26android:layout_height="wrap_content" 27android:layout_alignParentRight="true" 28android:layout_below="@id/goods_title" 29android:layout_marginRight="8dp" 32android:text="总价:80元" /> 33 34<TextView 35android:id="@+id/order_time" 36android:layout_width="wrap_content" 37android:layout_height="wrap_content" 38android:layout_below="@id/order_status" 39android:layout_margin="8dp" 40android:text="创建时间:" /> 41</RelativeLayout> MainActivity页面的代码如下。 程序清单516: chart0504\java\com\example\administrator\chart0504\MainActivity.java 1public class MainActivity extends AppCompatActivity { 2private ListView orderList; 3private String[] orderTitle = {"云南天然干花花束真花勿忘我家居客厅摆设超大花束", "2019秋装新款白色纯棉长袖t恤女装宽松春秋打底衫体恤大码上衣", 4"鞋袋子装鞋子的收纳袋旅行鞋包收纳包防尘袋家用鞋罩束口鞋袋鞋套"}; 5private String[] orderValue = {"145", "213", "89"}; 6@Override 7protected void onCreate(Bundle savedInstanceState) { 8super.onCreate(savedInstanceState); 9setContentView(R.layout.activity_main); 10orderList = (ListView) findViewById(R.id.order_ListView); 11orderList.setAdapter(new myListAdapter()); 12} 13private class myListAdapter extends BaseAdapter { 14private ViewHolder viewHolder; 15@Override 16public int getCount() { 17return orderTitle.length; 18} 19@Override 20public Object getItem(int position) { 21return orderTitle[position]; 22} 23@Override 24public long getItemId(int position) { 25return position; 26} 27@Override 28public View getView(int position, View convertView, ViewGroup parent) { 29if (convertView == null) { 30convertView = View.inflate(MainActivity.this, R.layout.order_list_items, null); 31viewHolder = new ViewHolder(); 32viewHolder.orderTitle = (TextView) convertView. findViewById(R.id.goods_title); 33viewHolder.totalValue = (TextView) convertView.findViewById(R.id.order_value); 34viewHolder.orderTime = (TextView) convertView.findViewById(R.id.order_time); 35convertView.setTag(viewHolder); 36} else { 37viewHolder = (ViewHolder) convertView.getTag(); 38} 39SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 40viewHolder.orderTitle.setText(orderTitle[position]); 41viewHolder.totalValue.setText("总价:" + orderValue[position] + "元"); 42viewHolder.orderTime.setText("创建时间:" + df.format(new Date())); 43return convertView; 44} 45 46class ViewHolder { 47TextView orderTitle; 48TextView totalValue; 49TextView orderTime; 50} 51} 52} 上述程序运行结果如图59所示。 图59程序运行效果 视频详解 5.3网格列表GridView “欢乐购商城”首页使用GridView控件使推荐商品按照网格的形式展示,下面对首页中的推荐商品列表做概要分析。 分析: 在首页中需要展示推荐商品,展示的商品只需要展示概要信息并尽可能多展示一些商品,如果采用列表的形式展示,每行只能展示一个列表项。为了多展示一些商品信息,在项目中采用GridView控件展示商品,使每行可展示两个列表项,如图510所示。 图510列表展示商品 视频详解 5.3.1GridView控件 前面所学的Spinner和ListView显示列表数据时,都是以垂直方向显示,每行只显示一个列表项。无法实现一行显示多个列表项的效果。如果要实现多行多列的展示效果,可使用GridView 控件。GridView可将界面划分为若干个网格,可以设置每一行所能显示的列表项数量,然后根据总的列表项数来计算一共有多少行。例如,总共有15个列表项,每行存放4个列表项,则包含4行,如果每行存放3个列表项,则包含5行。使用GridView 时,关键属性如表52所示。 表52GridView关键属性 属性名功 能 描 述 android: numColumns每行中列的数量 android: horizontalSpacing设置两个列表项之间的水平间距 android: verticalSpacing设置两个列表项之间的垂直间距 下面以展示植物信息的例子讲解GridView的用法。 (1) 创建程序,创建名字为chart0503的应用程序。 (2) 导入图片,在Android Studio中切换到Project选项卡,选中程序中res文件夹,右击选择New→Directory选项,创建一个名为drawablehdpi的文件夹。将程序所需要的图片导入到文件夹。 (3) 设置布局文件,在res/layout/activity_main.xml文件中添加一个ListView控件,并对相应属性进行设置。布局文件的核心代码如下。 程序清单517: chart0505\app\src\main\res\layout\activity_main.xml 1<GridView 2android:id="@+id/gridView" 3android:layout_width="match_parent" 4android:layout_height="wrap_content" 5android:horizontalSpacing="3dp" 6android:numColumns="4" 7android:verticalSpacing="3dp"></GridView> (4) 设置每个Item的布局,在res/layout文件夹中创建一个Item界面的布局文件grid_item.xml,在该文件中添加一个ImageView用来展示植物的图片,添加一个TextView用于展示植物的名称。完整的布局文件代码如下。 程序清单518: chart0505\app\src\main\res\layout\grid_item.xml 1<?xml version="1.0" encoding="utf-8"?> 2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3android:layout_width="wrap_content" 4android:layout_height="wrap_content" 5android:background="#cccccc" 6android:gravity="center_horizontal" 7android:orientation="vertical" 8android:padding="3dp"> 9<ImageView 10android:id="@+id/image" 11android:layout_width="100dp" 12android:layout_height="90dp" 13android:scaleType="fitXY" 14android:src="@drawable/a" /> 15<TextView 16android:id="@+id/tv_title" 17android:layout_width="wrap_content" 18android:layout_height="wrap_content" 19android:layout_below="@id/image" 20android:text="铁茉莉" 21android:textSize="15sp" /> 22</LinearLayout> (5) 在MainActivity中创建一个SimpleAdapter对象,gridView对象通过setAdapter()方法将SimpleAdapter对象与gridView控件关联起来,实现数据适配。完整的代码如下。 程序清单519: chart0505\java\com\example\administrator\chart0505\MainActivity.java 1public class MainActivity extends AppCompatActivity { 2private GridView gridView; 3private List<Map<String, Object>> datas = new ArrayList<Map<String, Object>>(); 4 5private int[] icons = {R.drawable.a, R.drawable.b, R .drawable.c, 6R.drawable.d, R.drawable.e,R.drawable.f, R.drawable.g, 7R.drawable.h, R.drawable.i, R.drawable.j, R.drawable.k, 8R.drawable.l}; 9private String[] titles = {"白鹃梅", "五色椒", "小檗", 10"李叶绣菊", "多花兰", "獐耳细辛", "铁茉莉", "三色狸藻", "东洋菊", 11"大花皇冠", "苹果花", "三弄芙蓉"}; 12@Override 13protected void onCreate(Bundle savedInstanceState) { 14super.onCreate(savedInstanceState); 15setContentView(R.layout.activity_main); 16gridView = (GridView) findViewById(R.id.gridView); 17initData(); 18SimpleAdapter simpleAdapter = new SimpleAdapter 19(this, datas, R.layout.grid_item, new 20String[]{"image", "titles"}, new int[]{R 21.id.image, R.id.tv_title}); 22gridView.setAdapter(simpleAdapter); 23} 24private void initData() { 25for (int i = 0; i < titles.length; i++) { 26Map<String, Object> item = new 27HashMap<String, Object>(); 28item.put("titles", titles[i]); 29item.put("image", icons[i]); 30datas.add(item); 31} 32} 33} 上述代码中,在第3行定义一个List集合对象,用于存储植物标题和植物图片的信息。第5行和第9行分别定义两个数组用于保存植物的标题和图片。 第24~32行定义一个方法initData()将数组中的数据以键值对的形式保存在Map对象中,将每个Map对象保存到List集合对象中。 第18行定义SimpleAdapter对象,调用其构造函数,并传入5个相应的参数。第一个参数表示上下文对象,第二个参数表示数据源,第三个参数表示Item布局资源的id,第四个参数表示Map集合中的key值,第五个参数表示Item布局中key值所代表的value所绑定的控件。 第22行通过setAdapter()将SimpleAdapter对象设置给gridView实现数据适配。 上述程序运行效果如图511所示。 图511程序运行效果 在上述案例中,GridView控件使用的是SimpleAdapter对象实现了数据的适配,如果Item要实现更加复杂的布局,可采用继承BaseAdapter抽象类的方法来实现。BaseAdapter的使用已在ListView中做过相应展示,在这里不再演示。对于GridView中每个Item也可以设置其单击事件,包括普通的单击事件和长按事件,其用法已在ListView中做过相应的展示,在这里不再演示。 视频详解 5.3.2实战演练——首页商品列表 通过对GridView的学习,我们可以对“欢乐购商城”中的首页进行实现。其中布局页面的代码如下。 程序清单520: chart0506\app\src\main\res\layout\activity_main.xml 1<?xml version="1.0" encoding="utf-8"?> 2<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" 3android:layout_width="match_parent" 4android:layout_height="match_parent" 5android:gravity="center"> 6<LinearLayout 7android:layout_width="match_parent" 8android:layout_height="match_parent" 9android:orientation="vertical"> 10<ImageView 11android:layout_width="match_parent" 12android:layout_height="150dp" 13android:scaleType="fitXY" 14android:src="@drawable/jhs" /> 15<TableLayout 16android:id="@+id/table_smallIcon" 17android:layout_width="match_parent" 18android:layout_height="wrap_content" 19android:gravity="center"> 20<TableRow> 21<ImageView 22android:id="@+id/a" 23android:layout_width="73dp" 24android:layout_height="73dp" 25android:clickable="true" 26android:src="@drawable/a" /> 27<ImageView 28android:id="@+id/b" 29android:layout_width="73dp" 32android:layout_height="73dp" 33android:src="@drawable/b" /> 34<ImageView 35android:id="@+id/c" 36android:layout_width="73dp" 37android:layout_height="73dp" 38android:src="@drawable/c" /> 39<ImageView 40android:id="@+id/d" 41android:layout_width="73dp" 42android:layout_height="73dp" 43android:src="@drawable/d" /> 44<ImageView 45android:id="@+id/e" 46android:layout_width="73dp" 47android:layout_height="73dp" 48android:src="@drawable/e" /> 49</TableRow> 50<TableRow> 51<ImageView 52android:id="@+id/f" 53android:layout_width="73dp" 54android:layout_height="73dp" 55android:src="@drawable/f" /> 56<ImageView 57android:id="@+id/g" 58android:layout_width="73dp" 59android:layout_height="73dp" 60android:src="@drawable/g" /> 61<ImageView 62android:id="@+id/h" 63android:layout_width="73dp" 64android:layout_height="73dp" 65android:src="@drawable/h" /> 66<ImageView 67android:id="@+id/i" 68android:layout_width="73dp" 69android:layout_height="73dp" 70android:src="@drawable/i" /> 71<ImageView 72android:id="@+id/k" 73android:layout_width="73dp" 74android:layout_height="73dp" 75android:src="@drawable/k" /> 76</TableRow> 77</TableLayout> 78<com.example.administrator.taobao.adapter.MyGridView 79android:id="@+id/gridView" 80android:layout_width="match_parent" 81android:layout_height="wrap_content" 82android:layout_marginTop="5dp" 83android:gravity="center" 84android:horizontalSpacing="5dp" 85android:numColumns="2" 86android:verticalSpacing="5dp"> 87</com.example.administrator.taobao.adapter.MyGridView> 88</LinearLayout> 89</ScrollView> 其中,Item的布局文件如下。 程序清单521: chart0506\app\src\main\res\layout\goods_detail_item.xml 1<?xml version="1.0" encoding="utf-8"?> 2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3android:layout_width="wrap_content" 4android:layout_height="wrap_content"> 5<ImageView 6android:id="@+id/iv_iamge" 7android:layout_width="160dp" 8android:layout_height="160dp" 9android:layout_marginTop="4dp" /> 10<TextView 11android:id="@+id/tv_title" 12android:layout_width="160dp" 13android:layout_height="wrap_content" 14android:layout_below="@+id/iv_iamge" 15android:ellipsize="end" 16android:maxLines="1" 17android:text="标题测试数据" 18android:textSize="14dp" /> 19<TextView 20android:id="@+id/tv_prices" 21android:layout_width="wrap_content" 22android:layout_height="wrap_content" 23android:layout_below="@id/tv_title" 24android:text="¥10" 25android:textColor="#f00" 26android:textSize="16sp" /> 27<TextView 28android:id="@+id/tv_numbers" 29android:layout_width="wrap_content" 32android:layout_height="wrap_content" 33android:layout_alignBottom="@id/tv_prices" 34android:layout_marginLeft="10dp" 35android:layout_toRightOf="@id/tv_prices" 36android:text="1000人付款" /> 37</RelativeLayout> 定义一个适配器类,代码如下。 程序清单522: chart0506\java\com\example\administrator\chart0506\GoodsAdapter.Java 1public class GoodsAdapter extends BaseAdapter { 2private Context context; 3private List<Good> goodsList; 4private ViewHolder viewHolder; 5public GoodsAdapter(Context context, List<Good> goodsList) { 6this.context = context; 7this.goodsList = goodsList; 8} 9@Override 10public int getCount() { 11return goodsList.size(); 12} 13@Override 14public Object getItem(int position) { 15return goodsList.get(position); 16} 17@Override 18public long getItemId(int position) { 19return position; 20} 21@Override 22public View getView(int position, View convertView, ViewGroup parent) { 23if (convertView == null) { 24convertView = LayoutInflater.from(context). inflate(R.layout.goods_detail_item, parent, false); 25viewHolder = new ViewHolder(); 26viewHolder.image = convertView.findViewById(R.id.iv_iamge); 27viewHolder.textTitle = convertView.findViewById(R.id.tv_title); 28viewHolder.textPrice = convertView.findViewById(R.id.tv_prices); 29viewHolder.textNumber = convertView.findViewById(R.id.tv_numbers); 30convertView.setTag(viewHolder); 31} else { 32viewHolder = (ViewHolder) convertView.getTag(); 33} 34viewHolder.image.setBackgroundResource(goodsList.get(position).getImage()); 35viewHolder.textTitle.setText(goodsList.get(position).getName()); 36viewHolder.textPrice.setText("¥"+goodsList.get(position).getPrice()); 37viewHolder.textNumber.setText(goodsList.get(position).getVolume()+"人付款"); 38return convertView; 39} 40class ViewHolder { 41ImageView image; 42TextView textTitle; 43TextView textPrice; 44TextView textNumber; 45} 46} MainActivity页面的代码如下。 程序清单523: chart0506\java\com\example\administrator\chart0506\MainActivity.java 1public class MainActivity extends AppCompatActivity { 2 3private MyGridView gridView; 4private List<Good> goodList = new ArrayList<Good>(); 5private int[] pictrue = {R.drawable.a000, R.drawable.a001, R.drawable.a002, 6R.drawable.a003,R.drawable.a004, R.drawable.a005, R.drawable.a006, 7R.drawable.a007, R.drawable.a008,R.drawable.a009}; 8private String[] title = {"2019秋装新款白色纯棉长袖t恤女装宽松春秋打底衫体恤大码上衣", "2019初秋新款潮韩版洋气女装宽松秋季短款衬衫女秋装方领长袖上衣", 9"纯棉长袖T恤女上衣2019新款竹节棉宽松秋衣女装薄款初秋打底衫潮", "半高领打底衫女装秋冬薄款洋气莫代尔堆堆领长袖t恤内搭针织上衣", 10"女装2019新款潮超火cec短袖女春夏季港味宽松百搭上衣T恤ins洋气", "夏大大胖mm韩版宽松秋装新款女装大码2019牛仔套装外套减龄连衣裙", 11"大姗姗家瘦瘦裤胖mm韩版大码女装秋季百搭外穿打底裤网红款小脚裤", "中国大陆','夏装紧身纯白色T恤女短袖修身女装女士纯棉2019新款上衣潮打底衫", 12"纯棉长袖T恤女上衣2019新款竹节棉宽松秋衣女装薄款初秋打底衫潮", "纯棉红色v领T恤女短袖修身夏女装2019新款潮体恤紧身上衣黑色短款",}; 13private String[] prices = {"39.9", "29.87", "29", "56", "20", "138", "97", "15", "78", "30"}; 14private String[] number = {"87816", "37384", "62165", "43221", "4561", "43211", 15"3011","764", "7656", "4567"}; 16@Override 17protected void onCreate(Bundle savedInstanceState) { 18super.onCreate(savedInstanceState); 19setContentView(R.layout.activity_main); 20initView(); 21} 22private void initView() { 23for (int i = 0; i < 10; i++) { 24Good good = new Good(); 25good.setImage(pictrue[i]); 26good.setName(title[i]); 27good.setPrice(Double.valueOf(prices[i])); 28good.setVolume(number[i]); 29goodList.add(good); 30} 31gridView = (MyGridView) findViewById(R.id.gridView); 32gridView.setAdapter(new GoodsAdapter(MainActivity.this, goodList)); 33gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 34@Override 35public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 36 37} 38}); 39} 40} 上述程序运行效果如图512所示。 图512程序运行效果 视频详解 5.4增强列表RecyclerView “欢乐购商城”项目中商品列表页面使用了RecyclerView控件,下面对商品列表页面做概要分析。 分析: 在商品列表页面中,需要展示分类商品的概要信息,包括商品图片、商品标题、商品产地和商品所属店铺等。每个列表项的布局较复杂,在项目中采用RecyclerView控件实现商品列表展示功能,如图513所示。 图513商品列表展示 视频详解 5.4.1RecyclerView控件 RecyclerView是Android 5.0以后提供的更为强大的列表控件,不仅可以轻松实现 ListView、GridView的效果,还优化了之前列表控件存在的各种不足之处,是目前官方推荐使用的列表控件。 RecyclerView控件可通过LayoutManager类设置列表项的布局管理器,用于控制列表项的整体布局,常见的布局管理器有: LinearLayoutManager (线性布局管理器,可以控制列表项按照水平从左到右摆放,或者垂直从上到下摆放)、GridLayoutManager (网格布局管理器,按照若干行或者若干列来摆放列表项)、StaggeredGridLayoutManager (交错网格布局管理器,可以实现瀑布流效果,网格不是整齐的而是有所偏移)。 RecyclerView控件使用RecyclerView.Adapter适配器,该适配器将BaseAdapter中的getView()方法拆分为onCreateViewHolder()方法和onBindViewHolder()方法,强制使用ViewHodler类,使代码编写更加规范,提高性能。RecyclerView控件复用Item对象是由控件自身完成,提高了灵活性。 为了使RecyclerView在所有的Android版本上都可以使用,Android团队将其定义在兼容包中,因此如果想使用RecyclerView控件首先需要在项目的build.gradle中添加相关的依赖库。其中,RecyclerView的版本可根据本机上安装的Android版本变化。 接下来通过加载植物信息的例子讲解RecyclerView的使用,并实现垂直列表、水平列表和网格列表效果。 (1) 创建程序,创建名字为chart0507的应用程序。 (2) 导入图片,在Android Studio中切换到Project选项卡,选中程序中res文件夹,右击,选择New→Directory选项,创建一个名为drawablehdpi的文件夹,将程序所需要的图片导入文件夹。 (3) RecyclerView是Android 5.0新增的控件,为了让RecyclerView在所有的Android版本中都能使用,RecyclerView被定义在support库中,因此在使用时,需要在项目的build.gradle中添加相应的依赖库。打开app\build.gradle文件,在dependencies{}节点中添加com.android.support: recyclerviewv7库。 dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:recyclerview-v7:28.0.0' … } 注意: 添加的com.android.support: recyclerviewv7库的版本需要与com.android.support: appcompat库的版本一致,否则会报错。 (4) 设置布局文件,在res\layout\activity_main.xml文件中添加一个RecyclerView控件,并设置相应属性,布局文件的核心代码如下。 程序清单524: char0507\app\src\main\res\layout\activity_main.xml 1<android.support.v7.widget.RecyclerView 2android:id="@+id/recyclerView" 3android:layout_width="match_parent" 4android:layout_height="match_parent"> 5</android.support.v7.widget.RecyclerView> (5) 设置每个Item的布局,由于每个Item的布局效果和GridView中的显示效果一样,把上一个项目的图片复制过来,新建一个布局文件命名为grid_item.xml。把上一节例子中的Item布局复制过来。 (6) 在MainActivity中获取RecyclerView对象并进行数据适配,最终将数据显示在列表界面中。完整的代码如下。 程序清单525: chart0507\java\com\example\administrator\chart0507\MainActivity.java 1public class MainActivity extends AppCompatActivity { 2 3private int[] icons = {R.drawable.a, R.drawable.b, R 4.drawable.c, R.drawable.d, R.drawable.e, 5R.drawable.f, R.drawable.g, R.drawable.h, R 6.drawable.i, R 7.drawable.j, R.drawable.k, R.drawable.l}; 8private String[] titles = {"白鹃梅", "五色椒", "小檗", 9"李叶绣菊", "多花兰", "獐耳细辛", "铁茉莉", "三色狸藻", "东洋菊", 10"大花皇冠", "苹果花", "三弄芙蓉"}; 11private RecyclerView recyclerView; 12@Override 13protected void onCreate(Bundle savedInstanceState) { 14super.onCreate(savedInstanceState); 15setContentView(R.layout.activity_main); 16recyclerView = (RecyclerView) findViewById(R.id 17.recyclerView); 18LinearLayoutManager linearLayoutManager = new 19LinearLayoutManager(MainActivity.this); 20recyclerView.setLayoutManager(linearLayoutManager); 21MyAdapter myAdapter = new MyAdapter(); 22recyclerView.setAdapter(myAdapter); 23} 24public class MyAdapter extends RecyclerView 25.Adapter<MyAdapter.MyViewHolder> { 26@Override 27public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { 28MyViewHolder myViewHolder = new MyViewHolder (LayoutInflater.from(MainActivity.this) 29.inflate(R.layout.recycler_item, viewGroup, false)); 30return myViewHolder; 31} 32@Override 33public void onBindViewHolder(MyViewHolder myViewHolder, int position) { 34myViewHolder.image.setImageResource 35(icons[position]); 36myViewHolder.titleView.setText 37(titles[position]); 38} 39@Override 40public int getItemCount() { 41return titles.length; 42} 43public class MyViewHolder extends RecyclerView 44.ViewHolder { 45ImageView image; 46TextView titleView; 47public MyViewHolder(View itemView) { super(itemView); 48image = (ImageView) itemView.findViewById (R.id.image); 49titleView = (TextView) itemView.findViewById(R.id.tv_title); 50} 51} 52} 53} 上述代码中,在第3~10行定义两个数组对象,用于存储植物标题和植物图片的信息。第16~22行首先获取recyclerView对象并为recyclerView创建布局管理器,其中linearLayoutManager对象用于指明recyclerView为线性布局方式,默认为垂直方向的线性布局。通过setLayoutManager()将linearLayoutManager对象设置给recyclerView对象。之后创建MyAdapter对象,最后通过setAdapter()方法将适配器MyAdapter对象设置到recyclerView控件上,这样recyclerView就完成了数据的适配。 第24~42行为recyclerView创建适配器对象,新建一个内部类MyAdapter继承自RecyclerView.Adapter并将ViewHolder泛型参数指定为MyAdapter.MyViewHolder,其中MyViewHolder 是MyAdapter中定义的一个内部类。此外,RecyclerView.Adapter类是一个抽象类,还需要实现里面的三个抽象方法: onCreateViewHolder()、onBindViewHolder()、getItemCount()。 图514程序运行效果 onCreateViewHolder()中创建 MyViewHolder对象,并将recycler_item布局文件转换成对象之后传入到MyViewHolder构造函数中,最后将myViewHolder对象返回。on BindViewHolder()用于对recyclerView每个Item项的数据进行赋值,会在每个子项滚动到屏幕内的时候执行,并通过position获取Item的索引。getItemCount()是获取列表总条目数。 第43~51行定义MyViewHolder 类继承自RecyclerView.ViewHolder。在该类中获取Item界面的控件,在MyViewHolder的构造函数中需要传入一个参数itemView,这个参数通常是recyclerView中Item项的最外层布局,对于itemView对象通过findViewById()获取到Item布局中的每一个控件对象。 上述程序运行效果如图514所示。 视频详解 5.4.2RecyclerView实现横向和 网格布局 在使用RecyclerView控件时需要注意在运行之前一定要为RecyclerView指定列表项的布局管理器,否则系统不知道该如何显示,就会报错并强制退出。另外,RecyclerView不仅可以实现纵向布局,还可以实现横向布局。实现横向布局的主要代码如下。 LinearLayoutManager linearLayoutManager = newLinearLayoutManager(MainActivity.this, LinearLayoutManager.HORIZONTAL,false); recyclerView.setLayoutManager(linearLayoutManager); 上述代码中在创建线性布局管理器时传递了三个参数,第一个参数为上下文对象,通常为当前的Activity; 第二个参数用于指定方向,默认为垂直的,在此指定为水平的; 第三个参数表示列表项的顺序是否反转,对于水平方向来说,列表项默认是从左到右摆放,如果需要从右到左摆放,则表示需要反转,传递true进去即可。 除了LinearLayoutManager之外,RecyclerView 还提供了GridLayoutManager 和 StaggeredGridLayoutManager这两种内置的布局排列方式。GridLayoutManager可以用于实现网格布局,StaggeredGridLayoutManager可以用于实现瀑布流布局。如果要实现网格布局,代码如下。 GridLayoutManager gridLayoutManager = newGridLayoutManager (this, 4, GridLayoutManager .VERTICAL, false); recyclerView.setLayoutManager(gridLayoutManager); 创建网格布局管理器时需传递四个参数,第一个参数为上下文对象; 第二个参数为每行或者每列排列Item个数; 第三个参数表示网格的方向,是水平摆放还是垂直摆放,GridLayoutManager.VERTICAL表示纵向排列; 第四个参数表示列表项的顺序是否反转,false表示不反转,按照原有次序加载。上述代码运行结果如图515所示。 图515程序运行效果 视频详解 5.4.3RecyclerView实现单击事件 在ListView和GridView中对于每个Item都有单击事件,对于RecyclerView同样也有单击事件,不过不同于ListView,RecyclerView并没有提供类似于setOnItemClickListener()这样的注册监听器方法,需要我们给Item中具体的View去注册单击事件,看似编写代码变复杂,其实是增加了控件的灵活性,这也是比ListView更强大的地方之一。对于RecyclerView控件的单击事件,需要修改自定义类MyAdapter中的代码,单击事件是定义在onCreateViewHolder()方法中,具体代码如下。 程序清单526: chart0507\java\com\example\administrator\chart0507\MainActivity.java 1public class MyAdapter extends RecyclerView .Adapter<MyAdapter.MyViewHolder> { 2@Override 3public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { 4final MyViewHolder myViewHolder = new 5MyViewHolder (LayoutInflater.from(MainActivity 6.this).inflate(R.layout.recycler_item, viewGroup, false)); 7myViewHolder.view.setOnClickListener(new View 8.OnClickListener() { 9@Override 10public void onClick(View v) { 11int adapterPosition = myViewHolder .getAdapterPosition(); 12Toast.makeText(MainActivity.this, "你单击的植物是:" + titles[adapterPosition], 13Toast.LENGTH_SHORT).show(); 14} 15}); 16myViewHolder.image.setOnClickListener(new View.OnClickListener() { 17@Override 18public void onClick(View v) { 19int adapterPosition = myViewHolder 20.getAdapterPosition(); 21Toast.makeText(MainActivity.this, 22"你单击图片的植物名是:" + titles[adapterPosition], 23Toast.LENGTH_SHORT).show(); 24} 25}); 26return myViewHolder; 27} 28@Override 29public void onBindViewHolder(MyViewHolder myViewHolder, int position) { 30myViewHolder.image.setImageResource (icons[position]); 31myViewHolder.titleView.setText 32(titles[position]); 33} 34@Override 35public int getItemCount() { 36return titles.length; 37} 38public class MyViewHolder extends RecyclerView.ViewHolder { 39ImageView image; 40TextView titleView; 41View view; 42public MyViewHolder(View itemView) { 43super(itemView); 44view = itemView; 45image = (ImageView) itemView.findViewById 46(R.id.image); 47titleView = (TextView) itemView 48.findViewById(R.id.tv_title); 49} 50} 51} 上述代码中,第6行声明view变量保存Item最外层布局对象。第7~15行代码为最外层布局对象view添加单击事件,第11行获取用户单击的position,第12~13行代码通过Toast弹出用户单击植物的名称。第16~27行为ImageView控件绑定单击事件,当用户单击Item项中的图片,通过Toast弹出用户单击图片所对应的植物的名称。 上述程序运行效果如图516所示。 图516程序运行效果 视频详解 5.4.4实战演练——产品列表 通过对RecyclerView的学习,我们可以对“欢乐购商城”中商品列表页面进行实现。其布局页面的代码如下。 程序清单527: chart0508\app\src\main\res\layout\activity_main.xml 1<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2xmlns:tools="http://schemas.android.com/tools" 3android:layout_width="match_parent" 4android:layout_height="match_parent" 5android:orientation="vertical" 6tools:context=".MainActivity"> 7 8<android.support.v7.widget.RecyclerView 9android:id="@+id/recycle_Viewes" 10android:layout_width="match_parent" 11android:layout_height="match_parent"> 12</android.support.v7.widget.RecyclerView> 13</LinearLayout> 其中Item的布局文件如下。 程序清单528: chart0508\app\src\main\res\layout\goods_list_items.xml 1<?xml version="1.0" encoding="utf-8"?> 2<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3android:layout_width="match_parent" 4android:layout_height="match_parent"> 5<ImageView 6android:id="@+id/iv_iamge" 7android:layout_width="120dp" 8android:layout_height="120dp" 9android:layout_marginTop="4dp" 10android:src="@drawable/a000" /> 11<TextView 12android:id="@+id/tv_title" 13android:layout_width="match_parent" 14android:layout_height="wrap_content" 15android:layout_alignTop="@+id/iv_iamge" 16android:layout_marginLeft="5dp" 17android:layout_toRightOf="@id/iv_iamge" 18android:ellipsize="end" 19android:maxLines="2" 20android:text="测试测试测试测试测试测试" 21android:textSize="13dp" /> 22<TextView 23android:id="@+id/tv_count" 24android:layout_width="wrap_content" 25android:layout_height="wrap_content" 26android:layout_below="@id/tv_title" 27android:layout_margin="10dp" 28android:layout_toRightOf="@+id/iv_iamge" 29android:text="1100" /> 32<TextView 33android:layout_width="wrap_content" 34android:layout_height="wrap_content" 35android:layout_alignBottom="@+id/tv_count" 36android:layout_marginRight="3dp" 37android:layout_toLeftOf="@+id/tv_address" 38android:text="产地:" /> 39<TextView 40android:id="@+id/tv_address" 41android:layout_width="wrap_content" 42android:layout_height="wrap_content" 43android:layout_alignBottom="@+id/tv_count" 44android:layout_alignParentRight="true" 45android:text="河南 商丘" /> 46<TextView 47android:id="@+id/price_title" 48android:layout_width="wrap_content" 49android:layout_height="wrap_content" 50android:layout_alignLeft="@+id/tv_count" 51android:layout_below="@+id/tv_count" 52android:layout_marginTop="8dp" 53android:text="价格:¥" /> 54<TextView 55android:id="@+id/tv_new_price" 56android:layout_width="wrap_content" 57android:layout_height="wrap_content" 58android:layout_alignBottom="@+id/price_title" 59android:layout_toRightOf="@+id/price_title" 60android:text="12.00" 61android:textSize="16sp" 62android:textStyle="bold" /> 63<LinearLayout 64android:layout_width="match_parent" 65android:layout_height="25dp" 66android:layout_alignBottom="@+id/iv_iamge" 67android:layout_alignLeft="@+id/price_title" 68android:background="@drawable/textview_border_style" 69android:gravity="center" 70android:orientation="horizontal"> 71<TextView 72android:layout_width="wrap_content" 73android:layout_height="wrap_content" 74android:text="店铺:" /> 75<TextView 76android:id="@+id/tv_shopname" 77android:layout_width="wrap_content" 78android:layout_height="wrap_content" 79android:text="小飞的店铺" 80android:textColor="@color/colorPrimary" /> 81</LinearLayout> 82<View 83android:id="@+id/view" 84android:layout_width="match_parent" 85android:layout_height="1dp" 86android:layout_below="@id/iv_iamge" 87android:layout_marginTop="3dp" 88android:background="@color/grey" /> 89</RelativeLayout> 定义一个适配器类,代码如下。 程序清单529: chart0508\app\src\main\java\com\example\administrator\goodsListAdapter 1public class goodsListAdapter extends RecyclerView.Adapter<goodsListAdapter.ViewHolder> { 2private List<Good> mList; 3private Activity mActivity; 4private Good good; 5public goodsListAdapter(Activity activity, List<Good> list) { 6mList = list; 7mActivity = activity; 8} 9@Override 10public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 11final ViewHolder viewHolder = new ViewHolder(View.inflate(mActivity, R.layout.goods_list_items, null)); 12viewHolder.functionView.setOnClickListener(new View.OnClickListener() { 13@Override 14public void onClick(View v) { 15int adapterPosition = viewHolder.getAdapterPosition(); 16Intent intent = new Intent(mActivity, GoodDetailActivity.class); 17intent.putExtra("goodInfo", mList.get(adapterPosition)); 18mActivity.startActivity(intent); 19} 20}); 21return viewHolder; 22} 23@Override 24public void onBindViewHolder(ViewHolder holder, int position) { 25good = mList.get(position); 26holder.imageView.setImageResource(good.getImage()); 27holder.title.setText(good.getName()); 28holder.price.setText(""+good.getPrice()); 29holder.count.setText(good.getVolume()+"人付款"); 32holder.address.setText(good.getProductLocation()); 33holder.shopName.setText(good.getShopName()); 34} 35@Override 36public int getItemCount() { 37return mList.size(); 38} 39class ViewHolder extends RecyclerView.ViewHolder { 40TextView shopName; 41TextView title; 42TextView count; 43TextView address; 44TextView price; 45ImageView imageView; 46View functionView; 47public ViewHolder(View itemView) { 48super(itemView); 49functionView = itemView; 50imageView = itemView.findViewById(R.id.iv_iamge); 51title = (TextView) itemView.findViewById(R.id.tv_title); 52count = (TextView) itemView.findViewById(R.id.tv_count); 53address = (TextView) itemView.findViewById(R.id.tv_address); 54price = (TextView) itemView.findViewById(R.id.tv_new_price); 55shopName = (TextView) itemView.findViewById(R.id.tv_shopname); 56} 57 58} 59} MainActivity页面的代码如下。 程序清单530: chart0508\app\src\main\java\com\example\administrator\MainActivity.java 1public class MainActivity extends AppCompatActivity { 2 3private RecyclerView recyclerView; 4private LinearLayoutManager linearLayoutManager; 5private List<Good> goodList = new ArrayList<Good>(); 6private int[] pictrue = {R.drawable.a000, R.drawable.a001, R.drawable.a002, R. 7drawable.a003,R. drawable.a004, R.drawable.a005, R.drawable.a006, R.drawable.a007, S. R.drawable.a008, R.drawable.a009}; 8private String[] title = {"2019秋装新款白色纯棉长袖t恤女装宽松春秋打底衫体恤大码上衣", "2019初秋新款潮韩版洋气女装宽松秋季短款衬衫女秋装方领长袖上衣", 9"纯棉长袖T恤女上衣2019新款竹节棉宽松秋衣女装薄款初秋打底衫潮", "半高领打底衫女装秋冬薄款洋气莫代尔堆堆领长袖t恤内搭针织上衣", 10"女装2019新款潮超火cec短袖女春夏季港味宽松百搭上衣T恤ins洋气", "夏大大胖mm韩版宽松秋装新款女装大码2019牛仔套装外套减龄连衣裙", 11"大姗姗家瘦瘦裤胖mm韩版大码女装秋季百搭外穿打底裤网红款小脚裤", "中国大陆','夏装紧身纯白色T恤女短袖修身女装女士纯棉2019新款上衣潮打底衫", 12"纯棉长袖T恤女上衣2019新款竹节棉宽松秋衣女装薄款初秋打底衫潮", "纯棉红色v领T恤女短袖修身夏女装2019新款潮体恤紧身上衣黑色短款",}; 13private String[] location = {"云南 昆明", "广东 广州", "江西 南昌", "广东 汕头", "浙江 金华", "广东 广州", "江苏 淮安", 14"江苏 苏州", "广东 深圳", "安徽 芜湖"}; 15private String[] shop = {"lovtis兰蒂斯旗舰店", "lovtis兰蒂斯旗舰店", "lovtis兰蒂斯旗舰店", "lovtis兰蒂斯旗舰店", 16"旗舰店", "旗舰店", "旗舰店", "旗舰店", "旗舰店", "旗舰店"}; 17private String[] number = {"87816", "37384", "62165", "43221", "4561", "43211", 18 "3011","764", "7656", "4567"}; 19private String[] prices = {"39.9", "29.87", "29", "56", "20", "138", "97", "15", "78", "30"}; 20@Override 21protected void onCreate(Bundle savedInstanceState) { 22super.onCreate(savedInstanceState); 23setContentView(R.layout.activity_main); 24initView(); 25} 26private void initView() { 27 28for (int i = 0; i < 10; i++) { 29Good good = new Good(); 32good.setImage(pictrue[i]); 33good.setName(title[i]); 34good.setProductLocation(location[i]); 35good.setShopName(shop[i]); 36good.setPrice(Double.valueOf(prices[i])); 37good.setVolume(number[i]); 38goodList.add(good); 39} 40recyclerView = (RecyclerView) findViewById(R.id.recycle_Viewes); 41linearLayoutManager = new LinearLayoutManager(this); 42recyclerView.setLayoutManager(linearLayoutManager); 43recyclerView.setItemAnimator(new DefaultItemAnimator()); 44recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration 45.VERTICAL)); 46recyclerView.setAdapter(new goodsListAdapter(this, goodList)); 47} 48} 上述程序运行效果如图517所示。 图517程序运行效果 本章小结 本章围绕“欢乐购商城”项目首页、产品列表页面、订单列表页面和修改收货地址页面引入Android中列表控件的讲解。详细介绍了常用的列表控件的使用场景和基本属性,并通过实现首页、产品列表页面、订单列表页面和修改收货地址页面演示了这几种列表控件的具体使用用法。其中,Spinner控件主要适用于具有下拉选项的应用场景,ListView控件常用于数据量较小和简单布局的应用,GridView主要应用于多行多列的展示,而对于要实现页面布局较复杂或者加载数据量较大的应用场景推荐使用RecyclerView控件,这些列表控件在以后的开发中经常使用,需要读者熟练掌握灵活运用。 自测习题 1. BaseAdapter为什么定义为抽象类?要想实现自定义的Adapter,必须实现哪些方法? 2. 简述SimpleAdapter对象创建时,各个参数的含义。 3. 根据所学的ListView控件,实现“欢乐购商城”的地址列表页面,如图518所示。 4. 根据所学的RecyclerView控件,实现“欢乐购商城”首页中的小图标加载,如图519所示。 图518地址列表页面 图519首页小图标加载