摆脱ViewHolder,一种无需ViewHolder的高效ListView写法

使用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,填充数据等。

原文作者不喜欢ViewHolder的原因

  • ViewHolder模式会在Adapter的getView(...)方法中干了太多事。
  • ViewHolder类太过公式化,创建和设置holder让人累觉不爱。
  • View.getTag()得到的对象需要强转为正确的holder类型,坑(查不到kludgy的翻译,只能这样了,坑)。
  • 因为adapter/holder需要知道代表列表各项的view的内部结构,有违封装性。

无需ViewHolder的写法

出于复用布局的目的,原文作者定义了两个布局文件和使用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了。

总结这种写法的优势

  • Adapter的代码会变得极其简洁
  • 很容易通过代码和xml文件来创建ItemView
  • 以后有需要更改,只需要对布局文件和ItemView进行动刀即可
  • 整个过程无需创建额外的holder类和对象

好了,基本就这样,转述可能跟原文有些出入,有能力的话可以看原文怎么说明的。

文章地址 http://licheetec.com/2015/03/21/no-viewholder-listview/

你可能感兴趣的:(viewholder)