1. 程式人生 > >ListView (3) 之介面卡 ArrayAdapter/SimpleAdapter/BaseAdapter

ListView (3) 之介面卡 ArrayAdapter/SimpleAdapter/BaseAdapter

ListView (3) 之介面卡 Adapter

ArrayAdapter/SimpleAdapter/BaseAdapter的使用

Android中通過Adapter為AbsListView列表控制元件提供基礎資料,Adapter只是一個介面,它派生了ListAdapter和SpinnerAdapter,其中ListAdapter為AbsListView提供列表,SpinnerAdapter為AbsSpinner提供資料。
ListView、 Adapter、 資料來源三者之間的關係圖
ListView、 Adapter、 資料來源三者之間的關係圖

ArrayAdapter

ArrayAdapter是較為簡單快捷的介面卡,不需要建立專門的Item佈局來填充列表項,通過提供一個String[]型別的資料來源,可以直接通過系統自帶的佈局檔案

資料來源為String[] 的資料集合

String[] list = { "李陽瘋狂英語", "AOC", "BBC", "CCTV", "優酷財經" };
// 將陣列包裝成ArrayAdapter
ArrayAdapter<String> adapter = new ArrayAdapter<String>(context,android.R.layout.simple_list_item_single_choice, list);
listView.setAdapter(adapter);

ArrayAdapter建構函式,第二個引數為 帶有一個TextView系統佈局資源ID,TextView的ID為@android:id/text1,點選可檢視佈局如下

<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:gravity="center_vertical"
    android:minHeight="?android:attr/listPreferredItemHeightSmall"
    android:paddingEnd
="?android:attr/listPreferredItemPaddingEnd" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:textAppearance="?android:attr/textAppearanceListItemSmall" />

資料來源儲存在資原始檔

// 獲取resource資原始檔定義好的陣列
Resources res = getResources();
String[] arrs = res.getStringArray(R.array.books);
// 將陣列包裝成ArrayAdapter,第二個引數為 帶有一個TextView
// 佈局資源,TextView的ID為@android:id/text1
ArrayAdapter<String> adapter = new ArrayAdapter<String>(context,android.R.layout.simple_list_item_1, arrs);
listView.setAdapter(adapter);

SimpleAdapter

通過ArrayAdapter實現的Adapter雖然簡單、易用,但功能有限,它只能通過每個列表項是TextView,如果需要實現更復雜的列表項,則可以使用SimpleAdapter。SimpleAdapter並非如名字簡單,它的功能很強大,可實現ListView的大部分應用。

<?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="match_parent">

    <ImageView 
        android:id="@+id/Img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView 
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/Img"
        android:gravity="center_vertical"
        android:layout_marginLeft="20sp"/>

    <TextView 
        android:id="@+id/country"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/name"
        android:layout_marginLeft="20sp"/>

</RelativeLayout>
package com.example.listview03_simpleadapter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.SimpleAdapter;

public class MainActivity extends Activity {
    private String[] names = { "姚明", "Kobe", "貝克漢姆", "加索爾" };
    private String[] country = { "中國", "美國", "英國", "西班牙" };
    private int[] imgId = { R.drawable.j1a2, R.drawable.j1a4, R.drawable.j1a5,R.drawable.j1a6 };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 建立一個List集合,集合元素Map
        List<Map<String, Object>> listItem = new ArrayList<Map<String, Object>>();
        for (int i = 0; i < names.length; i++) {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("img", imgId[i]);
            map.put("name", names[i]);
            map.put("country", country[i]);
            listItem.add(map);
        }

        // 建立Adapter例項
        SimpleAdapter adapter = new SimpleAdapter(MainActivity.this, listItem,R.layout.item, new String[] { "img", "name", "country" },new int[] { R.id.Img, R.id.name, R.id.country });

        ListView listView = (ListView) findViewById(R.id.listViewId);
        listView.setAdapter(adapter);
    }
}

BaseAdapter使用及優化

【重點】下面重點介紹,BaseAdapter是一個實現了ListAdapter和SpinnerAdapter介面的抽象類,可用於為ListView和Spinner提供資料介面卡。

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {

繼承自BaseAdapter的自定義介面卡

public class MyListAdapter extends BaseAdapter {
    private Context context;

    /** 資料來源 */
    private List<User> mList;

    private LayoutInflater inflater;

    public MyListAdapter(Context context, List<User> mList) {
        super();
        this.context = context;
        this.mList = mList;
        inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public User getItem(int position) {
        return mList.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        return null;
    }

}

從上程式碼片段中可以看到,我們自定義的資料介面卡繼承子BaseAdapter,需要複寫下面四個方法
getCount() —獲取資料來源個數
getItem(int position) —獲取指定position的資料實體
getItemId(int position) —獲取資料列表的行編號,一般返回position
getView(int position, View convertView, ViewGroup parent) —此方法最為重要,返回一個顯示在資料列表中View;
在getView方法載入指定XML佈局檔案,設定每個Item顯示內容,繫結Item中各個控制元件的監聽事件,具體操作以下程式碼塊

getView方法 convertView的複用

首先,getView的三個引數分別代表: 序號(位置)、列表中一個ITEM的顯示檢視、view依附的父檢視
Android中有個叫做Recycler(反覆迴圈器)的構件,下圖是它的工作原理:
這裡寫圖片描述
convert的複用:一共有200條,每屏可以顯示7條資料,初次載入並不會一次性呼叫200次getView方法,只會呼叫前7次,生成7個convertView顯示到頁面上, 當頁面滑動第一個Item被劃出螢幕外,此時劃出螢幕外的Item1存在convertView中,convertView是一個xml資源生成的View,當底部有新的Item劃入螢幕,此時convertView持有了劃出螢幕外的view,可以拿來直接使用,而減少了inflate資源的次數。也就是說利用convertView的複用,雖然資料來源總個數為200,但只有第一次 執行inflate載入xml資源。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View view = convertView;
    if (view == null) {
        view = inflater.inflate(R.layout.item_simple, parent, false);
    }
    TextView tvName = (TextView) view.findViewById(R.id.name);
    TextView tvCity = (TextView) view.findViewById(R.id.country);
    tvName.setText(mList.get(position).getName());
    tvCity.setText(mList.get(position).getCountry());
    Log.i("getView", "pos:" + position);
    return view;
}

ViewHolder優化findView次數

從上述程式碼片中,通過convertView複用減少了inflate載入資源的次數,可每次執行getView方法時,每次通過findViewById去找convertView的所用子控制元件,在反覆滑動時會不斷呼叫getView方法,所以同時就會反覆呼叫findViewById方法。為了提高效能,減少findViewById次數,我們這裡就用到了ViewHolder來持有convertView的每一個子控制元件,以setTag的形式和convertView繫結,需要時再從convertView.getTag()中取出,避免了反覆findViewById的情況。

private class ViewHolder {
    public TextView tvName;
    public TextView tvCity;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View view = convertView;
    if (view == null) {
        ViewHolder viewHolder = new ViewHolder();
        view = inflater.inflate(R.layout.item_simple, parent, false);
        // 通過ViewHolder持有view中的子控制元件
        viewHolder.tvName = (TextView) view.findViewById(R.id.name);
        viewHolder.tvCity = (TextView) view.findViewById(R.id.country);
        // 再通過setTag的形式和view繫結
        view.setTag(viewHolder);
    }
    ViewHolder viewHolder = (ViewHolder) view.getTag();
    viewHolder.tvName.setText(mList.get(position).getName());
    viewHolder.tvCity.setText(mList.get(position).getCountry());
    return view;
}