简介
如果让你实现一个如下图所示的复杂的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。
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;
}
}
<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的类型。
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时只需以下几行语句:
2.托管Item的getView
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();
}
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;
}
public abstract boolean convert(ItemBuilder builder, ViewGroup parent, T data);
public View getView(View convertView, ViewGroup parent) {
ItemBuilder builder = getAdapterBuilder(convertView, parent);
convert(builder, parent, getData());
return builder.getView();
}
builder.setImageResource(R.id.image_view, data);
:-)只需一行代码便可创建好ValueHolder并填充完数据
。
最后,我们的代码看起来是这样的:
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模式进行性能优化。再也不用担心周末没空约会了^_^。