什么是數據適配器?
下圖展示了數據源、適配器、ListView等數據展示控件之間的關系。我們知道,數據源是各種各樣的,而ListView所展示數據的格式則是有一定的要求的。數據適配器正是建立了數據源與ListView之間的適配關系,將數據源轉換為ListView能夠顯示的數據格式,從而將數據的來源與數據的顯示進行解耦,降低程序的耦合性。這也體現了Android的適配器模式的使用。對於ListView、GridView等數據展示控件有多種數據適配器,本文講解最通用的數據適配器——BaseAdapter。

.ListView的顯示與緩存機制
我們知道,ListView、GridView等控件可以展示大量的數據信息。假如下圖中的ListView可以展示100條信息,但是屏幕的尺寸是有限的,一屏幕只能顯示下圖中的7條。當向上滑動ListView的時候,item1被滑出了屏幕區域,那么系統就會將item1回收到Recycler中,即View緩沖池中,而將要顯示的item8則會從緩存池中取出布局文件,並重新設置好item8需要顯示的數據,並放入需要顯示的位置。這就是ListView的緩沖機制,總結起來就是一句話:需要時才顯示,顯示完就被會收到緩存。ListView,GridView等數據顯示控件通過這種緩存機制可以極大的節省系統資源。

.BaseAdapter
使用BaseAdapter比較簡單,主要是通過繼承此類來實現BaseAdapter的四個方法:
public int getCount(): 適配器中數據集的數據個數;
public Object getItem(int position): 獲取數據集中與索引對應的數據項;
public long getItemId(int position): 獲取指定行對應的ID;
public View getView(int position,View convertView,ViewGroup parent): 獲取沒一行Item的顯示內容。
下面通過一個簡單示例演示如何使用BaseAdapter。
1.創建布局文件
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.cbt.learnbaseadapter.MainActivity">
<ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
item.xml (ListView中每條信息的顯示布局)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_image"
android:src="@mipmap/ic_launcher"
android:layout_width="60dp"
android:layout_height="60dp"/>
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_toEndOf="@id/iv_image"
android:text="Title"
android:gravity="center"
android:textSize="25sp"/>
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/iv_image"
android:layout_below="@id/tv_title"
android:text="Content"
android:textSize="20sp"/>
</RelativeLayout>
2.創建數據源
ItemBean.java
package com.cbt.learnbaseadapter;
/**
* Created by caobotao on 15/12/20.
*/
public class ItemBean {
public int itemImageResId;//圖像資源ID
public String itemTitle;//標題
public String itemContent;//內容
public ItemBean(int itemImageResId, String itemTitle, String itemContent) {
this.itemImageResId = itemImageResId;
this.itemTitle = itemTitle;
this.itemContent = itemContent;
}
}
通過此Bean類,我們就將要顯示的數據與ListView的布局內容一一對應了,每個Bean對象對應ListView的一條數據。這種方法在ListView中使用的非常廣泛。
MainActivity.java
package com.cbt.learnbaseadapter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
ListView mListView ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<ItemBean> itemBeanList = new ArrayList<>();
for (int i = 0;i < 20; i ++){
itemBeanList.add(new ItemBean(R.mipmap.ic_launcher, "標題" + i, "內容" + i));
}
mListView = (ListView) findViewById(R.id.lv_main);
//設置ListView的數據適配器
mListView.setAdapter(new MyAdapter(this,itemBeanList));
}
}
3.創建BaseAdapter
通過上面的講解,我們知道繼承BaseAdapter需要重新四個方法:getCount、getItem、getItemId、getView。其中前三個都比較簡單,而getView稍微比較復雜。通常重寫getView有三種方式,這三種方法性能方面有很大的不同。接下來我們使用此三種方式分別實現MyAdapter。
第一種:逗比式
package com.cbt.learnbaseadapter;
import android.content.Context;
import android.view.*;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by caobotao on 15/12/20.
*/
public class MyAdapter extends BaseAdapter{
private List<ItemBean> mList;//數據源
private LayoutInflater mInflater;//布局裝載器對象
// 通過構造方法將數據源與數據適配器關聯起來
// context:要使用當前的Adapter的界面對象
public MyAdapter(Context context, List<ItemBean> list) {
mList = list;
mInflater = LayoutInflater.from(context);
}
@Override
//ListView需要顯示的數據數量
public int getCount() {
return mList.size();
}
@Override
//指定的索引對應的數據項
public Object getItem(int position) {
return mList.get(position);
}
@Override
//指定的索引對應的數據項ID
public long getItemId(int position) {
return position;
}
@Override
//返回每一項的顯示內容
public View getView(int position, View convertView, ViewGroup parent) {
//將布局文件轉化為View對象
View view = mInflater.inflate(R.layout.item,null);
/**
* 找到item布局文件中對應的控件
*/
ImageView imageView = (ImageView) view.findViewById(R.id.iv_image);
TextView titleTextView = (TextView) view.findViewById(R.id.tv_title);
TextView contentTextView = (TextView) view.findViewById(R.id.tv_content);
//獲取相應索引的ItemBean對象
ItemBean bean = mList.get(position);
/**
* 設置控件的對應屬性值
*/
imageView.setImageResource(bean.itemImageResId);
titleTextView.setText(bean.itemTitle);
contentTextView.setText(bean.itemContent);
return view;
}
}
為什么稱這種getView的方式是逗比式呢?
通過上面講解,我們知道ListView、GridView等數據展示控件有緩存機制,而這種方式每次調用getView時都是通過inflate創建一個新的View對象,然后在此view中通過findViewById找到對應的控件,完全沒有利用到ListView的緩存機制。這種方式沒有經過優化處理,對資源造成了極大的浪費,效率是很低的。
第二種:普通式
public View getView(int position, View convertView, ViewGroup parent) {//如果view未被實例化過,緩存池中沒有對應的緩存
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item,null);
}
/**
* 找到item布局文件中對應的控件
*/
ImageView imageView = (ImageView) convertView.findViewById(R.id.iv_image);
TextView titleTextView = (TextView) convertView.findViewById(R.id.tv_title);
TextView contentTextView = (TextView) convertView.findViewById(R.id.tv_content);
//獲取相應索引的ItemBean對象
ItemBean bean = mList.get(position);
/**
* 設置控件的對應屬性值
*/
imageView.setImageResource(bean.itemImageResId);
titleTextView.setText(bean.itemTitle);
contentTextView.setText(bean.itemContent);
return convertView;
}
此方式充分使用了ListView的緩存機制,如果view沒有緩存才創建新的view,效率相比於逗比式提升了很多。但是,當ListView很復雜時,每次調用findViewById都會去遍歷視圖樹,所以findViewById是很消耗時間的,我們應該盡量避免使用findViewById來達到進一步優化的目的。
第三種:文藝式
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
//如果view未被實例化過,緩存池中沒有對應的緩存
if (convertView == null) {
viewHolder = new ViewHolder();
// 由於我們只需要將XML轉化為View,並不涉及到具體的布局,所以第二個參數通常設置為null
convertView = mInflater.inflate(R.layout.item, null);
//對viewHolder的屬性進行賦值
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.iv_image);
viewHolder.title = (TextView) convertView.findViewById(R.id.tv_title);
viewHolder.content = (TextView) convertView.findViewById(R.id.tv_content);
//通過setTag將convertView與viewHolder關聯
convertView.setTag(viewHolder);
}else{//如果緩存池中有對應的view緩存,則直接通過getTag取出viewHolder
viewHolder = (ViewHolder) convertView.getTag();
}
// 取出bean對象
ItemBean bean = mList.get(position);
// 設置控件的數據
viewHolder.imageView.setImageResource(bean.itemImageResId);
viewHolder.title.setText(bean.itemTitle);
viewHolder.content.setText(bean.itemContent);
return convertView;
}
// ViewHolder用於緩存控件,三個屬性分別對應item布局文件的三個控件
class ViewHolder{
public ImageView imageView;
public TextView title;
public TextView content;
}
此方式不僅利用了ListView的緩存機制,而且使用ViewHolder類來實現顯示數據視圖的緩存,避免多次調用findViewById來尋找控件,以達到優化程序的目的。所以,大家在平時的開發中應當盡量使用這種方式進行getView的實現。
總結一下用ViewHolder優化BaseAdapter的整體步驟:
>1 創建bean對象,用於封裝數據;
>2 在構造方法中初始化的數據List;
>3 創建ViewHolder類,創建布局映射關系;
>4 判斷convertView,為空則創建,並設置tag,不為空則通過tag取出ViewHolder;
>5 給ViewHolder的控件設置數據。

