使用自訂的 Adapter,需要最佳化,說到底是最佳化我們自訂的適配器類!
再說到底就是最佳化回調方法 getView 方法。
ListView 編程: Adapter 何方神聖? 部落格中,只是簡單的介紹了如何去自訂一個適配器以及注意事項。
但是.......
如果像 ListView 編程: Adapter 何方神聖? 中的範例程式碼那樣去寫程式的話,那麼估計要被老大BS的,呵呵!
那麼,結合 Google IO 的建議、APIDemo 代碼以及 個人見解,作進一步的最佳化工作。
說明:該部落格使用的布局檔案與 ListView 編程: Adapter 何方神聖? 附錄中一致。
整理代碼,為最佳化做準備
Activity 代碼
package mark.zhang;import java.util.ArrayList;import android.app.ListActivity;import android.os.Bundle;public class FileActivity extends ListActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 模擬添加數據 ArrayList<String> arrays = new ArrayList<String>(); for(int i=0; i<30; i++) { arrays.add("" + i); } // 執行個體化自訂 Adapter FileViewAdapter adapter = new FileViewAdapter(this, arrays); // 設定適配器 setListAdapter(adapter); }}
FileViewAdapter 代碼
package mark.zhang;import java.util.ArrayList;import android.content.Context;import android.util.Log;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 FileViewAdapter extends BaseAdapter {private LayoutInflater inflater = null;private ArrayList<String> arrays = null;public FileViewAdapter(Context context, ArrayList<String> arrays) {this.arrays = arrays;inflater = LayoutInflater.from(context);}@Overridepublic int getCount() {Log.d("mark", "getCount() is invoked!");// 返回需要顯示的 item 數目// 這次是外界提供的數據,與上次代碼有差異return arrays.size();}@Overridepublic Object getItem(int position) {Log.d("mark", "getItem() is invoked!");return position;}@Overridepublic long getItemId(int position) {Log.d("mark", "getItemId() is invoked!");Log.d("mark", "position = " + position);return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {Log.d("mark", "getView() is invoked!");View v = inflater.inflate(R.layout.custom_fileview, null);((ImageView) v.findViewById(R.id.image_pic)).setImageResource(R.drawable.file);((TextView) v.findViewById(R.id.text_content)).setText("fileName");return v;}}
運行效果:
ok,對代碼的重新整理算是為下面的最佳化做準備。
在接下來的最佳化方案裡,主要是針對 FileViewAdapter 的 getView 方法進行最佳化,其他代碼不變。
說最佳化之前,先搞明白一個問題:getView 方法的三個參數
getView 方法有三個參數,各個參數的含義可以諮詢 SDK API 文檔。
修改代碼(就是修改 Log 輸出):
@Overridepublic View getView(int position, View convertView, ViewGroup parent) {Log.d("mark", "getView() is invoked!" + "position = " + position + ","+ "convertView = " + convertView + "," + "parent = " + parent);View v = inflater.inflate(R.layout.custom_fileview, null);((ImageView) v.findViewById(R.id.image_pic)).setImageResource(R.drawable.file);((TextView) v.findViewById(R.id.text_content)).setText("fileName");return v;}
運行 APP,列印資訊:
可以看出,getView 方法被調用了 16(0-15) 次,不是 30 次。
仔細看看模擬器就可以知道當前顯示的 item
是 16(下面還有一個顯示不全,應該是 17 個,從下面的列印資訊可以看出) 個.
不是 30 個。
那麼,我們滑動當前視圖中的捲軸,試一試。
可以看出,getView 方法確實是調用了 30 次(0-29)。
你還可以發現,(滾動)顯示的列印資訊中,convertView 不是 null 了。
我們再次將捲軸滾動到頂部,發現列印資訊中 convertView 也不是 null 了。
換句話說:
利用 convertView 這個參數,而不去在 getView 方法中重新建立一個臨時的變數 View 了,
那麼可以減輕虛擬機器(回收)的負擔,從而提高效率。
方案 1_ 最佳化代碼: 使用 convertView
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.d("mark", "getView() is invoked!" + "position = " + position + ","
+ "convertView = " + convertView + "," + "parent = " + parent);
if (convertView == null) {
convertView = inflater.inflate(R.layout.custom_fileview, null);
}
((ImageView) convertView.findViewById(R.id.image_pic))
.setImageResource(R.drawable.file);
((TextView) convertView.findViewById(R.id.text_content))
.setText("fileName");
return convertView;
}
可以看出,只有 convertView == null 為真(上面測試已經說明 convertView 何時為 null),
才去建立 View 對象。
如果你有興趣的話,可以再次運行 APP 可以看出從底部再次滾動到頂部,
反覆幾次,你會發現幾乎很少建立 View 對象,而是重複利用原來已經存在的 View 對象。
下面還有一種方式來最佳化代碼,不說是最好但至少是 even better (Google 推薦)!
方案 2_ 最佳化代碼:hold 一把
在 android 提供的 APIDemo 中(List14.java)使用了 ViewHolder ,
所以 ViewHolder 不是 android 內建的 api,也不是什麼詭異的東西。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.d("mark", "getView() is invoked!" + "position = " + position + ","
+ "convertView = " + convertView + "," + "parent = " + parent);
ViewHolder vHolder = null;
if (convertView == null) {
convertView = inflater.inflate(R.layout.custom_fileview, null);
// 創建 ViewHodler 對象
vHolder = new ViewHolder();
vHolder.pic = (ImageView) convertView.findViewById(R.id.image_pic);
vHolder.content = (TextView) convertView
.findViewById(R.id.text_content);
// 設置 Tag
convertView.setTag(vHolder);
} else {
vHolder = (ViewHolder) convertView.getTag();
}
vHolder.pic.setImageResource(R.drawable.file);
vHolder.content.setText("fileName");
return convertView;
}
其中 ViewHolder 是 FileViewAdapter 的 一個靜態內部類。
static class ViewHolder {
TextView content;
ImageView pic;
}
使用 ViewHolder 的關鍵好處是緩衝了顯示資料的視圖,加快了 UI 的響應速度。
到目前為止,彷彿最佳化工作已經 ok,其實,
還有一個小問題: ImageView 使用的圖片資源需要預先處理。
FileViewAdapter 完整的代碼如下:
package mark.zhang;import java.util.ArrayList;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.util.Log;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 FileViewAdapter extends BaseAdapter {private LayoutInflater inflater = null;private ArrayList<String> arrays = null;private Bitmap showIcon = null;public FileViewAdapter(Context context, ArrayList<String> arrays) {this.arrays = arrays;inflater = LayoutInflater.from(context);// 處理圖片資源showIcon = BitmapFactory.decodeResource(context.getResources(),R.drawable.file);}@Overridepublic int getCount() {// Log.d("mark", "getCount() is invoked!");// 返回需要顯示的 item 數目// 這次是外界提供的數據,與上次代碼有差異return arrays.size();}@Overridepublic Object getItem(int position) {// Log.d("mark", "getItem() is invoked!");return position;}@Overridepublic long getItemId(int position) {// Log.d("mark", "getItemId() is invoked!");// Log.d("mark", "position = " + position);return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {Log.d("mark", "getView() is invoked!" + "position = " + position + ","+ "convertView = " + convertView + "," + "parent = " + parent);ViewHolder vHolder = null;if (convertView == null) {convertView = inflater.inflate(R.layout.custom_fileview, null);// 創建 ViewHodler 對象vHolder = new ViewHolder();vHolder.pic = (ImageView) convertView.findViewById(R.id.image_pic);vHolder.content = (TextView) convertView.findViewById(R.id.text_content);// 設置 TagconvertView.setTag(vHolder);} else {vHolder = (ViewHolder) convertView.getTag();}// 設置位圖vHolder.pic.setImageBitmap(showIcon);vHolder.content.setText("fileName");return convertView;}static class ViewHolder {TextView content;ImageView pic;}}
最後強調一下,要在布局檔案裡面將 ListView 控制項的屬性設定為:
android:layout_width="fill_parent"
android:layout_height="fill_parent"
關於 ListView 的工作原理(Recycler)以及如何進一步最佳化 ListView,可以參考
http://mzh3344258.blog.51cto.com/1823534/889879
Google IO 文檔下載
http://download.csdn.net/detail/androidbluetooth/3783925