使用ListView来展示列表数据,基本上是每个Android猿必须掌握的技能,而ListView的基本优化技巧,基本也烂大街了,无非是复用convertView对象还有使用ViewHolder来缓存Item中各个子View的引用。
最近看到一篇文章,描述了一种非主流的高效的ListView写法,无需创建ViewHolder类和holder对象,不用setTag()和getTag(),感觉效率更高。
原文见
http://www.bignerdranch.com/blog/customizing-android-listview-rows-subclassing/
原文作者通过创建RelativeLayout子类(这里ListView各item的布局的根布局是RelativeLayout,根据实际情况而定)来封装所有自定义操作,如findViewById各个子View,填充数据等。
出于复用布局的目的,原文作者定义了两个布局文件和使用merge标签来描述item的view,不过我觉得这不是这个写法的重点,所以改了一下,只是简单地定义了一个布局文件来描述item view。
先看看Demo截图
1. 定义一个Item
类来定义列表数据。
public class Item {
private int imgId;
private String title;
private String content;
...getter...setter...
}
2. 定义item view的布局(见item_view.xml
)。
3. 自定义ItemView
类,继承步骤2
中定义的布局的根布局(这里是RelativeLayout
),实现参数为(Context context, AttributeSet attrs)
或(Context context, AttributeSet attrs, int defStyle)
的构造方法,并添加用于获取子View对象引用、获取ItemView对象和填充数据的方法,具体代码如下:
public class ItemView extends RelativeLayout {
private ImageView ivImage;
private TextView tvTitle;
private TextView tvContent;
public ItemView(Context context, AttributeSet attrs) {
super(context, attrs);
}
// 获取ItemView
public static ItemView newInstance(ViewGroup parent) {
ItemView view = (ItemView) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view,parent,false);
view.setupChildren();
return view;
}
// 获取子View对象的引用
private void setupChildren() {
ivImage = (ImageView) findViewById(R.id.iv_image);
tvTitle = (TextView) findViewById(R.id.tv_title);
tvContent = (TextView) findViewById(R.id.tv_content);
}
// 填充数据
public void populateData(Item item) {
ivImage.setImageResource(item.getImgId());
tvTitle.setText(item.getTitle());
tvContent.setText(item.getContent());
}
}
需要注意的是,有网友反馈,说参照原文那样改写,出现异常
03-22 10:11:09.340: E/AndroidRuntime(1417): android.view.InflateException: Binary XML file line #1: Error inflating class com.xxxx.ItemView
....
03-22 10:11:09.340: E/AndroidRuntime(1417): Caused by: java.lang.NoSuchMethodException:[class android.content.Context, interface android.util.AttributeSet]
分析最后一个“Caused by”异常消息,应该是填充xml初始化ItemView的时候,
调用的是参数为(Context context, AttributeSet attrs)
的构造方法,
而原文用的是参数为(Context context, AttributeSet attrs, int defStyle)
的构造方法,如果按照原文那样写的话,就有可能出现这个找不到方法的异常,
只要按照log的提示,修改为需要的那种构造方法就好。
4. 创建ListView用的adapter类(ItemAdapter
)。
public class ItemAdapter extends ArrayAdapter<Item> {
public ItemAdapter(Context context, List<Item> objects) {
super(context, 0, objects);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ItemView view = (ItemView) convertView;
if (view == null) {
view = ItemView.newInstance(parent);
}
view.populateData(getItem(position));
return view;
}
}
5. (很重要,我经常会忘了这步
)把item view中布局(item_view.xml)的根标签
,改为ItemView
。
<?xml version="1.0" encoding="utf-8"?>
<com.licheetec.noholderlistviewdemo.ItemView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="64dp"
android:padding="8dp">
<ImageView
android:id="@+id/iv_image"
.../>
<TextView
android:id="@+id/tv_title"
.../>
<TextView
android:id="@+id/tv_content"
.../>
</com.licheetec.noholderlistviewdemo.ItemView>
6. 剩下的就是在activity获取和设置ListView了。
好了,基本就这样,转述可能跟原文有些出入,有能力的话可以看原文怎么说明的。
文章地址 http://licheetec.com/2015/03/21/no-viewholder-listview/