As the most visited page of the app, the first page is destined to have rich ui elements. To control so many ui elements without affecting performance, listview is a good choice. The problem is that different elements require different data structures and item layout files.What to do?
This is the universal adapter, the main idea is to record the kinds of elements in the list through a map, then make the corresponding items inherit from the same interface, call adapter's getView method uniformly for templating rendering actions, specific rendering to various item methods, at the time of initializationWait, the master needs to pass the dataset and the corresponding ui layout file.
Core Code
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); } }
The Universal Adapter manages two map s, one mMapOfLayoutIdType is a list of element types, recording all types in the list
The other is the mMapOfTypeViewList, which records datasets of each type, equivalent to a two-dimensional array, with layoutId as the first dimension and a list of datasets corresponding to elements as the second layer
To accommodate different items, you must override the getItem method (getting item data from a list) and the getItemViewType method
Because only the position parameter is available, you need to determine which segment the current position falls in to get the correct type
It is worth noting that the getSizeOfListValue method returns a 1 by default because subsequent judgments are subtracted
The most important method for an adapter is getView
This is just a template code
final CommonViewHolder viewHolder = getViewHolder(position, convertView, parent); convert(viewHolder, getItem(position)); View view = viewHolder.getConvertView();
The convert method is an abstract method that subclasses must implement, which is equivalent to the baseAdapter's getView method, but the parameters encapsulated here are the more practical CommenViewHolder and the corresponding data structure (item's)
CommenViewHolder can be understood as an encapsulated ui manager, not view itself, but only through
View convertView = helper.getConvertView();
Get 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; } }
Universal viewholder, need to pass in layoutId initialization, when initialization, each mConverView will be labeled, then get the view through the tag in get method, if empty, then newone, newalso labeled, so that converView can be reused, this design will move the reused code into CommenViewHolder, targeted.After all, adapter mainly processes data, and the ui aspect is handled by 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); } } }
The export class of the adapter is simple, just override the convert method. Note that there is also template code inside the convert method
The specific implementation is handled by the corresponding item class and the setUp method of the item is called
The item I mentioned above is really a ui implementation class that inherits from Viewgroup and implements the interface HolderViewListItem
public interface HolderViewListItem<T> { void setup(T obj); void onItemClick(View.OnClickListener listener); }
Why does item implement this interface, because template code is used in the conversion method, HolderViewListItem type is obtained, and setup method is used. This method is very flexible, item can implement multiple interfaces as needed, but it does not affect the execution of each interface.
((HolderViewListItem) convertView).setup(item); when a method is used, the editor automatically finds the corresponding implementation class by transitioning down and calls the setup method exactly, which is also the basic use of the interface.Provides a unified interface to the outside world, resolves to specific methods at runtime, unifies encapsulation code, reduces coupling, and performs their respective duties.
Now let's look at the implementation class for 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); } }
The root directory in the layout file of item is also a reference to this class and is not listed here
Above are just parts, let's see how to assemble
Instantiate Universal Adapter
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; }
Refresh usage is the same as normal usage