1. 程式人生 > Android入門教學 >Android 列表控制元件 ListView

Android 列表控制元件 ListView

在學習了 ScrollView 及 Adapter 兩節內容之後,大家應該對 ListView 有了一些基本的瞭解,它是一個列表樣式的 ViewGroup,將若干 item 按行排列。ListView 是一個很基本的控制元件也是 Android 中最重要的控制元件之一。它可以幫助我們完成多個 View 的垂直排列並支援滾動顯示效果,而它比 ScrollView 更靈活也更易擴充套件,Adapter 作為 UI 控制元件和資料來源之間的橋樑,會幫我們實現 MVC 模式,所以在實際開發中大多數的列表場景我們會優先考慮使用 ListView 來實現(目前 Google 推出了新的更強大的列表控制元件——RecyclerView,不過基本原理和 ListView 類似)。

1. ListView 的特性

ListView 在 Android App 中無處不在,比如最常用的“聯絡人”就可以通過 ListView 輕鬆實現。通過 ListView 使用者可以上下滑動來瀏覽列表資訊,我們可以在 ListView 中放置各種控制元件,比如 ImageView、Button、ToggleButton 等來豐富我們的列表樣式。

正因為 ListView 通常是用來展示大量的資料集的控制元件,所以我們不可能挨個的為每個 item 去設定相應的資料,這時候就要藉助 Adapter 來幫助我們完成 UI 控制元件和資料的繫結工作了。

2. ListView 的基本用法

ListView 相比其他控制元件來講確實比較特殊,也有很多使用技巧,但是它作為一個 ViewGroup,同樣也有自己的佈局屬性、 API 及事件監聽器。

2.1 ListView 的常用屬性

  • divider:
    設定 item 之間的分隔線,可以設定成顏色,也可以設定成 drawable 資源。
  • dividerHeight:
    設定分隔線的高度;
  • footerDividersEnabled:
    是否在 footerView(表尾)前繪製一個分隔線,預設為 true;
  • headerDividersEnabled:
    是否在 headerView(表首)前繪製一個分隔線,預設為 true;
  • android:scrollbars:
    設定滾動條樣式,有兩種樣式: horizontal 和 vertical,以及 none 表示隱藏滾動條。

2.2 ListView 的常用 API

  • addHeaderView(View v):
    新增 headView,headView 會固定顯示在表的第一個元素之前。引數是一個 View 物件,比如可以用作“下拉重新整理”的 View;
  • addFooterView(View v):
    新增 footerView,footerView 會固定顯示在表的最後一個元素之後。引數是一個 View 物件,比如可以用作“上拉載入更多”的 View;
  • addHeaderView(View v, Object data, boolean isSelectable):
    新增 headView,第二個引數表示與 headView 繫結的資料物件,第三個引數表示當前這條 item 是否可選中,通常“下拉重新整理”可以設定成無法選中;
  • addFooterView(View v, Object data, boolean isSelectable):
    新增 footerView,第二個引數表示與 footerView 繫結的資料物件,第三個引數表示當前這條 item 是否可選中,通常“上拉載入更多”可設定成無法選中。

2.3 點選事件監聽器

ListView 的樣式示意圖如下:

ListView示意圖

ListView 中的每個 item 可以設定成任意樣式,可以包含任意的 Android 控制元件,非常靈活。接下來,我們來一起看看如何使用。

3. ListView 的使用示例

使用 ListView 就一定逃不開 Adapter,在上一節我們介紹了 ArrayAdapter 和 SimpleAdapter 配合 ListView 的使用方法,其實 ArrayAdapter 和 SimpleAdapter 都是繼承 BaseAdapter 做的封裝,那麼這一節我們就來看看 BaseAdapter 究竟是何方神聖。為了讓大家更好的看到對比,這一節我們用 BaseAdapter 來實現上一節的水果的列表。

3.1 自定義 Adapter

BaseAdapter 是一個介面,我們自定義 Adapter 就需要實現一個 BaseAdapter 介面,首先建立一個 MyAdapter 類實現 BaseAdapter 介面,如下:

package com.emercy.myapplication;

import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public class MyAdapter extends BaseAdapter {
    @Override
    public int getCount() {
        return 0;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        return null;
    }
}

可以看到,繼承自 BaseAdapter 的類有 4 個方法是必須實現的,我們具體看看這四個方法分別表示什麼以及如何實現:

  • public int getCount():
    返回列表的長度,即 ListView 需要展示的 item 數量。通常我們會將資料儲存在 List 或者陣列當中,從而可以通過資料或者 list 獲取列表的長度返回即可。比如如果我們通過 ArrayList 儲存列表的資料,那麼我們可以通過 List 的 size() 方法獲取列表的長度,並在 getCount() 回撥方法中返回,如下:
    @Override
    public int getCount() {
      int count = arrayList.size();     // 計算資料 ArrayList 的長度
      return count;                     // 返回列表的長度
    }
    
  • public Object getItem(int position):
    獲取位於 position 的 item 對應的資料內容,當 ListView 需要填充第 position 個 item 的時候會回撥此函式獲取當前 item 上應該顯示的資料內容,如果資料存在 ArrayList 當中,直接返回當前 position 的 ArrayList 內容即可,如下:
    @Override
    public Object getItem(int i)return arrayList.get(i);        // item 對應的資料內容
    }
    
  • public long getItemId(int position) :
    返回當前行的 itemid,itemid 是唯一標識當前 item 的索引,通常情況下我們可以直接返回 position,如下:
    @Override
    public long getItemId(int i) {
      return i;
    }
    
  • public View getView(int position, View convertView, ViewGroup parent):
    當列表中的一個 item 即將被展示的時候系統會回撥此函式,我們需要在此回撥介面中完成資料與 UI 控制元件的繫結。通過LayoutInflater類獲取佈局物件,然後通過findViewById拿到具體的控制元件,並將資料內容設定到控制元件當中,比如我們需要在列表中設定一個圖片資源:
    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
      view = inflter.inflate(R.layout.activity_gridview, null);      // 獲取佈局物件
      ImageView icon = (ImageView) view.findViewById(R.id.icon);     // 通過ID拿到具體的View物件
      icon.setImageResource(flags[i]);                               // 設定ImageView的圖片資源
      return view;
    }
    

3.2 編寫佈局檔案

為了對比學習,本例實現一個和上一節中 SimpleAdapter 的水果列表相同的例子,佈局檔案可直接引用,具體程式碼可以參考 第 23 節第 2 小節的內容。

3.3 建立資料模型

在 Adapter 中建立我們需要儲存的資料,和上一節的例子一樣,我們用兩個陣列分別儲存水果名稱和水果圖片資源:

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};

在 MyAdapter 中新增資料更新介面,用於初始資料的設定及後續資料的更新:
MyAdapter.java

public void setData(String[] name, int[] resId)

這樣一來我們就有了資料來源,接著按照 第 24 節 3.1 小節中對 4 個回撥介面的描述修改回撥方法體。主要是在getCount中返回陣列長度,getView中通過 layout 物件獲取到 TextView 和 ImageView,然後設定水果名稱和圖片,最終 MyAdapter 類的程式碼如下:

package com.emercy.myapplication;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;


public class MyAdapter extends BaseAdapter {

    private Context mContext;
    private String[] mName;
    private int[] mResId;

    public MyAdapter(Context context) {
        mContext = context;
    }

    public void setData(String[] name, int[] resId) {
        mName = name;
        mResId = resId;
    }


    @Override
    public int getCount() {
        return mName.length;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        convertView = LayoutInflater.from(mContext).inflate(R.layout.list_view, null);
        TextView name = convertView.findViewById(R.id.textView);
        ImageView image = convertView.findViewById(R.id.imageView);
        name.setText(mName[position]);
        image.setImageResource(mResId[position]);
        return convertView;
    }
}

3.4 編寫主 Activity

ListView 的核心適配邏輯都在 Adapter 中完成,主 Activity 比較簡單,主要做以下幾件事:

  • 獲取 listView 物件
  • 建立自定義 adapter,即 MyAdapter,並設定資料
  • 將自定義 Adapter 設定給 ListView
  • 設定 ListView 列表項的點選事件
    程式碼如下:
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.Toast;

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.listView);

        MyAdapter adapter = new MyAdapter(this);
        adapter.setData(mDataName, mDataImage);
        mListView.setAdapter(adapter);

        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();
            }
        });
    }
}

執行效果和上一節一樣:

ListView + BaseAdapter

4. 小結

本節主要介紹了 ListView 搭配 BaseAdapter 實現列表功能的方法,BaseAdapter 比 ArrayAdapter 和 SimpleAdapter 擁有更大的可控性,我們可以自己實現很多複雜的功能。目前更推薦使用的是 Google 近年推出的 RecyclerView,不過基本原理和 ListView 類似,本節主要介紹的是基礎的用法,在掌握了基本用法及核心思想之後,可以繼續學習一些優化手段及高階用法。