首页作为app的访问最多的页面,注定是拥有丰富的ui元素,要驾驭这么多的ui元素并不影响性能的话,listview是个非常适合的选择,问题是不同的元素需要的数据结构体也不一样,item的布局文件也不一样。该如何处理呢
这是万能适配器应运而生了,主要思路是通过一个map来记录list中元素的种类,之后就是让对应的item都继承于同一个接口,统一调用adapter的getView方法进行模版化的渲染动作,具体的渲染交给各种item的方法来进行,在初始化的时候,主需要传递数据集和对应的ui布局文件就可以了。
核心代码
MultiCollectionAdapter
public abstract class MultiCollectionAdapter extends BaseAdapter {
final SparseArray<List> mMapOfTypeViewList;
final SparseIntArray mMapOfLayoutIdType;
public MultiCollectionAdapter() {
mMapOfTypeViewList = new SparseArray<>(10);
mMapOfLayoutIdType = new SparseIntArray(10);
}
public MultiCollectionAdapter(SparseArray<List> 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<List>,SparseArray<Integer>)");
}
}
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<List> 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 SparseArray<View> mViews;
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<View>();
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 extends View> 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(SparseArray<List> mapOfTypeViewList, 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<T> {
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<SelectorInfo> {
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 SparseArray<List> generateDataCollection() {
SparseArray<List> 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;
}
刷新的用法和平常的用法一样