Android模块化开发项目式教程(Android Studio)
上QQ阅读APP看书,第一时间看更新

项目2-4 随手记列表界面设计

学习目标

■ 掌握ListView组件的使用方法,了解ListView组件事件监听处理机制。

■ 掌握SimpleAdapter简单适配器的使用方法。

■ 掌握BaseAdapter自定义适配器的使用方法,了解ListView组件的优化方法。

■ 学会运用ListView组件进行随手记列表界面设计。

项目描述

运用 ListView 组件与 BaseAdapter 自定义适配器的方法,设计一个随手记列表界面,要求在随手记列表界面上能显示图片和文字。

知识储备

2.4.1 ListView组件

在Android中,ListView被称为列表视图,是Android中最常用的一种视图组件。它是以垂直列表的形式显示所有列表项的数据。例如,显示电话簿中联系人的信息、系统功能设置或状态等。

1.ListView组件在XML文件中的基本语法

格式代码如下:

<ListView

  属性列表

/>

2.ListView组件常用的XML属性

ListView组件常用的XML属性如表2-14所示。

表2-14 ListView组件常用的XML属性

续表

3.ListView组件显示列表项的方法

ListView 组件显示列表项的数据方式是采用 MVC 模式将前端显示和后端数据进行分离,即ListView组件在装载数据时并不是直接使用ListView.add或者类似的方法添加数据,而是需要指定一个Adapter作为适配器来显示列表中元素的布局方式。

Adapter常用实现类分为以下几种。

ArrayAdapter:通常用于将数组或List集合的多个值包装成多个列表项。

SimpleAdapter:用于将List集合的多个对象包装成多个列表项。

SimpleCursorAdapter:只是用于包装Cursor提供的数据。

BaseAdapter:自定义的适配器。

通过Adapter为ListView指定要显示的列表项,可分为如下步骤。

① 创建 Adapter 对象。对于纯文字的列表项,通常使用 ArrayAdapter 对象。而创建ArrayAdapter对象通常可以有两种情况,一种是通过数组资源文件创建,另一种是通过在Java文件中使用的字符串数组创建。

在创建ArrayAdapter时,必须指定Android系统自带的用于显示选项的TextView组件ID,该参数决定每个列表项的外观形式。这与在项目2-3中的 Spinner 列表选项框中介绍的创建ArrayAdapter 对象基本相同。其不同之处是在创建该对象时,要指定列表项的外观形式。为ListView指定的外观形式通常有以下几个属性值。

simple_list_item_1:每一个列表项都是普通的TextView。

simple_list_item_2:每一个列表项都是普通的TextView(字体略大)。

simple_list_item_checked:每一个列表项都是一个已勾选的列表项。

simple_list_item_multiple_choice:每一个列表项都是带多选框的文本。

simple_list_item_single_choice:每一个列表项都是带单选按钮的文本。

② 将创建的适配器对象与ListView相关联,具体代码如下:

ListView.setAdapter(adapter);

下面分别介绍在Android中创建ListView的方法。

第一种:通过数组资源文件创建ListView。

【例2-6】在 Android Studio中创建新项目,名称为 ListViewDemo,通过布局文件添加一个 ListView 组件,并通过数组资源为其设置列表项,列表项中的数据为电话簿中联系人姓名。

首先修改新建项目List View Demo中res/layout目录下的布局文件activity_main_xml,将默认添加的Text View组件删除,并添加一个List View组件。添加List View组件布局代码如下:

<ListView

   android:id="@+id/listView_name"

   android:layout_width="wrap_content"

   android:layout_height="wrap_content"

   android:entries="@array/name"

   android:divider="@color/colorAccent"

   android:dividerHeight="3dp"

/>

然后,在res/values目录中创建一个定义数组资源的XML文件arrays.xml,并在该文件中添加名称为name的字符串数组。关键代码如下:

<?xml version="1.0" encoding="utf-8"?>

<resources>

     <string-array name="name">

        <item>张艳</item>

        <item>李力</item>

        <item>王丹</item>

     </string-array>

</resources>

最后运行本实例,观察运行结果。

注意

数组资源文件中的列表项也可放置在strings.xml文件里。

第二种:通过ArrayAdapter创建ListView。

【例2-7】修改项目ListViewDemo下的布局文件activity_main_xml中的ListView组件属性,并通过 ArrayAdapter 适配器使用数组提供列表项,列表项中的数据为电话簿中联系人姓名和电话,列表项可设置为单选模式。

首先,修改 ListViewDemo 项目的 res/layout 目录下的布局文件 activity_main_xml 中的ListView组件属性。ListView组件的布局代码如下:

<ListView

     android:id="@+id/listView_tel"

     android:layout_width="wrap_content"

     android:layout_height="wrap_content"

     android:divider="@color/colorAccent"

     android:dividerHeight="3dp" />

然后,在MainActivity.java中的onCreate()方法中为ListView创建并关联适配器。先定义存储姓名和电话的字符串数组,然后获取布局文件中ListView组件,创建适配器,并将字符串数组装载到适配器,最后将适配器与ListView组件相关联。关键代码如下:

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    //定义一个数组,并将字符串数组初始化

    String [] arr = {"张艳 133123456","李力 185234567","王丹 130122334"};

    //获取布局文件中的ListView

    ListView listview = (ListView)findViewById(R.id.listView_tel);

    //定义数据源适配器

    ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(this,andro

    id.R.layout.simple_list_item_single_choice,arr);

    //为ListView设置Adapter

    listview.setAdapter(arrayAdapter);

}

最后运行本实例,观察运行结果。

第三种:通过SimpleAdapter创建ListView。

SimpleAdapter适配器的主要功能是将List集合的数据转换为ListView可以支持的数据。而要实现这种转换,首先需要定义一个数据显示的布局文件,该布局文件用于定义 ListView每一行所需要显示的所有组件。在需要转换的List集合中保存的是多条Map集合数据,这些Map集合保存的是具体要显示的信息,即该布局文件中的每个组件的ID就是保存在Map集合的key,而每个组件的显示内容,则是由Map保存的value决定的。SimpleAdapter构造方法如下:

SimpleAdapter(Context context,List<Map<String ,?>>,int resource,String[] from,int[] to)

下面通过【例2-8】说明使用SimpleAdapter创建ListView的方法。

【例2-8】在Android Studio中创建新项目,名称为SimpleAdapterDemo。然后,修改项目下的布局文件activity_main_xml,添加第1个ListView组件。通过定义SimpleAdapter适配器,可提供数据源的列表项,列表项数据为电话簿中的头像、联系人姓名、城市和电话。

首先修改SimpleAdapterDemo项目res/layout目录下的布局文件activity_main_xml,将默认添加的TextView组件文本属性修改为“电话簿”,并添加第1个ListView组件。布局代码如下:

<TextView

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:text="电话簿"

    android:textSize="30dp"

    android:layout_gravity="center"/>

<ListView

    android:id="@+id/personListView"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:divider="@color/colorAccent"

    android:dividerHeight="3dp" />

创建ListView中列表选项布局文件list_person_layout.xml的方法:右击【res】→【layout】目录,选择快捷菜单【New】→【XML】→【Layout XML File】,输入list_person_layout,单击【Finish】按钮。

其次,在列表选项布局文件list_person_layout.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">

  <ImageView

    android:paddingRight="20dp"

    android:layout_width="50dp"

    android:layout_height="30dp"

    android:src="@drawable/contact_pic"

    android:id="@+id/img"/>

  <TextView

    android:id="@+id/txtName"

    android:layout_width="0dp"

    android:layout_weight="1"

    android:layout_height="wrap_content" />

  <TextView

    android:id="@+id/txtCity"

    android:layout_width="0dp"

    android:layout_weight="1"

    android:layout_height="wrap_content" />

  <TextView

    android:id="@+id/txtPhone"

    android:layout_width="0dp"

    android:layout_weight="1"

    android:layout_height="wrap_content" />

</LinearLayout>

然后在MainActivity.java程序中创建适配器需要的数据集合对象getData()方法,再在onCreate()方法中获取布局文件中的ListView组件,定义数据源适配器,将适配器与ListView组件相关联。其关键代码如下:

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    //获取布局文件中的ListView

    listview = (ListView)findViewById(R.id.personListView);

    //定义数据源适配器

    SimpleAdapter adapter = new SimpleAdapter(this, getData(),R.layout.list_ -

    person_layout, new String[] { "img", "name", "City", "Phone" }, new int[]-

    { R.id.img,R.id.txtName, R.id.txtCity, R.id.txtPhone });

    //适配器与ListView相关联

    listview.setAdapter(adapter);

}

  //创建适配器需要的数据集合对象

  private List<Map<String, Object>> getData() {

    List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

    Map<String, Object> map = new HashMap<String, Object>();

    map.put("img", R.drawable.contact_pic);

    map.put("name", " 张艳 ");

    map.put("City", " 长沙 ");

    map.put("Phone", " 133123456 ");

    list.add(map);

    map = new HashMap<String, Object>();

    map.put("img", R.drawable.contact_pic);

    map.put("name", " 李力 ");

    map.put("City", " 株洲 ");

    map.put("Phone", " 185123456");

    list.add(map);

    map = new HashMap<String, Object>();

    map.put("img", R.drawable.contact_pic);

    map.put("name", " 王丹 ");

    map.put("City", " 武汉 ");

    map.put("Phone", " 130123456 ");

    list.add(map);

    map = new HashMap<String, Object>();

    map.put("img", R.drawable.contact_pic);

    map.put("name", " 刘明艳 ");

    map.put("City", " 上海 ");

    map.put("Phone", " 1389990456 ");

    list.add(map);

    return list;

}

最后运行本实例,观察运行结果。

第四种:通过ListView+BaseAdapter实现复杂列表选项。

此方法将在随手记列表界面设计项目中介绍。

4.ListView组件监听器

ListView组件最重要的监听器是实施对用户选择了某个选项的监听。设定该监听器的方法为public void onItemClick(AdapterView<?> parent, View view, int position, long id)。它的功能是监听ListView选项被选中的事件。例如,当用户单击ListView的列表项时,为获得该选项的值,需要为ListView添加OnItemClickListener监听器。其代码如下:

//为ListView添加OnItemClickListener监听器

listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {

    @Override

    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

      //获取选项的值

      String result = parent.getItemAtPosition(position).toString();

      //显示提示的消息

      Toast.makeText(MainActivity.this,result,Toast.LENGTH_SHORT).show();

   }

});

2.4.2 BaseAdapter自定义适配器

在【例2-7】和【例2-8】中已经分别讲解了ArrayAdapter数组适配器和SimpleAdapter简单适配器的使用方法,现在将要介绍BaseAdapter自定义适配器的使用。BaseAdapter自定义适配器与其他Adapter有些不同,其他的Adapter可以直接在其构造方法中进行数据的设置,如下:

SimpleAdapter adapter = new SimpleAdapter(this, getData(), R.layout.list_item, new String[]{"img","title","info",new int[]{R.id.img, R.id.title, R.id.info}});

但在BaseAdapter自定义适配器中就需要创建一个继承自BaseAdapter的类,并要重载getCount、getItem、getItemId和getView方法,其中getView是用来刷新它所在的ListView。例如,创建MyAdapter类,让它继承自BaseAdapter的类,关键代码如下:

Public class MyAdapter extends BaseAdapter {

    private Context context;

    public MyAdapter(Context context) {

       this.context = context;

    }

    @Override

    public int getCount() {

       //此适配器中所代表的数据集中的条目数

       return 0;

    }

    @Override

    public Object getItem(int position) {

       //获取数据集中与指定索引对应的数据项

        return null;

   }

   @Override

   public long getItemId(int position) {

       //获取在列表中与指定索引对应的行ID

       return 0;

   }

   @Override

   public View getView(int position, View convertView, ViewGroup parent) {

       //获取一个在数据集中指定索引的视图来显示数据

       return null;

   }

}

其中,在对getView方法重写时,当列表项数据量很大时,如果每次都重新创建View,设置资源,会严重影响手机性能,因此,在重载getView方法时,要进行ListView优化。

ListView优化的第1种方法是利用缓存convertView的方式,判断缓存中是否存在View,如果不存在则要创建View,如果已存在就不要再创建View。利用重用缓存convertView传递给getView()方法来避免填充不必要的视图,如下:

If (convertView == null){

    View view = LayoutInfalter.from(getContext()).inflate(resourceID,null);

}else{

    View view = convertView;

}

ListView 优化的第2种方法是通过 convertView+ViewHolder 来实现,其中 ViewHolder是一个静态类。当判断convertView为空时,就会根据已设计好的ListView的Item布局XML来为convertView赋值,并生成一个ViewHolder来绑定convertView里面的各个View组件(也就是XML布局里面的组件),再用convertView的setTag将ViewHolder设置到Tag中,以便系统第2次绘制ListView时从Tag中取出。当convertView不为空时,就直接用convertView的getTag()来获得一个ViewHolder。ViewHolder模式通过getView()方法在返回的视图的标签(Tag)中存储一个数据结构,这个数据结构包含了指向要绑定数据的视图的引用,从而避免每次调用getView()的时候调用findViewById()。代码如下:

//先定义ViewHolder静态类

static class ViewHolder {

    public ImageView img;

    public TextView noteTime;

    public TextView noteContent;

}

 //重写getView

public View getView(int position, View convertView, ViewGroup parent) {

     ViewHolder holder;

    If (convertView == null) {

       holder = new ViewHolder();

       LayoutInflater inflater = LayoutInflater.from(context);

       convertView = inflater.inflate(R.layout.list_note_layout, null);

       holder.img = (ImageView) findViewById(R.id.img)

       holder.noteTime = (TextView) findViewById(R.id.noteTime);

       holder.noteContent = (TextView) findViewById(R.id.noteContent);

       convertView.setTag(holder);

    }else {

       holder = (ViewHolder)convertView.getTag();

       holder.img.setImageResource(R.drawable.ic_launcher);

       holder.noteTime.setText("5月1日");

       holder.noteContent.setText("劳动节");

    }

    return convertView;

}

项目实施

1.项目分析

运用 ListView 组件与 BaseAdapter 自定义适配器的方法,设计一个随手记列表界面,要求在随手记列表界面上能显示图片和文字,效果如图2-15所示。

图2-15 随手记列表界面

随手记列表界面设计特点:

① 整个界面由标题和垂直列表的数据项组成。

② 每个数据项由一个图片、两个文本框组成。

分析结果:

① 整个界面可采用相对布局或线性布局技术。

② 用一个文本框组件表示标题。

③ 用一个 ListView 组件实现垂直列表显示功能。

④ ListView 组件中 item 项的布局采用线性布局技术,并由图片和文本框组成。

2.项目实现

(1)新建随手记项目。

启动 Android Studio,在 Android Studio 起始页选择【Start a new Android Studio project】,或在Android Studio主页菜单栏上选择【File】→【New】→【New Project】,新建 Android 工程。在 New Project 页面上输入应用程序的名称(NoteTest)、公司域名(com.zzhn.zheng)和存储路径,单击【Next】按钮。然后,选择工程的类型以及支持的最低版本,单击【Next】按钮。之后选择是否创建Activity以及创建Activity的类型,选择【Empty Activity】,单击【Finish】按钮。

(2)设计布局文件。

首先在【res】→【layout】文件夹中,打开布局的【activity_main.xml】文件,使用相对布局技术,添加ListView组件,设置ListView、TextView组件属性。

操作方法:展开【res】→【layout】文件夹,双击【activity_main.xml】文件,打开右侧布局文件text编辑窗口,在编辑窗口中输入activity_main.xml文件代码,如下:

<TextView

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:text="随手记列表"

    android:textSize="30sp"

    android:id="@+id/tv1"

    android:gravity="center"/>

<ListView

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:layout_below="@+id/tv1"

    android:id="@+id/listview"

    android:divider="@color/colorAccent"

    android:listSelector="#ece10d"

    android:fadeScrollbars="true"

    android:dividerHeight="3dp" />

创建ListView中item列表选项布局文件list_note_layout.xml。

操作方法:右击【res】→【layout】目录,选择快捷菜单【New】→【XML】→【Layout XML File】,输入list_note_layout,单击【Finish】按钮。在列表选项布局文件list_note_layout.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"

   android:orientation="horizontal">

   <ImageView

     android:paddingRight="20dp"

     android:layout_width="50dp"

     android:layout_height="50dp"

     android:src="@drawable/note_pic"

     android:id="@+id/img"/>

   <TextView

     android:layout_width="0dp"

     android:layout_weight="0.5"

     android:layout_height="wrap_content"

     android:id="@+id/noteTime"/>

   <TextView

     android:layout_width="0dp"

     android:layout_weight="1"

     android:layout_height="wrap_content"

     android:id="@+id/noteContent"/>

</LinearLayout>

(3)编写Java程序代码。

首先创建MyAdapter类,让它继承自BaseAdapter的类,并通过convertView+ViewHolder实现对ListView的优化。程序关键代码如下:

public class MyAdapter extends BaseAdapter {

   private Context context;

   private List<Map<String, Object>> list ;

   public MyAdapter(Context context, List<Map<String, Object>> list) {

      this.context = context;

      this.list = list;

   }

   @Override

   public int getCount() {

      //在此适配器中所代表的数据集中的条目数

      return list.size();

   }

   @Override

   public Object getItem(int position) {

      //获取数据集中与指定索引对应的数据项

      return list.get(position);

   }

   @Override

   public long getItemId(int position) {

      //获取在列表中与指定索引对应的行id

      return position;

   }

   //ViewHolder静态类

   static class ViewHolder

   {

      public ImageView img;

      public TextView noteTime;

      public TextView noteContent;

   }

   //获取一个在数据集中指定索引的视图来显示数据

   @Override

   public View getView(int position, View convertView, ViewGroup parent) {

      ViewHolder holder = null;

      //如果缓存convertView为空,则需要创建View

      if(convertView == null){

        holder = new ViewHolder();

        //根据自定义的Item布局加载布局

        LayoutInflater inflater = LayoutInflater.from(context);

        convertView = inflater.inflate(R.layout.list_note_layout, null);

        holder.img = (ImageView)convertView.findViewById(R.id.img);

        holder.noteTime = (TextView)convertView.findViewById(R.id.noteTime);

        holder.noteContent = (TextView)convertView.findViewById(R.id.noteContent);

        //将设置好的布局保存到缓存中,并将其设置在Tag里,以便后面方便取出Tag

        convertView.setTag(holder);

     }else {

        holder = (ViewHolder)convertView.getTag();

     }

     holder.img.setBackgroundResource((Integer)list.get(position).get("img"));

     holder.noteTime.setText((String)list.get(position).get("noteTime"));

     holder.noteContent.setText((String)list.get(position).get("noteContent"));

     return convertView;

   }

}

然后,在 MainActivity.java 中编写创建适配器需要的数据集合对象 getData()方法,在onCreate()方法中将获取的数据设置到 list 中。创建自定义适配器,将自定义适配器绑定到数据源,并与ListView组件相关联。创建适配器需要的数据集合对象的代码如下:

private List<Map<String, Object>> getData()

{

    List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

    Map<String, Object> map;

    for(int i=0;i<10;i++)

    {

       map = new HashMap<String, Object>();

       map.put("img", R.drawable.note_pic);

       map.put("noteTime", "4月25日");

       map.put("noteContent", "班级活动内容……");

       list.add(map);

    }

    return list;

}

最后,设置数据源、ListView与自定义适配器相关联的代码如下:

listView = (ListView)findViewById(R.id.listview);

//获取将要绑定的数据并设置到list中

list = getData();

MyAdapter adapter = new MyAdapter(this,list);

listView.setAdapter(adapter);

(4)调试运行。

① 单击工具栏上的 AVD Manager 图标,打开虚拟设备对话框,在虚拟设备对话框中单击启动虚拟设备的命令按钮,打开Android Studio模拟器。

② 单击工具栏上的“三角形”运行按钮,运行本项目。

项目总结

通过本项目的学习,读者应学会运用 ListView+BaseAdapter 实现复杂列表选项的设计方法。

① 创建一个添加了ListView组件的布局文件,并设置相应属性。

② 创建ListView组件中item列表选项布局文件。

③ 创建一个类,让它继承自 BaseAdapter 的类,并通过 convertView+ViewHolder 实现对ListView优化。

④ 创建数据源,并实现数据源、BaseAdapter适配器和ListView组件三者之间的相互关联。

项目训练——用BaseAdapter创建ListView实现联系人列表界面

要求将【例2-8】中用 SimpleAdapter 创建 ListView 实现联系人列表的界面修改成用BaseAdapter创建ListView实现联系人列表的界面,列表选项数据为电话簿中的头像、联系人姓名、城市和电话。

练习题

2-4-1 如何实现在ListView组件上为每个列表项设置带单选按钮或多选按钮的样式?

2-4-2 说明ListView优化的方法和作用。

2-4-3 说明SimpleAdapter适配器的设计方法和作用。

2-4-4 如何监控ListView组件中item选项的变化?