編輯:關於Android編程
Adapter相當於一個數據源,可以給AdapterView提供數據,並根據數據創建對應的UI,可以通過調用AdapterView的setAdapter方法使得AdapterView將Adapter作為數據源。
常見的AdapterView的子類有ListView、GridView、Spinner和ExpandableListView等。
本文就以ListView為例講解各種常見的Adapter的使用。
以下是Adapter相關類的關系圖:
Adapter源碼鏈接如下:
https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/Adapter.java
Adapter接口定義了如下方法:
public abstract void registerDataSetObserver (DataSetObserver observer)
Adapter表示一個數據源,這個數據源是有可能發生變化的,比如增加了數據、刪除了數據、修改了數據,當數據發生變化的時候,它要通知相應的AdapterView做出相應的改變。為了實現這個功能,Adapter使用了觀察者模式,Adapter本身相當於被觀察的對象,AdapterView相當於觀察者,通過調用registerDataSetObserver方法,給Adapter注冊觀察者。
public abstract void unregisterDataSetObserver (DataSetObserver observer)
通過調用unregisterDataSetObserver方法,反注冊觀察者。
public abstract int getCount ()
返回Adapter中數據的數量。
public abstract Object getItem (int position)
Adapter中的數據類似於數組,裡面每一項就是對應一條數據,每條數據都有一個索引位置,即position,根據position可以獲取Adapter中對應的數據項。
public abstract long getItemId (int position)
獲取指定position數據項的id,通常情況下會將position作為id。在Adapter中,相對來說,position使用比id使用頻率更高。
public abstract boolean hasStableIds ()
hasStableIds表示當數據源發生了變化的時候,原有數據項的id會不會發生變化,如果返回true表示Id不變,返回false表示可能會變化。Android所提供的Adapter的子類(包括直接子類和間接子類)的hasStableIds方法都返回false。
public abstract View getView (int position, View convertView, ViewGroup parent)
getView是Adapter中一個很重要的方法,該方法會根據數據項的索引為AdapterView創建對應的UI項。
ListAdapter接口繼承自Adapter接口,ListAdapter源碼鏈接如下:
https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/ListAdapter.java
ListAdapter可以作為AbsListView的數據源,AbsListView的子類有ListView、GridView和ExpandableListView。
ListAdapter相比Adapter新增了areAllItemsEnabled和isEnabled兩個方法。
SpinnerAdapter接口繼承自Adapter接口,SpinnerAdapter源碼鏈接如下:
https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/SpinnerAdapter.java
SpinnerAdapter可以作為AbsSpinner的數據源,AbsSpinner的子類有Gallery, Spinner和AppCompatSpinner。
相比Adapter,SpinnerAdapter中新增了getDropDownView方法,該方法與Adapter接口中定義的getView方法類似,該方法主要是供AbsSpinner調用,用於生成Spinner下拉彈出區域的UI。在SpinnerAdapter的子類BaseAdapter中,getDropDownView方法默認直接調用了getView方法。
ArrayAdapter和SimpleAdapter都重寫了getDropDownView方法,這兩個類中的getDropDownView方法與其getView的方法都調用了createViewFromResource方法,所以這兩個類中方法getView與方法getDropDownView代碼基本一致。
CursorAdapter也重寫了getView與getDropDownView方法,雖然這兩個方法沒有使用公共代碼,但是這兩個方法代碼邏輯一致。
綜上,我們可知當我們在覆寫getDropDownView方法時,應該盡量使其與getView的代碼邏輯一致。
BaseAdapter是抽象類,其實現了ListAdapter接口和SpinnerAdapter接口,其源碼鏈接如下:
https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/BaseAdapter.java
BaseAdapter主要實現了以下功能:
- BaseAdapter實現了觀察者模式,Adapter接口定義了方法registerDataSetObserver和unregisterDataSetObserver,BaseAdapter中維護了一個DataSetObservable類型的變量mDataSetObservable,並實現了方法registerDataSetObserver和unregisterDataSetObserver。
BaseAdapter重寫了getDropDownView方法,其調用了getView方法,如下所示:
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
覆寫其他一些方法,設置了默認值,比如覆寫hasStableIds方法,使其默認返回false
類ArrayAdapter繼承並實現了BaseAdapter抽象類,其源碼鏈接如下:
https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/ArrayAdapter.java
ArrayAdapter是最簡單的Adapter,AdapterView會將ArrayAdapter中的數據項調用toString()方法,作為文本顯示出來。
ArrayAdapter的使用代碼如下所示:
package com.ispring.adapter;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView)findViewById(R.id.listView);
String[] values = {"iPhone","小米","三星","華為","中興","聯想","黑莓","魅族"};
//List list = Arrays.asList(values);
//Arrays.asList(values)返回的是一個只讀的List,不能進行add和remove
//new ArrayList<>(Arrays.asList(values))則是一個可寫的List,可以進行add和remove
List list = new ArrayList<>(Arrays.asList(values));
final ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, list);
listView.setAdapter(adapter);
//單擊item之後,刪除對應的item
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
String item = adapter.getItem(position);
adapter.remove(item);
Toast.makeText(MainActivity.this, item, Toast.LENGTH_SHORT).show();
}
});
}
}
界面如下所示:
我們綁定了ListView的OnItemClickListener事件,當單擊其中一項的時候就會通過ArrayAdapter的remove()方法刪除對應項。
ArrayAdapter有以下幾個構造函數:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
上面幾個構造函數其實最後都調用了最後構造函數,有幾點需要說明一下: 上面的構造函數中涉及到了resource和textViewResourceId這兩個參數,resource肯定是數據項對應的UI的layout文件,那textViewResourceId是什麼呢?我們之前提到,ArrayAdapter是用來讓AdapterView顯示文本項的,所以其數據項需要顯示在一個TextView上。如果resource本身就是以 ArrayAdapter的構造函數既可以接收List作為數據源,又可以接收一個數組作為數據源,如果傳入的是一個數組,那麼在構造函數中也會通過Arrays.asList()將數組轉換成list,最終用mObjects存儲該list。 ArrayAdapter重寫了getCount、getItem、getItemId、getView與getDropDownView,其中getView與getDropDownView這兩個方法都調用了createViewFromResource方法,其源碼如下所示: 該方法的主要邏輯是查找TextView,並給TextView設置文本值,其邏輯如下所示: 如果在ArrayAdapter的構造函數中沒有設置mFieldId的值或者mFieldId的值為0,那麼就將整個View作為TextView。 如果在ArrayAdapter的構造函數中設置了mFieldId的值,那麼就會調用(TextView)view.findViewById(mFieldId)查找TextView。 ArrayAdapter還增加了add、addAll、insert、remove、clear等方法,當調用了這些方法時,數據會發生變化,ArrayAdapter就會在這些方法裡面調用notifyDataSetChanged方法,比如remove源碼如下所示: 如果我們在一個for循環中多次調用add方法添加數據,那麼默認會多次觸發notifyDataSetChanged方法的執行,由於每次notifyDataSetChanged方法執行後,AdapterView都會重新渲染UI,所以多次觸發notifyDataSetChanged方法執行會導致效率比較低。最好的辦法是在所有數據變化完成後,我們自己調用notifyDataSetChanged方法。 為此,ArrayAdapter內部提供了一個boolean類型的變量mNotifyOnChange,默認值為true,每次調用add、addAll、insert、remove、clear等方法,都會先判斷mNotifyOnChange的值,只有當mNotifyOnChange為true,才會執行notifyDataSetChanged方法。我們可以通過調用setNotifyOnChange方法將mNotifyOnChange設置為false,然後在for循環中多次調用add方法,這樣不會觸發notifyDataSetChanged方法,在執行完for循環之後,我們自己再調用notifyDataSetChanged方法。 還有一點需要說明的是,如果我們將一個數組作為數據源傳遞給ArrayAdapter,那麼當調用ArrayAdapter的add、addAll、insert、remove、clear等寫操作的方法時就會拋出異常java.lang.UnsupportedOperationException。這是為什麼呢? 我們之前在上面提到,如果在構造函數中傳入數組,會調用Arrays.asList()將數組轉換成List,並賦值給字段mObjects存儲。但是Arrays.asList()返回的List是只讀的,不能夠進行add、remove等寫操作,Arrays.asList()返回的List是其實是一個java.util.AbstractList對象,其add、remove方法的默認實現就是拋出異常。 同理,如果我們傳入了一個只讀的List對象給ArrayAdapter的構造函數,那麼在調用add、addAll、insert、remove、clear等寫操作的方法時也會拋出異常。 在上面的例子中,我們執行了一下方法: 我們將Arrays.asList(values)得到的只讀的list作為參數實例化了一個ArrayList,新得到的ArrayList是可寫的,所以我們將它作為參數傳遞給ArrayAdapter之後,可以正常調用其remove()方法。 所以,在使用ArrayAdapter的時候,最好傳給構造函數一個可寫的List,這樣才能正常使用ArrayAdapter的寫操作方法。 類SimpleAdapter繼承並實現了BaseAdapter抽象類,其源碼鏈接如下: SimpleAdaper的作用是方便地將數據與XML文件定義的各種View綁定起來,從而創建復雜的UI。 SimpleAdapter的使用代碼如下所示:public ArrayAdapter(Context context, @LayoutRes int resource) {
this(context, resource, 0, new ArrayList
作為根結點,那麼textViewResourceId設置為0即可。如果resource所對應的layout不是以
作為根結點,那麼該layout中也必須要有一個
節點,textViewResourceId即時該
的id。字段mResource存儲了resource的值,字段mFieldId存儲了textViewResourceId。
private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
ViewGroup parent, int resource) {
View view;
TextView text;
if (convertView == null) {
view = inflater.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;
}
public void remove(T object) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.remove(object);
} else {
mObjects.remove(object);
}
}
if (mNotifyOnChange) notifyDataSetChanged();
}
List
SimpleAdapter類
https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/SimpleAdapter.java
package com.ispring.adapter;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView)findViewById(R.id.listView);
final String[] names = {"Windows","Mac OS","Linux","Android","Chrome OS"};
final String[] descriptions = {
"Windows是微軟公司的操作系統",
"Mac OS是蘋果公司的操作系統",
"Linux是開源免費操作系統",
"Android是Google公司的智能手機操作系統",
"Chrome OS是Google公司的Web操作系統"
};
final int[] icons = {
R.drawable.windows,
R.drawable.mac,
R.drawable.linux,
R.drawable.android,
R.drawable.chrome
};
List
本文主要為大家分享了Android實現搜索功能,並且可以實時顯示搜索的歷史記錄,根據輸入的內容去模糊查詢,供大家參考,界面圖如下。 本案例實現起來也非常的簡單,
之前自認為對於Android的事件分發機制還算比較了解,直到前一陣偶然跟人探討該問題,才發現自己以前的理解有誤,慚愧之余遂決定研習源碼,徹底弄明白Android的事件分發
本文力求用最簡單的方式實現這樣的一個效果,並輔以詳細的文字說明。老規矩,先看圖:一個點餐界面,6種菜品,意味著6個按鈕,點擊‘開始點餐’ 幕布上升
話不多說,我們先來看看效果:Hi前輩搜索預覽這一張是《Hi前輩》的搜索預覽圖,你可以在這裡下載這個APP查看更多效果:http://www.wandoujia.com/a