ListView优化新玩法,打造易维护,高性能,快速开发的ListView

简介

如果让你实现一个如下图所示的复杂的ListView你会怎么设计?

(红线部分为ListView区域,蓝线部分为Listview包含的四种Type)



没准你会先继承一个BaseAdapter,实现其中的getView()方法,

通过getViewTypeCount(),getItemViewType(int position)等方法在getView()中添加一大堆if...else...语句块,生成对应Type的View。

然后你会判断convertView是否是空,为空就inflate布局文件。再通过一堆findViewById和setXXX方法进行数据填充。

当然,可能你还会想起ValueHolder模式,为每种Type定制一个ValueHolder对象,让性能大幅提升,也让代码大幅增加。

到最后这个自定义的BaseAdapter能有上千行代码,并且充斥着if...else...语句,看起来非常难以维护。


这一切都太复杂了,现在我要帮你托管这一切。读完本文你可以完全不用实现getView()方法,但得到相同的效果。

=============================================================

下面我们看如何实现:

1.增强BaseAdapter。

为了简化代码,我们需要自定义的BaseAdapter能够更好的支持多ItemType的设计。

由于BaseAdapter支持了各种不同的ItemType,各种ItemType对应各自的UI布局和后台数据,并在getView中进行填充。
很明显,我们会想到将各种不同的ItemType封装为一个对象,这个对象拥有自己的UI布局,后台数据,和填充函数。
而BaseAdapter只需要将这些不同的ItemType管理起来,不用再去关心每种ItemType是如何生成UI布局,绑定数据。

有了这个思路,我们开始着手实现,首先定义一个Item.java,用来控制每一个ItemType对应的UI布局和后台数据:
public abstract class Item<T> {

    protected T data;

    protected int mLayoutResId = -1;

    public Item(T t) {
        this(t, -1);
    }

    public Item(T t, int layoutResId) {
        data = t;
        mLayoutResId = layoutResId;
    }

    public T getData() {
        return data;
    }
}


 
  
 
为了便于扩展,我们将Item定义为抽象类。这个抽象类包含两个成员变量,其中:
data是该Item对应的数据源, mLayoutResId是Item对应的布局文件
接下来,我们需要自定义自己的BaseAdapter,用来管理这些Item对象。我把它命名为ItemAdapter,我希望它可以智能的管理ItemType,当ItemType增加,减少时不需要我去写额外的if..else..,为了实现这个目标,我需要重写
<span style="font-size:14px;">getItemViewType()和getViewTypeCount()。并让它们更“聪明"</span>

话不多说,直接看代码:
public class ItemAdapter extends BaseAdapter {

    protected List<Item> mData = new ArrayList<Item>();

    protected Context mContext;

    private List<ItemLayout> mItemLayouts;

    private ItemLayout mTempItemLayout = new ItemLayout();

    private boolean mHasReturnedViewTypeCount = false;// 是否已经调用getViewTypeCount

    private int mMaxTypeCount = 10;

    private static final Boolean DEBUG = true;

    private static final String TAG = "ItemAdapter";

    public ItemAdapter(Context context) {
        mContext = context;
        mItemLayouts = new ArrayList<ItemLayout>();
    }

    private static class ItemLayout implements Comparable<ItemLayout> {

        private Class<?> clz;

        public int compareTo(ItemLayout other) {

            if (clz.hashCode() == other.clz.hashCode()) {
                return 0;
            } else if (clz.hashCode() < other.clz.hashCode()) {
                return -1;
            } else {
                return 1;
            }

        }
    }

    private ItemLayout createItemLayout(Item<?> item, ItemLayout in) {
        ItemLayout il = in != null ? in : new ItemLayout();
        il.clz = item.getClass();
        return il;
    }

    private void addToItemLayouts(Item<?> item) {
        final ItemLayout il = createItemLayout(item, null);
        int insertPos = Collections.binarySearch(mItemLayouts, il);

        // 不存在就添加一个类型
        if (insertPos < 0) {
            insertPos = insertPos * -1 - 1;
            mItemLayouts.add(insertPos, il);
        }

    }

    public void setTypeCount(int num) {
        if (mHasReturnedViewTypeCount) {
            throw new IllegalArgumentException("must call setTypeCount before setAdapter");
        }
        mMaxTypeCount = num;
    }

    public int indexOfItem(Item<?> item) {
        return mData.indexOf(item);
    }

    public void addItem(final Item<?> item) {
        mData.add(item);
        addToItemLayouts(item);
        notifyDataSetChanged();
    }

    public void addItem(int idx, final Item<?> item) {
        mData.add(idx, item);
        addToItemLayouts(item);
        notifyDataSetChanged();
    }

    public void addItems(final ArrayList<Item> items) {
        mData.addAll(items);
        for (Item<?> item : items) {
            addToItemLayouts(item);
        }
        notifyDataSetChanged();
    }

    public Item<?> removeItem(int idx) {
        if (idx < mData.size()) {
            Item<?> t = mData.remove(idx);
            notifyDataSetChanged();
            return t;
        } else {
            return null;
        }
    }

    public void removeItem(Class<?> clz) {
        Iterator<Item> iterator = mData.iterator();
        boolean find = false;
        while (iterator.hasNext()) {
            if (clz.isInstance(iterator.next())) {
                find = true;
                iterator.remove();
            }
        }
        if (find) {
            this.notifyDataSetChanged();
        }
    }

    public void clearItems() {
        mData.clear();
        // 不清mItemLayouts 是为了让getItemViewType始终得到正确的值
        this.notifyDataSetChanged();
    }

    @Override
    public int getItemViewType(int position) {
        if (!mHasReturnedViewTypeCount) {
            mHasReturnedViewTypeCount = true;
        }

        final Item<?> item = this.getItem(position);
        mTempItemLayout = createItemLayout(item, mTempItemLayout);
        int viewType = Collections.binarySearch(mItemLayouts, mTempItemLayout);
        if (DEBUG) {
            Log.i(TAG, "getItemView Type is : " + viewType + " position is : " + position
                    + " item class is : " + item.getClass().getName());
        }
        if (viewType < 0) {
            // 未测试:viewType正常情况下不会查不到,如果<0,属于异常情况。
            // 异常情况view不复用。
            if (DEBUG) {
                Log.e(TAG, "ERROR : viewType < 0!!!");
            }
            return IGNORE_ITEM_VIEW_TYPE;
        } else {
            return viewType;
        }
    }

    @Override
    public int getViewTypeCount() {
        if (!mHasReturnedViewTypeCount) {
            mHasReturnedViewTypeCount = true;
        }
        // 此处会造成内存额外开销,如需避免,需要在SimpleListFragment.java执行setAdapter之前添加所有item
        return mMaxTypeCount;
        // return Math.max(1, mItemLayouts.size());
    }

    // 某一种item出现的个数,
    public int getCount(Class<?> clz) {
        int num = 0;
        for (Item<?> item : mData) {
            if (item.getClass().equals(clz)) {
                num++;
            }
        }
        return num;
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Item<?> getItem(int position) {
        if (position < 0 || position >= getCount()) {
            return null;
        }
        Item<?> item = mData.get(position);
        return item;
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //TODO
    }

}

在ItemAdapter中,我使用List<Item> mData来保存所有的Item对象,List<ItemLayout> mItemLayouts保存所有 Type的类型。
例如,如果向ItemAdapter添加了50个Item,这50个Item属于5中不同的类型,那么mData的size是50,mItemLayouts的size是5.
如前面所说,我对getViewTypeCount()和getItemCount()两个方法做了大量处理,使它们更易用。

在ItemAdapter中我还实现了一个ItemLayout内部类,这个类主要用来做二分查找,提升从mItemLayouts中查找ItemType的速度。

接下来我们还需实现ItemAdapter的getView方法。开头提到,为了便于开发,我们希望Adapter的getView只做管理工作,将真正的实现放入Item中。所以,我们先在Item.java中添加一个getView方法:
public View getView(View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = LayoutInflater.from(mContext).inflate(mResLayoutId, parent, false);
    }
    //TODO填充数据
    return convertView;
}
</pre><pre>
接下来ItemAdapter的getView方法只需通过Item的getView获取convertView就可以了:
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        final Item<?> item = this.getItem(position);
        if (item == null) {
            Log.e(TAG, "ERROR item is NULL");
        }
        mTempItemLayout = createItemLayout(item, mTempItemLayout);

        if (Collections.binarySearch(mItemLayouts, mTempItemLayout) < 0) {
            if (DEBUG) {
                Log.v(TAG, "ERROR getView binarySearch not found");
            }
            convertView = null;
        }

        return item.getView(convertView, parent);
    }

至此,我们通过ItemAdapter和Item两个类将复杂的Apdater给大大简化了。在初始化ItemAdapter时只需以下几行语句:

mItemAdapter = new ItemAdapter(this);
mItemAdapter.addItem(new ItemA(itemDataA));
mItemAdapter.addItem(new ItemB(itemDataB));
mItemAdapter.addItem(new ItemC(itemDataC));
mListView.setAdapter(mItemAdapter);
然后实现每种Item的getView方法就行了,剩下的事情ItemAdapter会自动帮我们处理。

2.托管Item的getView

有了前面的努力,至少代码看起来不那么乱了,现在我们还需要让代码更简单。
现在代码最多,最复杂的部分就是Item的getView()方法了,我们就从这里入手。
getView()中主要要做两件事:
一:加载布局
二:填充数据

加载布局每个Item的实现都一样,都是先判断convertView是否为Null,为Null 就inflate布局文件。正因为太重复,所以我们可以新建一个类,将这些重复的语句放入这个类中实现。
我给这个类起名叫ItemBuilder.java

public class ItemBuilder {

    private final Context mContext;

    private View mConvertView;

    private int mLayoutId = -1;

    public ItemBuilder(Context context, ViewGroup parent, int layoutId) {
        mContext = context;
        mConvertView = LayoutInflater.from(mContext).inflate(layoutId, parent, false);
        mConvertView.setTag(this);
        mLayoutId = layoutId;
    }
</pre><pre name="code" class="java" style="color: rgb(34, 34, 34); line-height: 19px;">    public View getView() {
        return mConvertView;
    }
}
在Item.java的getView() 方法中通过ItemBuilder返回 convertView:

    public View getView(View convertView, ViewGroup parent) {
        ItemBuilder builder = getAdapterBuilder(convertView, parent);
        return builder.getView();
    }

    protected ItemBuilder getAdapterBuilder(View convertView, ViewGroup parent) {
        if (convertView == null) {
            return new ItemBuilder(parent.getContext(), parent, mLayoutResId);
        }
        return (ItemBuilder) convertView.getTag();
    }

如果convertView是空我们就生成一个新的ItemBuilder,在ItemBuilder的构造函数中inflate布局文件生成convertView,并将ItemBuilder作为Tag设置到convertView上。下一次只需从convertView上getTag()便可得到ItemBuilder对象。

接下来,我们还需填充数据,相信大家已经发现,ItemBuilder已经被做成一个ValueHolder的雏形了。现在我希望ItemBuilder
可以像ValueHolder一样保存所有子view。同时我还希望可以通过ItemBuilder直接对这些子View进行数据填充。

首先,我在ItemBuilder中定义一个SparseArray<View> mViews,使用SparseArray来保存所有的子View。使用SparseArray的原因是可以使用View的id作为Key,SparseArray采用二分查找,速度不错还比HashMap省内存。
    private final SparseArray<View> mViews;
因为希望通过ItemBuilder直接填充数据,因此在ItemBuilder中,还需提供了大量的setXXX()方法来对子View做填充:
    public ItemBuilder setImageResource(int viewId, int imageResId) {
        ImageView view = retrieveView(viewId);
        view.setImageResource(imageResId);
        return this;
    }

    public ItemBuilder setImageBitmap(int viewId, Bitmap bm) {
        ImageView view = retrieveView(viewId);
        view.setImageBitmap(bm);
        return this;
    }

    private <T extends View> T retrieveView(int viewId) {

        if (mLastViewId == viewId && mLastView != null) {
            return (T) mLastView;
        }
        View view = mViews.get(viewId);
        if (view == null) {
            view = mConvertView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        setLastView(view, viewId);
        if (mLastView == null)
            android.util.Log.i("xzy", "!!!!!mLastView is : null layout is: " + mLayoutId);
        // return (T) view;
        return (T) mLastView;
    }

如上,填充一个View控件时,只需要传入该控件的id,和需要填充的数据。setXXX()方法执行时会将view控件放入ItemBuilder的mViews中进行管理。如果把我们的ItemBuilder看成是ValueHolder,这个ValueHolder就是一个变长的ValueHolder,其中的View数据
可以动态增加,动态管理。


框架已经搭好了,最后,还需要在Item中添加一个convert()方法,将ItemBuilder暴露给开发者,让开发者在convert()方法中填充数据:
public abstract boolean convert(ItemBuilder builder, ViewGroup parent, T data);

更新一下Item的getView(),需要在这调用convert()方法:
    public View getView(View convertView, ViewGroup parent) {
        ItemBuilder builder = getAdapterBuilder(convertView, parent);
        convert(builder, parent, getData());
        return builder.getView();
    }

现在开发者在实现Item时,无需关心getView方法和valueHolder模式了,只需要实现convert方法,通过convert方法传下来的
ItemBuilder和data进行数据填充就行了,并且数据填充非常简单,用起来和AlertDialogBuilder类似:
builder.setImageResource(R.id.image_view, data);
:-)只需一行代码便可创建好ValueHolder并填充完数据

最后,我们的代码看起来是这样的:

Activity中代码:
mListView = (ListView) this.findViewById(R.id.listview);
        mItemAdapter = new ItemAdapter(this);
        mItemAdapter.addItem(new PicItem(mDrawableId[i], R.layout.pic_items));
        mListView.setAdapter(mItemAdapter);
 PicItem.java代码如下:
public class PicItem extends Item<Integer> {

    public PicItem(Integer t, int layoutResId) {
        super(t, layoutResId);
    }

    @Override
    public boolean convert(ItemBuilder builder, ViewGroup parent, Integer data) {
        builder.setImageResource(R.id.image_view, data);
        return true;
    }

}

就这么简单 ,支持多ItemType,采用ValueHolder模式进行性能优化。再也不用担心周末没空约会了^_^。

语文能力有限,没看明白还请看代码:
ItemBuilder部分参考了以下项目:https://github.com/JoanZapata/base-adapter-helper



以上。


作者:xzy2046,转载需注明。博客主页:http://blog.csdn.net/xzy2046






评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值