1. 程式人生 > Android入門教學 >Android 介面卡 Adapter

Android 介面卡 Adapter

本節將會引入一個全新的概念——介面卡,這個名字很形象,和電源介面卡的功能類似,從程式設計的角度出發,它可以將不同型別、不同結構的資料適配到一起。
在 Android 中,介面卡是 UI 元件和資料之間的橋樑,它幫助我們將資料填充到 UI 元件當中,實現了一個典型的 MVC 模式。我們可以分別編寫獨立的 UI 樣式和資料模型,至於資料如何與 UI 元件繫結都由 Adapter 幫我們完成,這樣的好處就是做到 UI 和資料的解耦。Android 系統為我們提供了多種 Adapter,今天就來介紹幾種常見同場景下 Adapter 的基本用法。

1. 為什麼要用 Adapter

我們首先看看 Android 為什麼要引入 Adapter,也就是使用 Adapter 有哪些好處?
在 Android 中Adapter 通常是搭配列表控制元件使用,我們先看看在沒有學習 Adapter 的時候,如何實現一個列表樣式,我們可能需要以下幾步:

  1. 建立一個 ScrollView(上一節剛學到的,不熟悉的可以參照 22 節);
  2. 在 ScrollView 中放置多個 View / ViewGroup,比如 TextView;
  3. 獲取每個 TextView 例項,根據業務需求為 TextView 設定 Text;
  4. 編寫額外程式碼管理所有的 TextView,並且需要分辨點選事件發生在第幾行從而定位到相應的 TextView,從而相應列表的點選事件。

讀到這裡,腦海裡已經有實現思路了嗎?即使你能捋清思路,程式碼也很難寫的優雅,因為編寫 TextView 樣式的這些 UI 程式碼一定會和 TextView 內容的資料程式碼耦合在一起,這樣如何 UI 樣式一變,資料也需要做很大的調整,後期的維護成本是相當高的。最好的辦法就是能夠有一套邏輯專門去管理資料和 UI 程式碼的繫結關係

,用它來將 UI、Data 隔離開,提高程式碼的簡潔性和可維護性。
我們結合一張圖來理解一下 Adapter:

Adapter in Android

電源介面卡將電器和電源介面適配到一起,好處是可以讓手機等電子產品及家用電器廠商在生產過程中完全不需要考慮使用者電源介面的型別,可以是 220V 交流電、也可以是 USB 介面,適配工作只需要交給相應的 Adapter 就可以完成。而 Android 介面卡是將資料和 UI 適配到一起,好處同樣也是我們在做 UI 的時候,完全不用考慮未來填充的資料是什麼樣的,只需要針對不同的資料型別提供一個 Adapter 即可。

如果你覺得上面的描述都太抽象,後面可以通過幾個簡單的例子來直觀感受一下 Adapter 的用法。

2. Adapter 的型別

就像電源介面卡需要根據不同的電源介面型別提供不同的介面卡一樣,Android 中我們需要根據不同資料型別提供不同的 Adapter,系統已經為我們實現了幾種 Adapter:

  1. BaseAdapter:
    所有 Adapter 的基類,通常我們需要實現自定義 Adapter 時,需要實現此抽象類,在實際開發中使用的最多的型別。
  2. ArrayAdapter:
    適用於一個單項列表,並且資料可以以資料形式存放的場景。
  3. SimpleAdapter:
    適用於一個列表項中有多個數據的場景,它可以將一個 map 裡的資料對映到 xml 佈局檔案中的各個控制元件上。
  4. SimpleCursorAdapter:
    針對資料庫使用的 Adapter,使用場景很少。

3. 常見 Adapter 的用法

其實最常用的是 BaseAdapter,在實際開發中稍微複雜一點的列表都需要通過繼承 BaseAdapter 來編寫一個自定義的 Adapter 。大多數場景是結合 ListView / GridView 來完成,所以 BaseAdapter 的具體用法我們會放到後面 ListView / GridView 的相關章節做詳細介紹,這裡主要是讓大家對 Adapter 的概念有個基本認識即可。

3.1 ArrayAdapter 的用法

ArrayAdpater 的用法非常簡單,如上一小節所說,它適合列表是單項列表並且資料可以存在一個數據當中的場景。首先我們建立佈局檔案,裡面只需要存放一個 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" />

其中有兩個屬性大家可能比較陌生:

 android:divider="#000"
 android:dividerHeight="2dp"

這兩個屬性是用來設定列表項之間的分割線樣式的,詳細的會在 ListView 章節進行介紹。然後還需要編寫列表中每個列表項的佈局樣式,我們只需要一個 TextView 來顯示文字,而文字的內容就是陣列的資料,列表項佈局程式碼 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>

一個我們非常熟悉的 TextView,然後就可以在 Java 程式碼中通過 ArrayAdapter進行資料 / 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);
    }

}

我們在 OnCreate() 中獲取ListView物件,然後建立 ArrayAdapter,傳入列表項的佈局檔案 ID、需要顯示內容的 TextView 控制元件 ID 以及陣列形式的資料。最後通過 setAdapter 完成資料及 UI 的繫結,這樣系統就會幫我們完成適配工作,效果如下:

ArrayAdapter

我們寫在陣列中的資料就會按順序填充到列表中了。

3.2 SimpleAdapter 的用法

SimpleAdapter 相比 ArrayAdapter 會更豐富一點,主要體現在 ArrayAdapter 只能適用於列表中只有一項資料(上一小節中的 TextView)的場景,而如果列表項由多個數據組成,比如文字配圖片的形式 ArrayAdapter 就有些力不從心,這時候就需要用到 SimpleAdapter 了。
整個 Activity 的佈局檔案依舊不變,只需要放置一個 ListView 即可。我們在之前的list_view.xml中增加一個 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>

從上面的佈局檔案可以看出,我們現在的列表項由兩個部分組成:一個圖片和一個文字。接著修改 Java 程式碼,主要是資料格式的變換,現在資料陣列需要包含圖片資源和文字內容兩個部分,如下:

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 = {"蘋果", "梨", "香蕉", "桃子", "西瓜", "荔枝", "橘子"};
    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);

        // 將水果圖片和水果名稱整合到一個map當中,最後將所有的水果都存放到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();
            }
        });
    }
}

在這段例子中,我們使用兩個陣列分別儲存水果名稱及水果圖片,然後再將每個水果的名稱和圖片存入一個 map,接著把所有的水果 map 都整合到一個 ArrayList 當中,最後建立 SimpleAdapter,這一步也是最關鍵的。我們來單獨看看 SimpleAdapter 的建立語句:

SimpleAdapter simpleAdapter = new SimpleAdapter(this, arrayList, R.layout.list_view, from, to);

SimpleAdapter 構造器引數比較多,我們來仔細分析分析。傳入構造器的第二個引數是資料來源,也就是存放所有水果 map 的 ArrayList 物件;傳入的第三個引數是列表項的佈局檔案,即 list_view.xml;第四個引數是一個字串陣列,表示水果 map 中的 key,也就是水果名和水果圖片的 key,用來與具體的 UI 控制元件對應;最後一個引數是一個整形陣列,用來與第四個引數匹配,告訴系統 map 中的哪些資料需要顯示到哪個 View 上。這樣一來,就完成了列表、列表項、資料的對應關係,接著直接用setAdapter完成適配,最後通過 ListView 的setOnItemClickListener為每個列表項新增點選事件(具體使用方法會在 ListView 章節詳細介紹),效果如下:

SimpleAdapter

4 小結

本節介紹了一個比較新鮮的概念——介面卡,大家初期理解它可以當成電源介面卡來理解就好。然後介紹了幾種常用的使用方法,系統也為我們提供了幾種封裝好的 Adapter 可以應付一些簡單的場景。但是在大家實際的開發過程中能夠直接使用系統提供的 Adapter 的場景比較少,大多數情況還是要繼承 BaseAdapter 來自己實現一套 Adapter,這個內容會在 ListView / GridView 相關章節做具體的介紹。另外,大家可以思考一下本章節的例子如果使用 ScrollView 要怎麼實現,優劣勢在哪裡?