简介
如果让你实现一个如下图所示的复杂的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;
}
可以动态增加,动态管理。
框架已经搭好了,最后,还需要在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();
}
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