概述
繼續構建Miwok語言應用,理解適配器的使用,並自定義適配器。
目標
上次利用視圖回收機制在頁面中實現了數據的展示,但Miwok是一個用於學習語言的app ,因此需要提供兩種語言的參照,如同一個單詞需要展示 Miwok 和 English 兩種語言的。
比如在學習 Numbers 的頁面中每行需顯示該數字的 Miwok 和 English 版本,最終需要實現的樣子應該類似這樣:
實現步驟
問題分析
上次在實現一個頁面中顯示 1~n 的數字時使用到了 ArrayAdapter
,以及 ArrayList<String>
並讓每個數字都作爲一個列表項顯示在Android 爲我們提供的佈局 文件android.R.layout.simple_list_item_1
當中。 ArrayAdapter
幫助我們管理 ArrayList 中的 String 對象,
點開該佈局文件,會發現其實就只是一個 TextView
,其內容如下:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:minHeight="?android:attr/listPreferredItemHeightSmall" />
之前在創建 數據適配器 ArrayAdapter 實例的語句爲:
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, words);
ArrayAdapter 指定了泛型 String 表示這個適配器管理的數據爲String類型,同時我們傳入了一個 包含 String對象 的 列表數據 words
。
頁面中顯示的每個數字列表項,其實就是通過這個android.R.layout.simple_list_item_1
佈局而創建的實例,因此 ArrayAdapter 默認管理的視圖和對象爲 單個 TextView 和 String。
而最終想要實現的效果爲 每個列表項中 包含 Miwok 和 English 兩個語言版本,爲達到這一效果,就需要自定義列表項的佈局文件,並自定義適配器 ,再讓這個適配器管理自定義的列表項佈局視圖。
列表項佈局
那麼首先來定義列表項的佈局文件,最終效果中的列表項分爲兩部分:Miwok 和 English ,那麼可以考慮使用垂直佈局的 LinearLayout
並嵌套 2個 TextView
來分別顯示兩種語言的單詞版本。
創建列表項的佈局文件(list_item.xml)如下:
使用 ViewGroup LinearLayout
作爲列表項佈局的基本視圖,android:orientation="vertical"
使佈局中的 兩個 TextView
垂直排列,同時設置 TextView 的 layout_height
爲 0dp
、layout_weight
爲 1
讓其高度均值分佈。
數據來源
我們需要在每個列表項顯示 2個 語言的字符串,因此可以考慮使用對象來封裝每個列表項中需要顯示的數據,可以更好的控制數據。
定義一個 Word
類型,幷包含兩個語言版本 的字符串 的屬性 、一個 初始化 該類的構造函數 和 兩個屬性的 Getter
方法。
Word.java
:
public class Word {
/**
* Default(English) translation for the word
*/
private String defaultTranslation;
/**
* Miwok translation for the word
*/
private String miwokTranslation;
/**
* Create a new Word object.
*
* @param defaultTranslation is the word in a language that the user is already familiar with
* (such as English)
* @param miwokTranslation is the word in the Miwok language
*/
public Word(String defaultTranslation, String miwokTranslation) {
this.defaultTranslation = defaultTranslation;
this.miwokTranslation = miwokTranslation;
}
}
之後就可以 將一個包含 Word
對象的數組列表交給 適配器來管理。
自定義適配器
Android 提供的 ArrayAdapter 只能管理 單個 TextView 和 String 對象,而我們自己的列表項需要顯示兩條數據(一個Word對象),或許可以對現有的 ArrayAdapter 進行擴展,讓它管理 自定義的 Word 對象 和 自定義的列表項佈局 ,這就需要自定義適配器(繼承基適配器)。
自定義的適配器需要繼承 ArrayAdapter
接收一個泛型 Word
表示管理一個包含 Word
的數據列表。
自定義適配器 WordAdapter.java
如下:
public class WordAdapter extends ArrayAdapter<Word> {
/**
* This is our own custom constructor (it doesn't mirror a superclass constructor).
* The context is used to inflate the layout file, and the list is the data we want
* to populate into the lists.
*
* @param context The current context. Used to inflate the layout file.
* @param words A List of Word objects to display in a list
*/
public WordAdapter(Activity context, ArrayList<Word> words) {
// Here, we initialize the ArrayAdapter's internal storage for the context and the list.
// the second argument is used when the ArrayAdapter is populating a single TextView.
// Because this is a custom adapter for two TextViews and an ImageView, the adapter is not
// going to use this second argument, so it can be any value. Here, we used 0.
super(context, 0, words);
}
}
自定義適配器 WordAdapter
繼承自 ArrayAdapter
,並指定泛型 Word
表示管理一組 Word
對象。
還有一個接收兩個參數(上下文、數據來源)的構造函數,在構造函數中只有一行語句,該語句將上下文 context 和數量來源 words,傳遞給了 父類 ArrayAdapter
的構造函數,並指定了 列表佈局資源的 id 爲 0
,這個 0
是一個不存在的佈局資源 ID 相當於 沒有指定要使用的視圖資源。
因爲 WordAdapter
繼承自 ArrayAdapter
,即 WordAdapter
默認也將默認管理單個 Textview
和 String
對象, 所以 在調用父類 ArrayAdapter
的構造函數時傳入 佈局資源ID爲 0
。
另外 還是需要讓自定義的這個適配器 WordAdapter
將 Word
對象和 列表項佈局資源 聯繫起來,實現 適配器的基礎功能 視圖回收。
經查閱資料,發現 ArrayAdapter
有一個 getView()
方法,用於分配要顯示的 視圖,那麼重寫這個方法讓其 將 一個 Word
對象 和 自定義的佈局資源 作爲要 顯示的 列表項視圖 交給 Numbers頁面根視圖 ListView
並顯示即可:
在 WordAdapter
中重寫 getView
方法:
/**
* Provides a view for an AdapterView (ListView, GridView, etc.)
*
* @param position The position in the list of data that should be displayed in the
* list item view.
* @param convertView The recycled view to populate.
* @param parent The parent ViewGroup that is used for inflation.
* @return The View for the position in the AdapterView.
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Check if the existing view is being reused, otherwise inflate the view
View itemView = convertView;
if (itemView == null) {
itemView = LayoutInflater.from(getContext()).inflate(
R.layout.list_item, parent, false);
}
// Get the {@link Word} object located at this position in the list
Word currentWord = getItem(position);
// Find the TextView in the list_item.xml layout with the ID miwok_text
TextView miwokTextView = (TextView) itemView.findViewById(R.id.miwok_text);
// Get the miwok text from the current Word object and
// set this text on the miwok TextView
miwokTextView.setText(currentWord.getMiwokTranslation());
// Find the TextView in the list_item.xml layout with the ID default_text
TextView defaultTextView = (TextView) itemView.findViewById(R.id.default_text);
// Get the version number from the current Word object and
// set this text on the default(english) TextView
defaultTextView.setText(currentWord.getDefaultTranslation());
// Return the whole list item layout (containing 2 TextViews)
// so that it can be shown in the ListView
return itemView;
}
重寫的 getView
方法接收 3個參數 position
(將在屏幕中顯示的對象在數組列表中所處的索引位置)、convertView
(被回收的列表項視圖對象)、parent
(被回收視圖的父視圖,即根視圖 ListView
)。
重寫的 getView
方法基本做了三件事情:
- 13 ~ 17 行,得到被回收的視圖
itemView
,如果該視圖爲 空對象則根據列表項的佈局資源list_item
創建一個(被回收的視圖存在空的情況是因爲 在Numbers 的頁面剛加載時,並沒有多餘 的視圖用於 回收,這是被回收的視圖對象itemView
就是一個空對象null
)。 - 20 ~ 32 行,調用繼承自
ArrayAdapter
的getItem
方法 得到 下一個 將要被顯示的Word
對象,然後通過預先在 列表項佈局資源中對於的視圖 ID 將 這個對象的屬性 分別設置爲被回收的視圖itemView
需要顯示的內容。 - 36 行,將這個 被賦予新內容的 列表項視圖
itemView
返回出去,即可將 新的Word
對象顯示在 頁面根視圖ListView
當中,重複利用了視圖資源 而不造成資源浪費。
顯示到頁面當中
列表項佈局、數據來源、自定義的適配器都有了,下面只需讓其將數據顯示到頁面即可,
在 Numbers 頁面的 onCreate
方法中更改 代碼如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_numbers);
// Create a array list of words
ArrayList<Word> words = new ArrayList<Word>();
words.add(new Word("one", "lutti"));
words.add(new Word("two", "otiiko"));
words.add(new Word("three", "tolookosu"));
words.add(new Word("four", "oyyisa"));
words.add(new Word("five", "massokka"));
words.add(new Word("six", "temmokka"));
words.add(new Word("seven", "kenekaku"));
words.add(new Word("eight", "kawinta"));
words.add(new Word("nine", "wo'e"));
words.add(new Word("ten", "na'aacha"));
// Create an {@link ArrayAdapter}, whose data source is a list of Strings. The
// adapter knows how to create layouts for each item in the list, using the
// simple_list_item_1.xml layout resource defined in the Android framework.
// This list item layout contains a single {@link TextView}, which the adapter will set to
// display a single word.
WordAdapter adapter = new WordAdapter(this, words);
// Find the {@link ListView} object in the view hierarchy of the {@link Activity}.
// There should be a {@link ListView} with the view ID called list, which is declared in the
// activity_numbers.xml layout file.
ListView listLayout = (ListView) findViewById(R.id.list);
// Make the {@link ListView} use the {@link ArrayAdapter} we created above, so that the
// {@link ListView} will display list items for each word in the list of words.
// Do this by calling the setAdapter method on the {@link ListView} object and pass in
// 1 argument, which is the {@link ArrayAdapter} with the variable name itemsAdapter.
listLayout.setAdapter(adapter);
}
和 ArrayAdapter
的使用基本相同:
- 7 ~ 17 行,準備數據來源
words
(一個包含Word
對象的數字列表) - 25 行,創建自定義適配器
WordAdapter
對象 adapter,傳入參數 context 上下文、words 數據來源(這裏沒有指定佈局資源是因爲在
WordAdapter的
getView` 方法中 已經實現了 自定義佈局資源和 Word 對象的適配) - 30 行,獲取用於顯示列表項的根視圖
ListView
對象。 - 36 行,爲根視圖
ListView
對象設置 適配器 爲自定義的WordAdapter
對象adpter
。
至此 使用自定義適配器在 Numbers 頁面中 每個列表項 顯示 兩條數據 的目標完成。
其他頁面
另外 3個 頁面 Family Members、Color 和 Phrases 的需求也 和 Numbers 頁面一樣(在頁面的每個列表項中顯示單詞 的 Miwok 和 English 兩個語言版本)。
這幾個也需要用到 同樣的 頁面佈局(根視圖 ListView)、列表項佈局(list_item.xml)和 自定義的適配器 WordAdapter
。
可以考慮讓每個頁面都 使用同一個頁面佈局(根視圖 ListView)文件,所以在 Numbers 頁面的 xml 佈局文件上 鼠標右擊 --> Refactor(重構) --> Rename(重命名)爲 word_list
,然後在 word_list.xml
佈局文件中 將 根視圖 ListView 的 tools:context
屬性和值刪除(這個屬性指定了 佈局文件作用與哪個頁面 Activity),最終 word_list.xml
內容如下:
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
然後 在 所有 頁面的 onCreate
方法中,重複之前的步驟(準備數據來源 --> 創建適配器 --> 獲取根視圖 --> 爲根視圖綁定適配器)。
注意: 每個頁面在加載時要使用 剛剛準備好的這個 word_list.xml
佈局資源。
比如 Colors 頁面的 onCreate
方法如下(第四行 指定了 頁面的佈局資源):
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.word_list);
// Create a array list of words
ArrayList<Word> words = new ArrayList<Word>();
words.add(new Word("red", "weṭeṭṭi"));
words.add(new Word("green", "chokokki"));
words.add(new Word("brown", "ṭakaakki"));
words.add(new Word("gray", "ṭopoppi"));
words.add(new Word("black", "kululli"));
words.add(new Word("white", "kelelli"));
words.add(new Word("dusty yellow", "ṭopiisə"));
words.add(new Word("mustard yellow", "chiwiiṭə"));
// Create an {@link ArrayAdapter}, whose data source is a list of Strings. The
// adapter knows how to create layouts for each item in the list, using the
// simple_list_item_1.xml layout resource defined in the Android framework.
// This list item layout contains a single {@link TextView}, which the adapter will set to
// display a single word.
WordAdapter adapter = new WordAdapter(this, words);
// Find the {@link ListView} object in the view hierarchy of the {@link Activity}.
// There should be a {@link ListView} with the view ID called list, which is declared in the
// activity_numbers.xml layout file.
ListView listLayout = (ListView) findViewById(R.id.list);
// Make the {@link ListView} use the {@link ArrayAdapter} we created above, so that the
// {@link ListView} will display list items for each word in the list of words.
// Do this by calling the setAdapter method on the {@link ListView} object and pass in
// 1 argument, which is the {@link ArrayAdapter} with the variable name itemsAdapter.
listLayout.setAdapter(adapter);
}
另外幾個頁面的 onCreate
方法 也是一樣的,唯一的的區別是 數據來源 中每個數據 的 值不一樣(這是肯定的,每個頁面顯示的是該頁面的詞彙)。
最終每個頁面的 實現效果如下:
總結
通過 繼承 實現了 自定義適配器,根據需求實現了 功能的擴展。
通過自定義適配器還可以實現 更多複雜的功能,如在每個列表項中爲單詞 添加圖片 或 音頻 等資源。
參考
How does ArrayAdapter getView() method works?