在Android应用开发中,ListView可以说是最为常用的一种视图控件,它以垂直列表的方式列出需要列出的列表项,比如说是通讯录里面的联系人、系统设置里面的各个被设置项等。
而且,ListView可以说是在Android各个视图控件里面,对MVC开发模式体现得最为清晰明了的一个视图控件。我们可以把ListView理解成为一个容器,类似于JavaSE里面的集合,不过,往ListView这个容器里面添加数据的方式并不是传统模式的add(Object data)方法,而是需要一个适配器类(Adapter),该类类似于MVC开发模式里面的控制器(C-Controller),ListView视图控件本身就是一个视图(V-View),而ListView控件里面所要显示的数据项,就是模型(M-Model)。其中,Adapter起了一个控制作用,既然是起了控制作用,为什么并没有将Adapter命名成类似于Cotroller这样的名字呢?原因就是因为,本来我们的ListView视图控件本身与里面所要显示的数据(一般是一个数组或者是集合)之间没有任何的联系,也就是没有任何的耦合,如果硬要将java中的两个对象之间本来没有关系的产生关系,使用适配器模式(Adapter)是一种比较合理的选择,所以这里的Adapter就是起了一个连接ListV视图控件和数据(Data)的作用。
按照ListView视图控件的开发形式与需要的不同,我们将之分为如下几种:
1.纯xml配置的方式
2.ArrayAdapter的方式
3.单选按钮的ListView
4.复选按钮的ListView
5.SimpleAdapter的方式
1.纯xml配置的方式
既然ListView是一个视图控件,那么它就可以在布局文件中定义:
list_view_1.xml(布局文件)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:entries="@array/weekdays" <!-- 这里是引用了数组资源文件的内容 -->
/>
</LinearLayout>
arrays.xml(数组资源文件)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="weekdays">
<item>星期日</item>
<item>星期一</item>
<item>星期二</item>
<item>星期三</item>
<item>星期四</item>
<item>星期五</item>
<item>星期六</item>
</string-array>
</resources>
无需写任何的java代码,直接部署到Android模拟器上运行,如下效果:
但是这种方式,仅仅能起到一个展示的效果,对开发真正的应用程序并没有太大的帮助,因为我们要点击里面的具体的列表项去做一定的业务逻辑处理,比如点击“星期五”,去买机票,因为明后天放假了,可以回家看望父母了。
2.使用ArrayAdapter
使用ArrayAdapter,可以在java代码中动态地创建列表项和处理具体的点击列表项的事件。
list_view_2.xml
注意:如果使用在代码中定义ListView列表项数组的时候,android:entries="@array/weekdays"该属性就不能再配置了,如果配置,Android不知道到底是以哪边的数据为准了!
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/listViewId"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>
ListViewActivity2.java源代码
public class ListViewActivity2 extends Activity {
//定义需要在ListView中显示的数据,这里是数组
private String[] weekdays;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_view_2);
//根据资源对象获取资源文件里面定义的数组
weekdays = getBaseContext().getResources().getStringArray(R.array.weekdays);
ArrayAdapter<String> aa = new ArrayAdapter<String>(
ListViewActivity2.this, //上下文对象
android.R.layout.simple_list_item_1,//布局文件
weekdays); //数组对象
//获取布局文件中的ListView
ListView lv = (ListView)findViewById(R.id.listViewId);
//将适配器封装的数组适配给ListView
lv.setAdapter(aa);
//为点击ListView列表项添加事件监听
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(ListViewActivity2.this,
weekdays[position]+"被点击了!",
Toast.LENGTH_LONG).show();
}
});
}
}
整个代码并不难理解,weekdays数组,除了可以以硬编码的方式直接写死在代码里之外,还可以通过资源对象(Resources)来从数组资源文件中获得,这种可配的代码方式还是值得推荐的。主要是ArrayAdapter对象的创建稍微有一点的麻烦,它需要三个参数:上下文对象(Context),而我们这里的Activity是继承自Context类的,所以可以以当前对象作为上下文对象(ListViewActivity2.this);布局文件,这里我们使用了系统自带的一个布局文件simple_list_item_1.xml,该文件可以在sdkplatformsandroid-API版本号datareslayout目录下找到,可以稍微看一下该文件:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center_vertical"
android:paddingLeft="6dip"
android:minHeight="?android:attr/listPreferredItemHeight"
/>
该文件是一个布局文件,仅仅只有一个TextView文本控件,而正是因为它,所有我们在我们的ListView选项列表中只看到一行数据;数组对象,对应的就是从资源文件拿出来的数组。
具体效果如下:
3.单选按钮的ListView
单选按钮的ListView很简单,跟之前第二种的方式几乎一样,就是需要将外观更改一下,需要更改两个地方:
3.1:在创建ArrayAdapter的时候,它的第二个布局文件要换成系统的另外一个布局文件:simple_list_item_single_choice.xml,对应的配置是android.R.layout.simple_list_item_single_choice
3.2:需要为ListView设置一个选择的模式:
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
其他配置与代码不变,如下图:
4.复选按钮的ListView
跟单选按钮的ListView与普通的ListView之间的区别一样,复选按钮的ListView也只需更改外观即可,修改如下:
4.1:在创建ArrayAdapter的时候,它的第二个布局文件要换成系统的另外一个布局文件:simple_list_item_multiple_choice.xml,对应的配置是android.R.layout.simple_list_item_multiple_choice
4.2:需要为ListView设置一个选择的模式:
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
其他配置与代码不变,如下图:
5.SimpleAdapter的方式
上述的一些ListView,只能显示一行的数据,而且这一行数据还是由一个控件来提供的(查看创建Adapter时的Android布局文件可以看出来),如果像一个电话通讯录的那个列表框,如下:(这是真实手机的电话通讯录)
这样的列表该如何实现呢?至少我们之前的列表方式是实现不了了,因为Android系统提供的布局根本不可能适应千变万化的用户需求,所以这个时候,我们就需要根据自己的需求定义自己的布局文件,让这个布局文件填充到ListView中的一行,因为布局文件改了,之前的ArrayAdapter所提供的数据功能就不能满足要求了,这个时候,我们可以使用SimpleAdapter适配器来满足我们的需求。
首选,定义一个我们自己的布局文件,如下:
list_view_oneline.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp" >
<ImageView
android:id="@+id/imageViewId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/rabbit"
/>
<TextView
android:id="@+id/textViewId1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/imageViewId"
android:paddingLeft="3dp"
/>
<TextView
android:id="@+id/textViewId2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/imageViewId"
android:layout_below="@id/textViewId1"
android:layout_alignBottom="@id/imageViewId"
android:paddingLeft="3dp"
/>
</RelativeLayout>
主要的Activity类文件如下:
ListViewActivity5.java
public class ListViewActivity5 extends Activity {
private String[] persons = {"令狐冲","杨过","张无忌","胡斐","韦小宝"};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_view_5);
//定义一个集合,用于存放列表项(map),而里面的Map集合存放一项里面的数据
List<Map<String,Object>> listItems = new ArrayList<Map<String,Object>>();
//往list中添加数据,模拟一些数据,测试使用
for(int i=0;i<persons.length;i++){
Map<String,Object> item = new HashMap<String,Object>();
item.put("name", persons[i]);
item.put("phone", "1505188227"+i);
listItems.add(item);
}
ListView lv = (ListView)findViewById(R.id.listViewId);
//创建SimpleAdapter
SimpleAdapter sa = new SimpleAdapter(
ListViewActivity5.this, //上下文对象
listItems, //数据集合
R.layout.list_view_oneline, //自定义的布局文件
new String[]{"name","phone"}, //规定了数据集合Map的全部的key
new int[]{R.id.textViewId1,R.id.textViewId2});//规定了数据集合Mapkey对应的值应该跟自定义布局的哪一个组件对应
lv.setAdapter(sa);
}
}
请注意红色部分的代码,在往List集合里面添加Map对象的时候,创建这个map对象,再往map集合里面添加数据时的key,要与创建SimpleAdapter时的第四个参数里面的数组的元素一致,map的value值正好与创建SimpleAdapter时的第五个参数相匹配。运行之后如下效果:
当然了,如果想将数据写的更真实一点,可以再模拟数据的时候,将图片以动态获取的方式生成。
问题:如何处理列表项里面的具体控件的点击事件?
我们知道,真正的电话通讯录的列表,点击前面的头像,会直接打电话,如果点击后面的名字,会进入查看,或者编辑当前联系人的联系信息。而我们这里的列表内容,不管是点击图片还是后面的文字,它都只会当做是点击了整个列表项,并不能区分具体点击了哪一个,那我们应该怎么办呢?
SimpleAdapter类有一个方法,叫做getView,该方法默认实现就相当于点击了整个列表项,我们可以定义一个我们自己的SimpleAdapter,覆盖该方法,改成适合我们自己业务的实现即可,具体做法如下:
定义一个继承自SimpleAdapter类的子类:MySimpleAdapter,并覆盖getView方法:
class MySimpleAdapter extends SimpleAdapter{
//必须要写的构造器
public MySimpleAdapter(Context context,
List<? extends Map<String, ?>> data, int resource,
String[] from, int[] to) {
super(context, data, resource, from, to);
}
//覆盖该方法,以自己的方式处理用户的点击动作
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
ImageView iv = (ImageView)view.findViewById(R.id.imageViewId);
TextView tv1 = (TextView)view.findViewById(R.id.textViewId1);
TextView tv2 = (TextView)view.findViewById(R.id.textViewId2);
iv.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(ListViewActivity5.this, "点击了图片,准备去打电话了", 1000);
}
});
tv1.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(ListViewActivity5.this, "点击了用户名,准备去编辑用户名了", 1000);
}
});
tv2.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(ListViewActivity5.this, "点击了图片,准备去编辑电话号码了", 1000);
}
});
return view;
}
}
在主类的Activity中,就需要使用自己的SimpleAdapter来作为适配器了,这样连处理ListView的列表选项的事件监听都无需处理了,如下图效果:
问题:如何动态地刷新ListView里面的数据呢?
这样的场景我们经常见到:删除我们联系人列表里面的某一个联系人,然后再回到列表页面的时候,刚刚删除的联系人就不会出现在列表项中,这个该如何处理?
这个其实很简单,只需要调用SimpleAdapter的notifyDataSetChanged()方法即可,也就是说需要在往创建SimpleAdapter时,在其构造器的第二个参数的List添加或者是删除一个map的时候需要调用该方法,核心代码如下:
String name = ((EditText)addView.findViewById(R.id.editTextId1)).getText().toString();
String phone = ((EditText)addView.findViewById(R.id.editTextId2)).getText().toString();
Map<String,Object> item = new HashMap<String,Object>();
item.put("name", name);
item.put("phone", phone);
listItems.add(item);
adapter.notifyDataSetChanged();
最终效果如下:
未完待续...