首页作为app的访问最多的页面,注定是拥有丰富的ui元素,要驾驭这么多的ui元素并不影响性能的话,listview是个非常适合的选择,问题是不同的元素需要的数据结构体也不一样,item的布局文件也不一样。该如何处理呢
这是万能适配器应运而生了,主要思路是通过一个map来记录list中元素的种类,之后就是让对应的item都继承于同一个接口,统一调用adapter的getView方法进行模版化的渲染动作,具体的渲染交给各种item的方法来进行,在初始化的时候,主需要传递数据集和对应的ui布局文件就可以了。
核心代码
MultiCollectionAdapter
public abstract class MultiCollectionAdapter extends BaseAdapter { final SparseArraymMapOfTypeViewList; final SparseIntArray mMapOfLayoutIdType; public MultiCollectionAdapter() { mMapOfTypeViewList = new SparseArray<>(10); mMapOfLayoutIdType = new SparseIntArray(10); } public MultiCollectionAdapter(SparseArray
mapOfTypeViewList,SparseIntArray mapOfLayoutIdType) { this.mMapOfTypeViewList = mapOfTypeViewList; this.mMapOfLayoutIdType = mapOfLayoutIdType; } public int getLayoutId(int position) { if (null != mMapOfLayoutIdType && mMapOfLayoutIdType.size() != 0) { int itemViewType = getItemViewType(position); return mMapOfLayoutIdType.get(itemViewType); } else { throw new IllegalStateException("please override "+getClass().getName()+".getLayoutId or build by "+getClass().getName()+"(SparseArray
,SparseArray
)"); } } public void putLayoutId(int key, int layoutId) { mMapOfLayoutIdType.put(key, layoutId); } public void updateData(int key,List list) { mMapOfTypeViewList.put(key, list); notifyDataSetChanged(); } public void removeData(int key) { mMapOfTypeViewList.delete(key); notifyDataSetChanged(); } public void appendData(int key, List list) { mMapOfTypeViewList.append(key, list); notifyDataSetChanged(); } @Override public int getCount() { int size = 0; for(int i = 0; i< mMapOfTypeViewList.size(); i++ ) { int key = mMapOfTypeViewList.keyAt(i); List list = mMapOfTypeViewList.get(key); size += getSizeOfListValue(list); } return size; } @Override public int getItemViewType(int position) { int prt = 0; int type = 0; for(int i = 0; i < mMapOfTypeViewList.size(); i++) { int key = mMapOfTypeViewList.keyAt(i); List list = mMapOfTypeViewList.get(key) ; prt += getSizeOfListValue(list); if (prt > position) { type = key; break; } } return type; } @Override public Object getItem(int position) { int size = 0; List list = null; int i = 0; for(; i < mMapOfTypeViewList.size(); i++) { int key = mMapOfTypeViewList.keyAt(i); list = mMapOfTypeViewList.get(key); size += getSizeOfListValue(list); if (size -1 >= position) { break; } } int p = getSizeOfListValue(list) - size + position; if (null!=list && 0 != list.size()) { return list.get(p); } return null; } protected int getSizeOfListValue(List list) { if(null!=list) return list.size(); else return 1; } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { final CommonViewHolder viewHolder = getViewHolder(position, convertView, parent); convert(viewHolder, getItem(position)); View view = viewHolder.getConvertView(); return view; } public abstract void convert(CommonViewHolder helper, Object item); public boolean useCustomListSelector() { return false; } public SparseArray getData() { return mMapOfTypeViewList; } private CommonViewHolder getViewHolder(int position, View convertView, ViewGroup parent) { return CommonViewHolder.get(convertView, parent, getLayoutId(position), position); } private MultiLayoutAdapter.OnItemClickListener mItemClickListener; public void setOnItemClickListener(MultiLayoutAdapter.OnItemClickListener listener) { mItemClickListener = listener; } public interface OnItemClickListener{ void onItemClick(int position, Object data, View itemView); }}
万能适配器负责管理两个map,一个mMapOfLayoutIdType是元素类型列表,记录list中的所有类型
另一个是mMapOfTypeViewList,记录各个类型对应的数据集,相当一个二维数组,第一维是layoutId,第二层是元素对应的数据集list
要做到适配不同的item,必须重写getItem方法(从list获取item数据)和getItemViewType方法
因为只有position这个参数可用,所以需要通过判断当前position落于哪一段,才能拿到正确的类型
值得注意的是getSizeOfListValue方法默认返回一个1,因为之后的判断会进行减
适配器最重要的方法莫过于getView
这里只是一个模版代码
final CommonViewHolder viewHolder = getViewHolder(position, convertView, parent); convert(viewHolder, getItem(position)); View view = viewHolder.getConvertView();
convert方法是一个抽象方法,子类必须实现这个方法,相当于baseAdapter的getView方法,不过这里封装成传递的参数是更为实用的CommenViewHolder和对应的数据结构体(item的)
CommenViewHolder可以理解为封装好的ui的管理器,本身不是view,但只能通过
View convertView = helper.getConvertView();
获取converView
public class CommonViewHolder { private final SparseArraymViews; private final int mLayoutId; private int mPosition; private final View mConvertView; public int getLayoutId() { return mLayoutId; } private CommonViewHolder(ViewGroup parent, int layoutId, int position) { this.mPosition = position; this.mViews = new SparseArray (); this.mLayoutId = layoutId; // TODO mConvertView = LayoutInflater.from(parent.getContext()).inflate(layoutId, null); mConvertView.setTag(this); } public static CommonViewHolder get(View convertView, ViewGroup parent, int layoutId, int position) { CommonViewHolder holder = null; if (convertView == null || ((CommonViewHolder) convertView.getTag()).getLayoutId() != layoutId) { holder = new CommonViewHolder(parent, layoutId, position); } else { holder = (CommonViewHolder) convertView.getTag(); holder.mPosition = position; } return holder; } public View getConvertView() { return mConvertView; } @SuppressWarnings("unchecked") public T getView(int viewId) { View view = mViews.get(viewId); if (view == null) { view = mConvertView.findViewById(viewId); mViews.put(viewId, view); } return (T) view; } public CommonViewHolder setText(int viewId, String text) { TextView view = getView(viewId); view.setText(text); return this; } public CommonViewHolder setImageResource(int viewId, int drawableId) { ImageView view = getView(viewId); if (view != null) { view.setImageResource(drawableId); } return this; } public CommonViewHolder setImageBitmap(int viewId, Bitmap bm) { ImageView view = getView(viewId); view.setImageBitmap(bm); return this; } public int getPosition() { return mPosition; }}
通用viewholder,需要传入layoutId初始化,,初始化的时候,每个mConverView都会被打标签,然后在get方法,通过标签获取view,如果为空,则再new一个,new的同时也把标签打好了,由此做到复用converView,这种设计把复用的代码搬到CommenViewHolder中进行,有针对性。毕竟adapter主要处理的是数据,ui方面的处理交给CommenViewHolder处理就可以了。
private class MyListViewAdapter extends MultiCollectionAdapter { static final int TYPE_HEAD_SELECTOR = 0; static final int TYPE_SELECTOR = 1; static final int TYPE_LAST_ITEM = 6; RecommendListViewAdapter(SparseArraymapOfTypeViewList, SparseIntArray mapOfLayoutIdType) { super(mapOfTypeViewList, mapOfLayoutIdType); } @Override public void convert(CommonViewHolder helper, Object item) { View convertView = helper.getConvertView(); if (convertView instanceof HolderViewListItem) { ((HolderViewListItem) convertView).setup(item); } } }
适配器的导出类很简单,只需要重写convert方法就可以了,需要注意的是,convert方法里面也是模版代码
具体的实现交给对应的item类处理即可,调用item的setUp方法
关于我一值提到的item,就是真正意义上的ui实现类,继承于Viewgroup,并实现接口HolderViewListItem
public interface HolderViewListItem{ void setup(T obj); void onItemClick(View.OnClickListener listener);}
为什么item都要实现这个接口呢,因为convert方法里面用到的是模版代码,获取的都是HolderViewListItem类型,并且使用了setup方法,这种方式,非常灵活,item可以根据需要实现多个接口,但各个接口之间并不影响,执行
((HolderViewListItem) convertView).setup(item);方法的时候,编辑器会通过向下转型自动找到对应的实现类,并准确得调用setup方法,这也是接口的基本用法。对外提供统一接口,运行时解析成具体的方法,统一封装代码,降低耦合性,各司其职。
现在来看看item的实现类
public class MyListItem extends RelativeLayout implements HolderViewListItem{ private TextView mText; public MyListItem (Context context) { super(context); } public MyListItem (Context context, AttributeSet attrs) { super(context, attrs); } public MyListItem (Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } public static MyListItem newInstance(Context context) { return (MyListItem ) LayoutInflater.from(context).inflate(R.layout.item_main_my_list_item, null); } private void initView() { mText = (TextView) findViewById(R.id.main_item_text); } @Override public void setup(final MyInfo data) { initView(); mText.setText(data.modelName); } @Override public void onItemClick(OnClickListener listener) { setOnClickListener(listener); }}
item的布局文件里面,根目录也是这个类的引用,在这里就不列出来了
以上只是零部件,接下来看看如何组装
实例化万能适配器
mAdapter = new ListViewAdapter(generateDataCollection(), generateLayoutIdCollection());
private SparseArraygenerateDataCollection() { SparseArray
map = new SparseArray<>(10); map.put(MyListViewAdapter.TYPE_HEAD_SELECTOR, null); map.append(MyListViewAdapter.TYPE_SELECTOR, selectorData); return map; } private SparseIntArray generateLayoutIdCollection() { SparseIntArray map = new SparseIntArray(10); map.put(MyListViewAdapter.TYPE_HEAD_SELECTOR, R.layout.widget_main_selector_head); map.append(MyListViewAdapter.TYPE_SELECTOR, R.layout.item_main_selector); return map; }
刷新的用法和平常的用法一样