Android 適配器 Adapter
本節(jié)將會(huì)引入一個(gè)全新的概念——適配器,這個(gè)名字很形象,和電源適配器的功能類似,從程序設(shè)計(jì)的角度出發(fā),它可以將不同類型、不同結(jié)構(gòu)的數(shù)據(jù)適配到一起。
在 Android 中,適配器是 UI 組件和數(shù)據(jù)之間的橋梁,它幫助我們將數(shù)據(jù)填充到 UI 組件當(dāng)中,實(shí)現(xiàn)了一個(gè)典型的 MVC 模式。我們可以分別編寫(xiě)?yīng)毩⒌?UI 樣式和數(shù)據(jù)模型,至于數(shù)據(jù)如何與 UI 組件綁定都由 Adapter 幫我們完成,這樣的好處就是做到 UI 和數(shù)據(jù)的解耦。Android 系統(tǒng)為我們提供了多種 Adapter,今天就來(lái)介紹幾種常見(jiàn)同場(chǎng)景下 Adapter 的基本用法。
1. 為什么要用 Adapter
我們首先看看 Android 為什么要引入 Adapter,也就是使用 Adapter 有哪些好處?
在 Android 中Adapter 通常是搭配列表控件使用,我們先看看在沒(méi)有學(xué)習(xí) Adapter 的時(shí)候,如何實(shí)現(xiàn)一個(gè)列表樣式,我們可能需要以下幾步:
- 創(chuàng)建一個(gè) ScrollView(上一節(jié)剛學(xué)到的,不熟悉的可以參照 22 節(jié));
- 在 ScrollView 中放置多個(gè) View / ViewGroup,比如 TextView;
- 獲取每個(gè) TextView 實(shí)例,根據(jù)業(yè)務(wù)需求為 TextView 設(shè)置 Text;
- 編寫(xiě)額外代碼管理所有的 TextView,并且需要分辨點(diǎn)擊事件發(fā)生在第幾行從而定位到相應(yīng)的 TextView,從而相應(yīng)列表的點(diǎn)擊事件。
讀到這里,腦海里已經(jīng)有實(shí)現(xiàn)思路了嗎?即使你能捋清思路,代碼也很難寫(xiě)的優(yōu)雅,因?yàn)榫帉?xiě) TextView 樣式的這些 UI 代碼一定會(huì)和 TextView 內(nèi)容的數(shù)據(jù)代碼耦合在一起,這樣如何 UI 樣式一變,數(shù)據(jù)也需要做很大的調(diào)整,后期的維護(hù)成本是相當(dāng)高的。最好的辦法就是能夠有一套邏輯專門(mén)去管理數(shù)據(jù)和 UI 代碼的綁定關(guān)系,用它來(lái)將 UI、Data 隔離開(kāi),提高代碼的簡(jiǎn)潔性和可維護(hù)性。
我們結(jié)合一張圖來(lái)理解一下 Adapter:
電源適配器將電器和電源接口適配到一起,好處是可以讓手機(jī)等電子產(chǎn)品及家用電器廠商在生產(chǎn)過(guò)程中完全不需要考慮用戶電源接口的類型,可以是 220V 交流電、也可以是 USB 接口,適配工作只需要交給相應(yīng)的 Adapter 就可以完成。而 Android 適配器是將數(shù)據(jù)和 UI 適配到一起,好處同樣也是我們?cè)谧?UI 的時(shí)候,完全不用考慮未來(lái)填充的數(shù)據(jù)是什么樣的,只需要針對(duì)不同的數(shù)據(jù)類型提供一個(gè) Adapter 即可。
如果你覺(jué)得上面的描述都太抽象,后面可以通過(guò)幾個(gè)簡(jiǎn)單的例子來(lái)直觀感受一下 Adapter 的用法。
2. Adapter 的類型
就像電源適配器需要根據(jù)不同的電源接口類型提供不同的適配器一樣,Android 中我們需要根據(jù)不同數(shù)據(jù)類型提供不同的 Adapter,系統(tǒng)已經(jīng)為我們實(shí)現(xiàn)了幾種 Adapter:
- BaseAdapter:
所有 Adapter 的基類,通常我們需要實(shí)現(xiàn)自定義 Adapter 時(shí),需要實(shí)現(xiàn)此抽象類,在實(shí)際開(kāi)發(fā)中使用的最多的類型。 - ArrayAdapter:
適用于一個(gè)單項(xiàng)列表,并且數(shù)據(jù)可以以數(shù)據(jù)形式存放的場(chǎng)景。 - SimpleAdapter:
適用于一個(gè)列表項(xiàng)中有多個(gè)數(shù)據(jù)的場(chǎng)景,它可以將一個(gè) map 里的數(shù)據(jù)映射到 xml 布局文件中的各個(gè)控件上。 - SimpleCursorAdapter:
針對(duì)數(shù)據(jù)庫(kù)使用的 Adapter,使用場(chǎng)景很少。
3. 常見(jiàn) Adapter 的用法
其實(shí)最常用的是 BaseAdapter,在實(shí)際開(kāi)發(fā)中稍微復(fù)雜一點(diǎn)的列表都需要通過(guò)繼承 BaseAdapter 來(lái)編寫(xiě)一個(gè)自定義的 Adapter 。大多數(shù)場(chǎng)景是結(jié)合 ListView / GridView 來(lái)完成,所以 BaseAdapter 的具體用法我們會(huì)放到后面 ListView / GridView 的相關(guān)章節(jié)做詳細(xì)介紹,這里主要是讓大家對(duì) Adapter 的概念有個(gè)基本認(rèn)識(shí)即可。
3.1 ArrayAdapter 的用法
ArrayAdpater 的用法非常簡(jiǎn)單,如上一小節(jié)所說(shuō),它適合列表是單項(xiàng)列表并且數(shù)據(jù)可以存在一個(gè)數(shù)據(jù)當(dāng)中的場(chǎng)景。首先我們創(chuàng)建布局文件,里面只需要存放一個(gè) ListView 控件即可:
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/simpleListView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#000"
android:dividerHeight="2dp" />
其中有兩個(gè)屬性大家可能比較陌生:
android:divider="#000"
android:dividerHeight="2dp"
這兩個(gè)屬性是用來(lái)設(shè)置列表項(xiàng)之間的分割線樣式的,詳細(xì)的會(huì)在 ListView 章節(jié)進(jìn)行介紹。然后還需要編寫(xiě)列表中每個(gè)列表項(xiàng)的布局樣式,我們只需要一個(gè) TextView 來(lái)顯示文本,而文本的內(nèi)容就是數(shù)組的數(shù)據(jù),列表項(xiàng)布局代碼 list_view.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="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="30dp"
android:textColor="#000" />
</LinearLayout>
一個(gè)我們非常熟悉的 TextView,然后就可以在 Java 代碼中通過(guò) ArrayAdapter進(jìn)行數(shù)據(jù) / UI 的綁定了,Java 代碼如下:
package com.emercy.myapplication;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.app.Activity;
public class MainActivity extends Activity {
ListView mList;
String mNums[] = {"TextView", "EditText", "Button", "ImageButton", "RadioButton", "ToggleButton",
"ImageView", "ProgressBar", "SeekBar", "RatingBar", "ScrollView", "Adapter"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mList = findViewById(R.id.simpleListView);
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(this, R.layout.list_view, R.id.textView, mNums);
mList.setAdapter(arrayAdapter);
}
}
我們?cè)?OnCreate() 中獲取ListView對(duì)象,然后創(chuàng)建 ArrayAdapter,傳入列表項(xiàng)的布局文件 ID、需要顯示內(nèi)容的 TextView 控件 ID 以及數(shù)組形式的數(shù)據(jù)。最后通過(guò) setAdapter 完成數(shù)據(jù)及 UI 的綁定,這樣系統(tǒng)就會(huì)幫我們完成適配工作,效果如下:
我們寫(xiě)在數(shù)組中的數(shù)據(jù)就會(huì)按順序填充到列表中了。
3.2 SimpleAdapter 的用法
SimpleAdapter 相比 ArrayAdapter 會(huì)更豐富一點(diǎn),主要體現(xiàn)在 ArrayAdapter 只能適用于列表中只有一項(xiàng)數(shù)據(jù)(上一小節(jié)中的 TextView)的場(chǎng)景,而如果列表項(xiàng)由多個(gè)數(shù)據(jù)組成,比如文字配圖片的形式 ArrayAdapter 就有些力不從心,這時(shí)候就需要用到 SimpleAdapter 了。
整個(gè) Activity 的布局文件依舊不變,只需要放置一個(gè) ListView 即可。我們?cè)谥暗?code>list_view.xml中增加一個(gè) ImageView,如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imageView"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"
android:padding="5dp" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:padding="30dp"
android:textColor="#000" />
</RelativeLayout>
從上面的布局文件可以看出,我們現(xiàn)在的列表項(xiàng)由兩個(gè)部分組成:一個(gè)圖片和一個(gè)文本。接著修改 Java 代碼,主要是數(shù)據(jù)格式的變換,現(xiàn)在數(shù)據(jù)數(shù)組需要包含圖片資源和文本內(nèi)容兩個(gè)部分,如下:
package com.emercy.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashMap;
public class MainActivity extends Activity {
ListView mListView;
String[] mDataName = {"蘋(píng)果", "梨", "香蕉", "桃子", "西瓜", "荔枝", "橘子"};
int[] mDataImage = {R.drawable.apple, R.drawable.pear, R.drawable.banana, R.drawable.peach,
R.drawable.watermelon, R.drawable.lychee, R.drawable.orange, R.drawable.orange};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = findViewById(R.id.simpleListView);
// 將水果圖片和水果名稱整合到一個(gè)map當(dāng)中,最后將所有的水果都存放到ArrayList
ArrayList<HashMap<String, String>> arrayList = new ArrayList<>();
for (int i = 0; i < mDataName.length; i++) {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("name", mDataName[i]);
hashMap.put("image", mDataImage[i] + "");
arrayList.add(hashMap);
}
String[] from = {"name", "image"};
int[] to = {R.id.textView, R.id.imageView};
SimpleAdapter simpleAdapter = new SimpleAdapter(this, arrayList, R.layout.list_view, from, to);
mListView.setAdapter(simpleAdapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Toast.makeText(getApplicationContext(), mDataName[i], Toast.LENGTH_LONG).show();
}
});
}
}
在這段例子中,我們使用兩個(gè)數(shù)組分別保存水果名稱及水果圖片,然后再將每個(gè)水果的名稱和圖片存入一個(gè) map,接著把所有的水果 map 都整合到一個(gè) ArrayList 當(dāng)中,最后創(chuàng)建 SimpleAdapter,這一步也是最關(guān)鍵的。我們來(lái)單獨(dú)看看 SimpleAdapter 的創(chuàng)建語(yǔ)句:
SimpleAdapter simpleAdapter = new SimpleAdapter(this, arrayList, R.layout.list_view, from, to);
SimpleAdapter 構(gòu)造器參數(shù)比較多,我們來(lái)仔細(xì)分析分析。傳入構(gòu)造器的第二個(gè)參數(shù)是數(shù)據(jù)源,也就是存放所有水果 map 的 ArrayList 對(duì)象;傳入的第三個(gè)參數(shù)是列表項(xiàng)的布局文件,即 list_view.xml;第四個(gè)參數(shù)是一個(gè)字符串?dāng)?shù)組,表示水果 map 中的 key,也就是水果名和水果圖片的 key,用來(lái)與具體的 UI 控件對(duì)應(yīng);最后一個(gè)參數(shù)是一個(gè)整形數(shù)組,用來(lái)與第四個(gè)參數(shù)匹配,告訴系統(tǒng) map 中的哪些數(shù)據(jù)需要顯示到哪個(gè) View 上。這樣一來(lái),就完成了列表、列表項(xiàng)、數(shù)據(jù)的對(duì)應(yīng)關(guān)系,接著直接用setAdapter
完成適配,最后通過(guò) ListView 的setOnItemClickListener
為每個(gè)列表項(xiàng)添加點(diǎn)擊事件(具體使用方法會(huì)在 ListView 章節(jié)詳細(xì)介紹),效果如下:
4 小結(jié)
本節(jié)介紹了一個(gè)比較新鮮的概念——適配器,大家初期理解它可以當(dāng)成電源適配器來(lái)理解就好。然后介紹了幾種常用的使用方法,系統(tǒng)也為我們提供了幾種封裝好的 Adapter 可以應(yīng)付一些簡(jiǎn)單的場(chǎng)景。但是在大家實(shí)際的開(kāi)發(fā)過(guò)程中能夠直接使用系統(tǒng)提供的 Adapter 的場(chǎng)景比較少,大多數(shù)情況還是要繼承 BaseAdapter 來(lái)自己實(shí)現(xiàn)一套 Adapter,這個(gè)內(nèi)容會(huì)在 ListView / GridView 相關(guān)章節(jié)做具體的介紹。另外,大家可以思考一下本章節(jié)的例子如果使用 ScrollView 要怎么實(shí)現(xiàn),優(yōu)劣勢(shì)在哪里?