安卓學習日誌 — Day03

概述

繼續構建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_height0dplayout_weight1 讓其高度均值分佈。

數據來源

我們需要在每個列表項顯示 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 默認也將默認管理單個 TextviewString 對象, 所以 在調用父類 ArrayAdapter 的構造函數時傳入 佈局資源ID爲 0

另外 還是需要讓自定義的這個適配器 WordAdapterWord 對象和 列表項佈局資源 聯繫起來,實現 適配器的基礎功能 視圖回收

經查閱資料,發現 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 方法基本做了三件事情:

  1. 13 ~ 17 行,得到被回收的視圖 itemView,如果該視圖爲 空對象則根據列表項的佈局資源 list_item 創建一個(被回收的視圖存在空的情況是因爲 在Numbers 的頁面剛加載時,並沒有多餘 的視圖用於 回收,這是被回收的視圖對象itemView 就是一個空對象 null)。
  2. 20 ~ 32 行,調用繼承自 ArrayAdaptergetItem 方法 得到 下一個 將要被顯示的 Word 對象,然後通過預先在 列表項佈局資源中對於的視圖 ID 將 這個對象的屬性 分別設置爲被回收的視圖 itemView 需要顯示的內容。
  3. 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 的使用基本相同:

  1. 7 ~ 17 行,準備數據來源 words(一個包含 Word 對象的數字列表)
  2. 25 行,創建自定義適配器 WordAdapter 對象 adapter,傳入參數 context 上下文、words 數據來源(這裏沒有指定佈局資源是因爲在WordAdaptergetView` 方法中 已經實現了 自定義佈局資源和 Word 對象的適配)
  3. 30 行,獲取用於顯示列表項的根視圖 ListView 對象。
  4. 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?

Using an ArrayAdapter with ListView

What Are Adapters in Android

Performance Tips for Android’s ListView

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章