本文部分內容啓發於:http://www.cnblogs.com/allin/archive/2010/05/11/1732200.html
首先上類圖:
一、在程序開發中,我們常用到ListView,使用ListView需要三個元素:
1、ListView控件:展示每條數據的框架,是一個ViewGroup;
2、Adapter適配器:連接數據與展示View的中介,通知ListView繪製多少View,每條View怎麼繪製,View內部事件如何響應;
3、數據:具體被映射的字符串、圖片、狀態等信息,有特定的格式要求,具體視Adapter實現而定;
二、適配器Adapter的基礎功能定義在接口Adapter中,包括10個方法:
1、registerDataSetObserver/unregisterDataSetObserver:註冊/撤銷Adapter關聯數據發生變化的監察者,註冊後可以完成重繪界面等操作;
2、getCount:返回當前Adapter需要繪製多少條數據,ListView按返回值繪製N行View;
3、getItem(int position):返回當前Adapter持有的對應position的數據,返回值對應ListView繪製的某一行展現的數據;
4、getItemId(int position):返回當前Adapter對應位置的數據的唯一標識id。通常如果沒有就返回position,如果是數據庫內讀出的數據就返回主鍵,如果item對應有int的唯一標識符就返回該值,該值如果設置不正確,可能會導致ListView的onItemClick內的操作對象不是你要處理的對象,比如你刪某個item,卻刪了另一個,或者刪除不起作用;
5、hasStableIds:返回當前Adapter持有的數據實體們是否有穩定的唯一標識id,比如4中講到的返回position是不穩定的,可能隨着數據變化導致其值變化,此時需要 返回false,如果返回主鍵或者唯一標識符則該方法可以返回true。其作用是:返回true,ListView會依據4中的返回的id來確定當前顯示哪條內容,也就是firstVisibleChild的位置。
6、getView(int position, View convertView, ViewGroup parent):返回當前Adapter對應position應該繪製的View,這個View可以千奇百怪,需要配套getItemViewType使用,一個列表內所有數據展示都一模一樣的就不需要了;
7、getItemViewType(int position):返回當前Adapter對應position位置數據的View的類型,比如你第一行繪製了一個TextView,第二行繪製了一個ImageView,在ListView滾動的時候,第二行肯定不能複用來顯示第一行應該顯示的數據,這時候就讓它們返回不同的ViewType,ListView控件就不會不識趣了,這裏需要注意AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2 已經被佔用了,ITEM_VIEW_TYPE_IGNORE = -1 也沒了,所以我們用正數一般都沒事;
8、getViewTypeCount:返回當前Adapter持有多少種ViewType,6中返回了多少種,這裏寫總數就行;
9、isEmpty:返回當前Adapter是否持有數據,官方文檔說法是:它用於判斷空的View是否需要展示,如果沒有headers和footers,返回getCount==0就好了,否則可以自定義;
這些裏面我們一般情況下主要和getCount、getItem(int position)、getItemId(int position)、getView打交道,因爲BaseAdapter已經實現了上面的其他方法;
三、Adapter的繼承者包括ListAdapter和SpinnerAdapter,也是接口。
1、 其中ListAdapter連接ListView和數據,增加了判斷ListView中繪製的View是否是分隔符的接口,定義了兩個方法:
1)areAllItemsEnabled:返回true標識所有的item對應的View都是可點擊可選擇的,也就是沒有分隔符;
2)isEnabled(int position):返回true標識當前Adapter對應position位置數據不是一個分隔符,因爲分隔符不可點擊且不可選擇;
2、 SpinnerAdapter連接Spinner和數據,獲取Spinner的下拉框內的顯示控件,只定義了一個方法:
1)getDropDownView(int position, View convertView, ViewGroup parent):返回當前Adapter對應position應該繪製的下拉框內的View;
四、擴充的接口往下就是我們比較熟悉的類BaseAdapter了,還有個擴展接口WrapperListAdapter。
1、 我們先講WrapperListAdapter,它定義了一個包裹另一個ListAdapter的類,增加了一個實現方法:
1)getWrappedAdapter:返回被包裹的ListAdapter;
在android系統內,系統只提供了它的一個實現類——HeaderViewListAdapter,也就是包含Header View和Footer View的ListAdapter,其內部維持了列表頭尾的增加刪除等操作,且封裝了一層ListAdapter,從而爲ListView提供列表頭尾的管理與數據關聯工作。該類還實現了Filterable接口,對外提供篩選功能(自動匹配功能,就是電話簿中輸入號碼查找出合適聯繫人的功能);
2、 下面我們詳細講一下BaseAdapter,因爲它是我們常用到的ArrayAdapter、SimpleAdapter、CursorAdapter和自定義Adapter的父親。
BaseAdapter非常簡單,絕大部分都是接口的默認實現,它持有一個android.database.DataSetObservable的對象,通過該對象註冊/註銷數據觀察者,並在接口之外提供了兩個方法:
1)notifyDataSetChanged:外部或內部調用通知數據觀察者適配器內持有的數據發生了改變,view監聽到該事件後可以刷新界面;
2)notifyDataSetInvalidated:外部或內部調用通知數據觀察者適配器內持有的數據已經失效,注意:按照官方解釋,該方法一旦調用,這個Adapter就失效了,也就不能接着報告數據變化事件;
這兩個方法都調用DataSetObservable對象的方法實現;
BaseAdapter的其他默認實現包括:
1)hasStableIds 返回false,也就是默認itemId不穩定,如果我們有穩定id,可以覆寫爲true;
2)areAllItemsEnabled 返回true,也就是默認沒有分隔符,所有item都可點擊可選擇;
3)isEnabled(int position) 返回true,同2;
4)getDropDownView 調用了getView;
5)getItemViewType(int position) 返回0,意味着默認情況下所有我們通過getView生成的View都是同類型的;
6)getViewTypeCount 返回1,依據5;
7)isEmpty 返回getCount==0,因爲沒有持有列表頭尾;
五、BaseAdapter的繼承者們就是我們做界面開發直接接觸的類了,它們包括:ArrayAdapter<T>、SimpleAdapter、CursorAdapter和自定義Adapter。這裏略過CursorAdapter,沒有用過,以後看機會再補上。
1、ArrayAdapter
ArrayAdapter只能用於顯示一行文字,用法一般如下:
mAdapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_spinner_item, getData());
mListView.setAdapter(mAdapter);
該類內置add,insert, remove, clear等方法,實現了BaseAdapter沒有搞定的4個方法,實現了自動匹配Filterable接口,還提供了兩個額外的方法:
1)setDropDownViewResource(int resource):供Spinner使用,設置下拉框顯示字符串的View資源樣式,也就是一個xml文件,文件裏只能定義一個TextView的子類,不能加LinearLayout之類的,要注意。
2)setNotifyOnChange(boolean notifyOnChange):設置爲false可以阻止界面知道數據發生了變化,直到外部調用notifyDataSetChanged將mNotifyOnChange開關打開爲止;
2、SimpleAdapter
SimpleAdapter相對而言比ArrayAdapter自由很多,我們可以用自己的定義的界面,可以展現很多種數據,包括選中狀態、文字、圖片等;它的缺點就是數據格式太死板,我們必須使用List<? extends Map<String, ?>>作爲數據源,這個轉化過程很沒有美感,且耗時;
android實現的SimpleAdapter默認只允許待顯示對象爲Checkable、TextView、ImageView的數據,用法一般如下:
1)佈局:
<?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="wrap_content"
android:orientation="horizontal" >
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<CheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF000000" />
</LinearLayout>
</LinearLayout>
2)代碼
package com.example.testlistview;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
public class MainActivity extends Activity {
private ListView mListView = null;
private BaseAdapter mAdapter = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
mAdapter = new SimpleAdapter(getApplicationContext(), getData(), R.layout.simple_adapter_demo,
new String[] {"text", "image", "checkable"},
new int[] {R.id.textView, R.id.imageView, R.id.checkBox});
mListView.setAdapter(mAdapter);
}
private List<? extends Map<String, ?>> getData() {
ArrayList<HashMap<String, Object>> list = new ArrayList<HashMap<String,Object>>();
HashMap<String, Object> map1 = new HashMap<String, Object>();
map1.put("text", "nihao");
map1.put("image", android.R.drawable.ic_secure);
map1.put("checkable", false);
list.add(map1);
HashMap<String, Object> map2 = new HashMap<String, Object>();
map2.put("text", "android");
map2.put("image", android.R.drawable.ic_delete);
map2.put("checkable", true);
list.add(map2);
return list;
}
private void initView() {
mListView = (ListView) findViewById(R.id.listview);
}
}
3) 效果:
當然,SimpleAdapter也不是隻能顯示這三種數據類型,它有個內部接口ViewBinder,只要自己實現一個ViewBinder的子類,並通過setViewBinder(ViewBinder viewBinder)賦值給SimpleAdapter,我們也可以定義對其他的控件進行賦值。這是SimpleAdapter的擴展性。
不過,說實話瑜不掩瑕,如果數據格式不是Map的,不建議用SimpleAdapter,費腦筋。
六、萬能的自定義Adapter
好了,我們爲了講解ListView的適配器Adapter,從Adapter接口往下講了四層了,到了這裏,就是故事的高潮部分。我們講自定義Adapter,到目前爲止,能裝在ListView裏面的東西只有Checkable、String和Bitmap,這明顯不夠用,但是android就給了這麼點功能,所以我們就得自己動手DIY了。
自定義Adapter,一般都繼承BaseAdapter,因爲BaseAdapter替我們實現了Adapter的基本功能,包括數據變化通知、部分接口實現等等,雖然ListView的setAdapter方法只要實現了ListAdapter接口的就可以用,但是有現成的,我們幹嘛要再實現一遍那十幾個接口呢,對吧!~
我們先說一下自定義Adapter的好處:
1)數據存儲方式靈活:隨便什麼格式的,只要自己知道格式,就能往裏面放;
2)界面展現方式靈活:你可以任意佈局擺弄數據,可以加自定義的分隔符,可以加按鈕等等等等;
好了,好處說完了就開始動手了。真寫起來,其實自定義Adapter沒有啥特別的,繼承BaseAdapter,如前文所敘,裏面有四個方法是需要我們搞定的,分別是:
1)getCount(),返回傳入我們定義的Adapter內的數據列表長度,用於通知ListView繪製多少行。
2)getItem(int position),返回傳入的數據列表的第position個數據。
3)getItemId(int position),返回傳入的數據列表的第position個數據對應的唯一標識符,強烈建議不要返回position。
4)getView(int position, View convertView, ViewGroup parent),返回傳入的數據列表的第position個數據對應在ListView的行內應該怎麼繪製,我們甚至可以判斷一下position,然後在第三行繪製一個ImageView,展現一個深情款款的大豬頭。當然,如果其他各行都不是ImageView的話,我們一定要覆寫getItemViewType(int position)和getViewTypeCount(),因爲BaseAdapter默認每行類型都是0,總的TypeCount爲1。
這裏有個兩個需要注意的地方:
1)convertView會爲當前position帶來一個可用的view,是給我們複用的,作用是當滾動導致ListView內某行View不可見時,直接複用該View來展示第position個數據,這樣能大大減少內存使用量,不管要顯示的數據是不是幾千條,一般顯示一行字的ListView最多創建10幾個View就可以滿足顯示要求了,所以一定得用上。
2)View有個setTag方法,而我們使用findViewById是要查找View樹的,相對而言,創建一個內部類ViewHolder並設爲View的Tag,會在複用convertView時直接提供對象引用(如TextView、ImageView等等),所以ViewHolder也應該用上。
下面上代碼:
1)佈局my_adapter_demo.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="wrap_content"
android:orientation="horizontal" >
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >
<CheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF000000" />
</LinearLayout>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="刪除" />
</LinearLayout>
2)數據體Data.java
package com.example.testlistview;
import android.graphics.Bitmap;
public class Data {
private int mId = -1;
private String mText = null;
private Bitmap mImage = null;
private boolean mIsChecked = false;
public Data(int mId, String mText, Bitmap mImage, boolean mIsChecked) {
super();
this.mId = mId;
this.mText = mText;
this.mImage = mImage;
this.mIsChecked = mIsChecked;
}
public int getId() {
return mId;
}
public void setId(int mId) {
this.mId = mId;
}
public String getText() {
return mText;
}
public void setText(String mText) {
this.mText = mText;
}
public Bitmap getImage() {
return mImage;
}
public void setImage(Bitmap mImage) {
this.mImage = mImage;
}
public boolean isChecked() {
return mIsChecked;
}
public void setIsChecked(boolean mIsChecked) {
this.mIsChecked = mIsChecked;
}
}
3)自定義Adapter MyAdapter.java
package com.example.testlistview;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
public class MyAdapter extends BaseAdapter {
private static final String TAG = "MyAdapter";
private List<Data> mList = null;
private LayoutInflater mInflater = null;
private Context mContext = null;
public MyAdapter(Context context, List<Data> list) {
if(list == null) {
list =new ArrayList<Data>();
}
mList = list;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mContext = context;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
//最好返回Data對應的唯一標識id
return mList.get(position).getId();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Data data = mList.get(position);
View view = null;
ViewHolder holder = null;
if(null == convertView) {
view = mInflater.inflate(R.layout.my_adapter_demo, null);
holder = new ViewHolder();
holder.id = data.getId();
holder.textView = (TextView) view.findViewById(R.id.textView);
holder.imageView = (ImageView) view.findViewById(R.id.imageView);
holder.checkBox = (CheckBox) view.findViewById(R.id.checkBox);
holder.button = (Button) view.findViewById(R.id.button);
//將持有的控件引用存入View的Tag內
view.setTag(holder);
} else {
//複用convertView,提高ListView的效率,減少內存佔用
view = convertView;
//讀出控件引用
holder = (ViewHolder) view.getTag();
Log.d(TAG, holder.id + " is now display " + data.getId());
holder.id = data.getId();
}
holder.textView.setText(data.getText());
holder.imageView.setImageBitmap(data.getImage());
holder.checkBox.setChecked(data.isChecked());
holder.button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext, "按鈕被點擊了", Toast.LENGTH_SHORT).show();
}
});
return view;
}
@Override
public int getItemViewType(int position) {
if(position == 2) {
return 1;
}
return super.getItemViewType(position);
}
@Override
public int getViewTypeCount() {
return 2;
}
private final class ViewHolder {
int id = -1;
TextView textView = null;
ImageView imageView = null;
CheckBox checkBox = null;
Button button = null;
}
}
4)主界面 MainActivity.java
package com.example.testlistview;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.BitmapFactory;
import android.view.Menu;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
public class MainActivity extends Activity {
private ListView mListView = null;
private BaseAdapter mAdapter = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
mAdapter = new MyAdapter(this, getData());
mListView.setAdapter(mAdapter);
}
private List<Data> getData() {
ArrayList<Data> list = new ArrayList<Data>();
Data data1 = new Data(1, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data1);
Data data2 = new Data(2, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data2);
Data data3 = new Data(3, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data3);
Data data4 = new Data(4, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data4);
Data data5 = new Data(5, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data5);
Data data6 = new Data(6, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data6);
Data data7 = new Data(7, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data7);
Data data8 = new Data(8, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data8);
Data data9 = new Data(9, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data9);
Data data10 = new Data(10, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data10);
Data data11 = new Data(11, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data11);
Data data12 = new Data(12, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data12);
Data data13 = new Data(13, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data13);
Data data14 = new Data(14, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data14);
Data data15 = new Data(15, "nihao",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_secure), false);
list.add(data15);
Data data16 = new Data(16, "android",
BitmapFactory.decodeResource(this.getResources(), android.R.drawable.ic_delete), true);
list.add(data16);
return list;
}
private void initView() {
mListView = (ListView) findViewById(R.id.listview);
}
}
通過這個例子我們可以驗證,不同ViewType的View是不會作爲convertView使用的,另外往下滾動時,上方不可見的View會被複用來顯示下方將要顯示出來的數據。
好了,整個Android的ListView Adapter就介紹到這裏,有後續需要注意的地方,我會接着這篇文章往下寫的。