ListView詳解之三


參考http://www.cnblogs.com/angeldevil/archive/2011/11/20/2255972.html

上上篇,我們使用了幾次Adapter,如ArrayAdapter、SimpleAdapter;上篇,我們又見識了更多的Adapter,有點暈了,到底什么是Adapter?

一、Adapter的作用

Adapter是AdapterView視圖與數據之間的橋梁,Adapter提供對數據的訪問,也負責為每一項數據產生一個對應的View。其作用如下圖所示:

看了這幅圖,我想你大概明白什么是Adapter了,不過你肯定也有了新的疑問,不管你有沒有,反正我是有過了。新疑問:這不是一直在講ListView嗎?那AdapterView是什么呢?

大概猜一下,它應該是所有要用到Adapter的View的父類吧。去開發文檔里面一看,果然如此,看下圖:

再點進去,看看listview:

這下更明白了吧!不過說實話,我現在更迷惑的是android類的繼承關系了,要說搞通所有類的繼承關系,一下兩下也不現實,先看看下面的Adapter的繼承關系吧!

二、Adapter的繼承結構

要了解Adapter的繼承和派生關系了,還好前面了解了這點東西,http://www.cnblogs.com/fww330666557/archive/2012/01/11/2319613.html,以作參考。

然后在去看看Adapter:

什么?Adapter是一個接口?我一直以為它是類。

什么?接口可以派生類?“接口!=函數或方法”嗎?http://www.cnblogs.com/huihui-gohay/archive/2009/12/13/1623070.html(這篇文章說了接口的問題,我沒仔細看,你可以去看看)。

再確認一下,widget是個包,沒學過java,到底什么是包?我的理解,跟一個文件差不多吧!

還有,要分清楚Adapter和AdapterView。

 

三、各個類的作用

下面的內容怎么說呢,有點繁雜,如果你有耐心,你就細細看吧,不過最好是先了解個大概,我們這里的關鍵任務是理解Adapter各個類之間的繼承關系。

Adapter

Adapter做為這個繼承結構的最頂層的基接口,定義了Adapter要實現的基本方法:

public interface Adapter { 
//注冊一個Observer,當Adapter所表示的數據改變時會通知它,DataSetObserver是一個抽象類,定義了兩個方法:onChanged與onInvalidated
void registerDataSetObserver(DataSetObserver observer);
//取消注冊一個Observer
void unregisterDataSetObserver(DataSetObserver observer);
//所表示的數據的項數
int getCount();
//返回指定位置的數據項
Object getItem(int position);
//返回指定位置的數據項的ID
long getItemId(int position);
//表示所有數據項的ID是否是穩定的,在BaseAdapter中默認返回了false,假設是不穩定的,在CursorAdapter中返回了true,Cursor中的_ID是不變的
boolean hasStableIds();
//為每一個數據項產生相應的視圖
View getView(int position, View convertView, ViewGroup parent);
//為了避免產生大量的View浪費內存,在Android中,AdapterView中的View是可回收的使用的。比如你有100項數據要顯示,而你的屏幕一次只能顯示10條數據,則
//只產生10個View,當往下拖動要顯示第11個View時,會把第1個View的引用傳遞過去,更新里面的數據再顯示,也就是說View可重用,只是更新視圖中的數據用於顯示新
//的一項,如果一個視圖的視圖類型是IGNORE_ITEM_VIEW_TYPE的話,則此視圖不會被重用
static final int IGNORE_ITEM_VIEW_TYPE = AdapterView.ITEM_VIEW_TYPE_IGNORE;
//獲得相應位置的這圖類型
int getItemViewType(int position);
//getView可以返回的View的類型數量。(在HeaderViewListAdapter中可以包含Header和Footer,getView可以返回Header、Footer及Adapter
//中的視圖,但其getViewTypeCount的實現只是調用了內部Adapter的的getViewTypeCount,忽略了Header、Footer中的View Type,不懂。
int getViewTypeCount();
static final int NO_SELECTION = Integer.MIN_VALUE;
boolean isEmpty();
}

Adapter有兩個子接口,ListAdapter(列表)與SpinnerAdapter(下拉列表),它們都只定義了少數方法。一般除WrapperListAdapter接口及其實現類只實現了ListAdapter外,都同時實現了這兩個接口。

ListAdapter

//是否在ListAdapter中的所有項都enabled,即是否所有項都selectable和clickable 
public boolean areAllItemsEnabled();
//指定位置的項是否是enabled的
boolean isEnabled(int position);

  SpinnerAdapter 
 
//產生相應位置下拉項的視圖 
public View getDropDownView(int position, View convertView, ViewGroup parent);
BaseAdapter
一個抽象類,Adapter的基礎實現類,一般作為其他實現類的基類,同時實現ListAdapter與SpinnerAdapter,提供了一些方法的默認實現:
 
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { 
//提供一些方法,當數據改變時調用注冊的DataSetObserver的回調函數
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
//通知相關聯的視圖,相應的數據已經改變
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
public boolean areAllItemsEnabled() {
return true;
}
public boolean isEnabled(int position) {
return true;
}
//通過getView實現
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
}
public boolean isEmpty() {
return getCount() == 0;
}
}

ArrayAdapter<T>

A concrete BaseAdapter that is backed by an array of arbitrary objects. By default this class expects that the provided resource id references a single TextView. If you want to use a more complex layout, use the constructors that also takes a field id. That field id should reference a TextView in the larger layout resource.

However the TextView is referenced, it will be filled with the toString() of each object in the array. You can add lists or arrays of custom objects. Override the toString() method of your objects to determine what text will be displayed for the item in the list.

To use something other than TextViews for the array display, for instance, ImageViews, or to have some of data besides toString() results fill the views, override getView() to return the type of view you want.

默認ArrayAdapter產生的視圖期望一個TextView,或者也可以指定一個Layout並指定其中一個類型為TextView的資源的ID,其底下的數據可以是任意類型,但顯示的時候會調用其toString()方法,也就是說只能用TextView顯示文字,如果想顯示其他的數據,要重寫getView()

ArrayAdapter有5個構造函數:

public ArrayAdapter(Context context, int textViewResourceId) { 
init(context, textViewResourceId, 0, new ArrayList<T>());
}
public ArrayAdapter(Context context, int resource, int textViewResourceId) {
init(context, resource, textViewResourceId, new ArrayList<T>());
}
public ArrayAdapter(Context context, int textViewResourceId, T[] objects) {
init(context, textViewResourceId, 0, Arrays.asList(objects));
}
public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) {
init(context, resource, textViewResourceId, Arrays.asList(objects));
}
public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) {
init(context, textViewResourceId, 0, objects);
}
public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) {
init(context, resource, textViewResourceId, objects);
}

最多有4個參數,分別為當前Context,layout(可省),TextView的ID與數據(可為數組或List),在構造函數中都調用了一個叫init的函數

private void init(Context context, int resource, int textViewResourceId, List<T> objects) { 
mContext = context;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mResource = mDropDownResource = resource;
mObjects = objects;
mFieldId = textViewResourceId;
}

 

分別賦值給類中的私有域,mInflater為LayoutInflater,產生相應項的視圖。

類中有兩個域保存數據

private ArrayList<T> mOriginalValues; 
private List<T> mObjects;

 

其中mOriginalValues用於過濾數據時保存過濾前的數據,將過濾后的數據存入mObjects。

在ArrayAdapter中還定義了add,insert,remove,clear函數用於改變數據,並定義了一個布爾變量mNotifyChange用於表示用這些函數改變數據后是否通知視圖(調用notifyDataSetChanged,調用這個函數時會把mNotifyChange置為true。

一些函數的實現:

public int getCount() { 
return mObjects.size();
}
public T getItem(int position) {
return mObjects.get(position);
}
public int getPosition(T item) {
return mObjects.indexOf(item);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mResource);
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mDropDownResource);
}

  可以看到getView和getDropDownView都通過調用createViewFromResourse來產生視圖。
private View createViewFromResource(int position, View convertView, ViewGroup parent, 
int resource) {
View view;
TextView text;

if (convertView == null) {
view = mInflater.inflate(resource, parent, false);
} else {
view = convertView;
}

try {
if (mFieldId == 0) {
// If no custom field is assigned, assume the whole resource is a TextView
text = (TextView) view;
} else {
// Otherwise, find the TextView field within the layout
text = (TextView) view.findViewById(mFieldId);
}
} catch (ClassCastException e) {
Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
throw new IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", e);
}

T item = getItem(position);
if (item instanceof CharSequence) {
text.setText((CharSequence)item);
} else {
text.setText(item.toString());
}

return view;
}

  在createViewFromResource中,首先判斷convertView是否存在,若不存在則inflate一個,然后判斷mFieldID是否為0,若為0則表示傳遞給ArrayAdapter的資源ID為一TextView,否則是傳遞了一Layout,mFieldID為此Layout中TextView的ID。然后通過getItem取得相應位置的數據項,判斷是否是CharSequence的實例,如果是直接setText,否則調用其toString()函數,可以ArrayAdapter默認只能給TextVext設置字符串,若要使用其他視圖,需要重載getView或getDropDownView,一般情況下會繼承BaseAdapter自定義自己的Adapter。
在ArrayAdapter中,還有一靜態函數
public static ArrayAdapter<CharSequence> createFromResource(Context context, 
int textArrayResId, int textViewResId) {
CharSequence[] strings = context.getResources().getTextArray(textArrayResId);
return new ArrayAdapter<CharSequence>(context, textViewResId, strings);
}

  讀取資源文件中定義的字符數組作為數據生成ArrayAdapter,可以看到只能用TextView視圖,而不可以指定一Layout再指定Layout中一個TextView的ID。

在ArrayAdapter中還定義了一個ArrayFilter,繼承自Filter,用於過濾數據項(當ListView有焦點時,通過鍵盤輸入字符來進行列表項的過濾),其過濾方法為傳入一CharSequence,判斷相應數據項是否以此CharSequence開頭,若不是則用空格分割些數據項,判斷分割后的各字符串是否以此CharSequence開頭,若是則保留(若數據不是CharSequence則調用其toString())。如果傳入的CharSequence為null或長度為0則不過濾。

 

CursorAdapter

 

用於顯示Cursor中的數據。

 

在構造函數中可傳遞一參數autoRequery表示當cursor的數據改變時是否自動調用cursor的requery()以保持視圖數據為最新的。

此類中重寫了hasStableIds(),返回true。

public boolean hasStableIds() { 
return true;
}

在CursorAdapter中,重寫的getView及getDropDownView判斷傳入的convertView是否為null,若為null及相應地調用newView()或newDropDownView()來生成一個視圖,而newDropDownView()只有一條語句 return newView(context, cursor, parent);所以最后都是調用newView(),newView()為abstract的,需要由子類重寫。

當通過newView()產生一個View之后,會調用 bindView(v, mContext, mCursor);將cursor中的數據綁定到newView()產生的View之中,此方法同樣為 abstract 的。
CursorAdapter實現了接口CursorFilter.CursorFilterClient中的方法
//改變cursor指向的數據
public void changeCursor(Cursor cursor)

 
//將cursor轉變為CharSequence,返回""或調用cursor.toString()
public CharSequence convertToString(Cursor cursor) 
//過濾數據
public Cursor runQueryOnBackgroundThread(CharSequence constraint)

 

ResourceCursorAdapter

如類名所示,該類繼承自CursorAdapter,通過XML產生Views,該類只是簡單地重寫了一些函數,通過LayoutInflater.inflate將XML轉換為View

@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(mLayout, parent, false);
}

@Override
public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(mDropDownLayout, parent, false);
}

 

SimpleCursorAdapter

一個ResourceCursorAdapter的簡單實現類,用於把cursor中相應的列映射為XML定義的視圖中的TextView和ImageView。

該類中定義了一個接口

public static interface ViewBinder { 
boolean setViewValue(View view, Cursor cursor, int columnIndex);
}

 

用於設置把cursor中的列映射為視圖的方法。

在SimpleCursorAdapter中重寫了bindView,控制cursor到視圖的綁定,其定義如下

@Override
public void bindView(View view, Context context, Cursor cursor) {
final ViewBinder binder = mViewBinder;
final int count = mTo.length;
final int[] from = mFrom;
final int[] to = mTo;

for (int i = 0; i < count; i++) {
final View v = view.findViewById(to[i]);
if (v != null) {
boolean bound = false;
if (binder != null) {
bound = binder.setViewValue(v, cursor, from[i]);
}

if (!bound) {
String text = cursor.getString(from[i]);
if (text == null) {
text = "";
}

if (v instanceof TextView) {
setViewText((TextView) v, text);
} else if (v instanceof ImageView) {
setViewImage((ImageView) v, text);
} else {
throw new IllegalStateException(v.getClass().getName() + " is not a " +
" view that can be bounds by this SimpleCursorAdapter");
}
}
}
}
}

 

可以看到,首先檢查類中的私有域mViewBinder是否為null(默認為null,可通過setViewBinder)設置,為不為null則通過binder.setViewValue(v, cursor, from[i]); 進行綁定,這個函數若返回true則綁定成功,若返回false則通過SimpleCursorAdapter的規則綁定,判斷相應的View是否為TextView或ImageView,若是則綁定,否則拋出異常。

由些可以看到,我們可以自定義一個類實現SimpleCursorAdapter.ViewBinder,然后通過setViewBinder來改變bindView的結果。

SimpleAdapter

一個BaseAdapter的實現類,用於綁定數據到一個XML定義的視圖中。數據類型為ArrayList<Map<String, ?>>。

SimpleAdapter也實現了Filter接口用於數據的過濾,過濾方法類似ArrayAdapter,只是其數據類型為Map<String,?>,要判斷Map中的每一項,若任意一頂符合要求就保留。

SimpleAdapter也是通過bindView函數進行數據的綁定,同SimpleCursorAdapter一樣,SimpleAdapter也定義了一個相同的內部接口ViewBinder,在bindView中,首先判斷是否通過setViewBinder設置了ViewBinder,若設置了則調用其setViewValue進行數據綁定,如果沒有設置其setViewValue返回了false,則進行下面的處理:依次判斷View是否為Checkable,TextView,ImageView並進行相應的處理,可見默認情況下SimpleAdapter也是處理TextView與ImageView,當然可以setViewBinder。

WrapperListAdapter

繼承自ListAdapder的接口,所以也是一個ListAdapter,同時里面嵌入了另一個ListAdapter,只定義了一個函數用於取得嵌入的ListAdapter

public ListAdapter getWrappedAdapter();

HeaderViewListAdapter

繼承自WrapperListAdapter,當你使用的ListView有頁首(Header Views)或頁尾(Footer Views)時使用。此類被設計用來作為一個基類,一般不需要使用。

類中定義了兩個ArrayList用於保存頁首和頁尾

ArrayList<ListView.FixedViewInfo> mHeaderViewInfos; 
ArrayList<ListView.FixedViewInfo> mFooterViewInfos;
這兩個域不為null,在構造函數中判斷,如果給這兩個域賦值的參數為null,則將他們賦於值 HeaderViewListAdapter.EMPTY_INFO_LIST,其定義為
 
static final ArrayList<ListView.FixedViewInfo> EMPTY_INFO_LIST = 
new ArrayList<ListView.FixedViewInfo>();
其中ListView.FixedViewInfo的定義為
public class FixedViewInfo { 
public View view;ListAdapter#getItem(int)}.
public Object data;
public boolean isSelectable;
}
該類重定義了getCount,areAllItemsEnabled等函數,把mHeaderViewInfos,mFooterViewInfos同時包括在內。而hasStableIds,getItemViewType與getViewTypeCount只考慮了其內嵌的ListAdapter

本篇完結。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM