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

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



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


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


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




其中,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;
	}
}


运行效果如图示:




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

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



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

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


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




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




可以看出,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 方法反复的被调用,从而产生很多对象,当数据量很大的的时候上面的做法效率肯定高不了,那么,如何优化呢?且听下回分解!




©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页