ListView 编程: Adapter 何方神圣?

本来是想用一篇博客一口气写完:如何优化ListView ,但是我发现这样做吃力不讨好,一方面,自己太累,另一方面给人的感觉是在记账,根本不是在交流。


最后还是觉得分开写会好一点,每一篇突出一个重点比较好。欢迎交流。


在这篇博客中,你可以了解到:


1)Adapter(适配器)模式简介


2)android Adapter 类简介


3)android Adapter 与 ListView 之间的关系。


4)如何自定义 Adapter 以及注意事项


设计模式很抽象,熟练使用各种设计模式需要不断地实践和思考。Adapter,适配器,也是一种设计模式(适配器模式)。


关于适配器模式,在这里可以打个比方:android 手机需要充电,但是你不可以直接使用 220v 的电压来给它充电除非你恨透了这个设备。那么,我们会使用标准的充电器来给该手机充电(它会进行电压电流的转换),那么这个充电器就好比 Adapter(适配器)。


GOF 关于 Adapter 模式是这样解释的:

将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使原本由于接口不兼容而不能一起工作的那些类可以一起工作。


当然,这篇博客不是讲解 Adapter 模式的,关于该模式大家可以 GOOGLE 一下。


android 中 Adapter 是一个接口,api 声明如下:


ListView 编程: Adapter 何方神圣?


该接口有很多间接或者直接的子接口、实现类。


ListView 编程: Adapter 何方神圣?

ListView 在 APP 开发中使用比较频繁,而 Adapter 是 ListView 与数据打交道的桥梁,当列表里的每一项显示到页面时,都会调用Adapter的getView方法返回一个View

二者的关系可以使用下图简单描述:

ListView 编程: Adapter 何方神圣?


使用 ListView 的同时,一般会使用如 ArrayAdapter、SimpleAdapter、SimpleAdapter 来为其添加显示数据。


ArrayAdapter、SimpleAdapter、SimpleAdapter这三个类直接或者间接的继承了 BaseAdapter(该类是一个抽象类)类。


BaseAdapter(该类是一个抽象类)类,api 声明如下:


ListView 编程: Adapter 何方神圣?


其中,ListAdapter、SpinnerAdapter 是 Adapter 的子接口。


一般为了是 UI 更加的人性化更加的美观,设计人员会自己写一个类继承 BaseAdapter。

那么举一个实例来看看如何实现这个子类,以及需要实现的方法的回调时机。


Activity 代码

package mark.zhang; import android.app.ListActivity; import android.os.Bundle; public class FileActivity extends ListActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 实例化自定义Adapter FileViewAdapter adapter = new FileViewAdapter(this); // 设置适配器 setListAdapter(adapter); } }

FileViewAdapter (自定义 Adapter)代码

package mark.zhang; import android.content.Context; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; public class FileViewAdapter extends BaseAdapter { private LayoutInflater inflater = null; public FileViewAdapter(Context context) { inflater = LayoutInflater.from(context); } @Override public int getCount() { Log.d("mark", "getCount() is invoked!"); return 10; } @Override public Object getItem(int position) { Log.d("mark", "getItem() is invoked!"); return position; } @Override public long getItemId(int position) { Log.d("mark", "getItemId() is invoked!"); Log.d("mark", "position = " + position); return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { Log.d("mark", "getView() is invoked!"); View v = inflater.inflate(R.layout.custom_fileview, null); ImageView image = (ImageView) v.findViewById(R.id.image_pic); TextView context = (TextView) v.findViewById(R.id.text_content); context.setText("fileName"); image.setImageResource(R.drawable.file); return v; } }

运行效果如图示:


ListView 编程: Adapter 何方神圣?


那么,重写的那四个方法(@Override),调用情况如下。

运行该 App 之后,打印信息如下:

ListView 编程: Adapter 何方神圣?


getCount 方法返回需要显示的数据的个数,在 FileViewAdapter 代码中 getCount 方法返回数据为 10(这个数据可以根据实际情况修改)。

从打印信息来看,getView 方法被调用了 10 次,这也正好说明 getCount 方法返回数据是多少,getView 方法就会被调用多少次。


再做一个测试,首先退出该 APP,再次启动该 APP,打印信息如下:


ListView 编程: Adapter 何方神圣?


这次可以看出 getItemId 方法没有被调用。那么,何时会再次调用这个方法呢?点击列表中的任意一条数据,这里点击第一条数据,看看打印信息:


ListView 编程: Adapter 何方神圣?


可以看出,getItemId 方法被调用,并且可以看出 ListView 中数据是以 0 开始为索引值的,类似数组的索引。

那么,思考一个问题,何时调用 getItem 方法呢,在 android 的 BaseAdapter 源码中没有发现直接调用 getItem 方法的代码(看小结吧)。


更多关于 Adapter 的方法,可以参阅 SDK API 文档。


小结:


1. 四个方法的重写

FileViewAdapter 继承 BaseAdapter,重写以下四个方法:getCount、getItem、getItemId、getView。


2. 绘制 ListView

首先,系统在绘制 ListView 之前,将会先调用 getCount 方法来获取 Item 的个数。之后每绘制一个 Item 就会调用一次 getView 方法(getCount 方法返回几个数据,getView方法 就会被调用几次),getView 方法内就可以使用自定义好的 xml 来确定显示的效果并返回一个 View 对象作为一个 Item 显示出来。


3. getView、getCount 方法

在绘制L istView 过程中完成了适配器的主要转换功能,把数据和资源以开发者想要的效果显示出来。重复调用getView,使得 ListView 的使用更为简单和灵活。

getView、getCount 两个方法是自定 ListView 显示效果中最为重要的,同时只要重写好了就两个方法,ListView 就能完全按开发者的要求显示。


4.getItem、getItemId 方法

而 getItem 和 getItemId 方法将会在调用 ListView 的响应方法的时候被调用到。


在 ListView 的源码中可以看出,很多地方都调用了 getItemId 方法,但是没有直接看到调用 getItem 方法的。那么,我们还是往深层次看吧,从 ListView 源码追踪到 AdapterView 源码,可以看到:

/** * Gets the data associated with the specified position in the list. * * @param position Which data to get * @return The data associated with the specified position in the list */ public Object getItemAtPosition(int position) { T adapter = getAdapter(); return (adapter == null || position < 0) ? null : adapter.getItem(position); } public long getItemIdAtPosition(int position) { T adapter = getAdapter(); return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position); }
可以看出这两个方法都是 public 的,当我们调用这两个方法时候,就会调用 getItem 或者 getItemId 方法。

为了保证 ListView 的各个方法有效,这两个方法也得重写。


附录 -- xml 文件


main.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>

custom_fileview.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:id="@+id/image_pic" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dip" /> <TextView android:id="@+id/text_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dip" android:textSize="20sp" /> </LinearLayout>

在这篇博客中,可以看出 getView 方法反复的被调用,从而产生很多对象,当数据量很大的的时候上面的做法效率肯定高不了,那么,如何优化呢?且听下回分解!