ListView 编程: 如何优化自定义 Adapter

使用自定义的 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); } @Override public int getCount() { Log.d("mark", "getCount() is invoked!"); // 返回需要顯示的 item 數目 // 這次是外界提供的數據,與上次代碼有差異 return arrays.size(); } @Override public Object getItem(int position) { Log.d("mark", "getItem() is invoked!"); return position; } @Override public long getItemId(int position) { Log.d("mark", "getItemId() is invoked!"); Log.d("mark", "position = " + position); return position; } @Override public 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; } }
运行效果:


ListView 编程: 如何优化自定义 Adapter

ok,对代码的重新整理算是为下面的优化做准备。


在接下来的优化方案里,主要是针对 FileViewAdapter 的 getView 方法进行优化,其他代码不变。


说优化之前,先搞明白一个问题:getView 方法的三个参数


getView 方法有三个参数,各个参数的含义可以咨询 SDK API 文档。修改代码(就是修改 Log 输出):

@Override public 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,打印信息:


ListView 编程: 如何优化自定义 Adapter


可以看出,getView 方法被调用了 16(0-15) 次,不是 30 次。
仔细看看模拟器就可以知道当前显示的 item 是 16(下面还有一个显示不全,应该是 17 个,从下面的打印信息可以看出) 个,不是 30 个。
那么,我们滑动当前视图中的滚动条,试一试。


ListView 编程: 如何优化自定义 Adapter


可以看出,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); } @Override public int getCount() { // Log.d("mark", "getCount() is invoked!"); // 返回需要顯示的 item 數目 // 這次是外界提供的數據,與上次代碼有差異 return arrays.size(); } @Override public Object getItem(int position) { // Log.d("mark", "getItem() is invoked!"); return position; } @Override public long getItemId(int position) { // Log.d("mark", "getItemId() is invoked!"); // Log.d("mark", "position = " + position); return position; } @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.setImageBitmap(showIcon); vHolder.content.setText("fileName"); return convertView; } static class ViewHolder { TextView content; ImageView pic; } }
恩,基本搞定。剩下的问题就是 findViewById 的问题了,留着后面讨论吧。


Google IO 文档下载:http://download.****.net/detail/androidbluetooth/3783925