本文主要解决ListView RecyclerView ViewPager的Adapter统一规划和使用问题。
每次使用以上控件,都需要重写Adapter,很烦。尤其是条目类型很多的时候,Adapter异常丑陋,窝心。既然如此我们就解决一下吧。
思路
条目的展示样式都是跟着数据走的,和Adapter关系不大,那么就把条目数据和数据处理类提取出来,Adapter在其中只做控制和转发。
以ListView为例, 我们来开始逐步进行分析和完善。首先进行抽离,Adapter需要负责一下几个事项:
数据列表的持有;
View类型的计算;
数据和处理类的关联;
相关处理的转发;
我们首先要分析一下数据和处理类的结构,数据类除了正常的展示数据,还需要提供一个处理类的相关信息,这里我们要求直接将处理类的类名返回。数据类的接口如下:
1 2 3 4 5 6 public interface IDataType { /** * @return */ String getViewHandlerName(); }
有了数据类接口,那么处理类要做的事情也很简单,提供View的样式,将相关数据加载到View上,处理类接口如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface IViewHandler<H, T> { /** * * @return */ int getResId(); /** * * @param holder * @param positon * @param data * @param parent */ void handleView(H holder, int positon, T data, ViewGroup parent); }
其中 H为一个抽象泛型,负责View的初始化和持有,以ListView为例,H的具体内容为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ListViewHolder { private ViewFinder viewFinder; private View mItemView; private ListViewHolder(Context context, ViewGroup parent, int layoutId) { LayoutInflater inflater = LayoutInflater.from(context); mItemView = inflater.inflate(layoutId, parent, false); viewFinder = new ViewFinder(mItemView); mItemView.setTag(R.id.item_holder, this); } public View getItemView() { return mItemView; } public ViewFinder getViewFinder() { return viewFinder; } public static ListViewHolder get(Context context, View itemView, ViewGroup parent, int layoutId) { if (itemView == null) return new ListViewHolder(context, parent, layoutId); else return (ListViewHolder) itemView.getTag(R.id.item_holder); } }
有了IDataType和IViewHandler,还需要一个类来讲两者进行关联和加载,因为IDataType持有的是Handler的Name,所以这里需要用到反射,为了降低放射带来的性能消耗,需要我们对Handler进行缓存,于是就有了HandlerFactory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class ViewHandlerFactory { private static final String TAG = ViewHandlerFactory.class.getSimpleName(); /** * cached handler */ private static Map<String, IViewHandler> mHandlerMap = new ArrayMap<>(); /** * * @param viewHandlerClazz */ public static <T extends IViewHandler> T getViewHandler(String viewHandlerClazz) { IViewHandler result = mHandlerMap.get(viewHandlerClazz); if (result == null) { try { Class clazz = Class.forName(viewHandlerClazz); Assert.judge(IViewHandler.class.isAssignableFrom(clazz), "IViewHandler not implemented"); result = (IViewHandler) clazz.newInstance(); mHandlerMap.put(viewHandlerClazz, result); } catch (Exception e) { Log.e(TAG, "Exception!", e); } } if (result == null) throw new RuntimeException("IViewHandler create failed:" + viewHandlerClazz); return (T) result; } }
万事具备,接下来只需要在Adapter中将所有的东西关联起来就可以啦,以ListView为例,具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 public class LEasyAdapter<T extends IDataType> extends BaseAdapter { /** * ListView item type count */ private int typeCount; /** * auto generate item type */ private int itemIdOffset = 0; /** * cache item type */ private Map<String, Integer> itemTypeArr; private List<T> dataList; public LEasyAdapter(List<T> dataList) { this(dataList, 1); } public LEasyAdapter(List<T> dataList, int typeCount) { this.dataList = dataList; this.typeCount = typeCount; itemTypeArr = new ArrayMap<>(); } @Override public int getCount() { return dataList == null ? 0 : dataList.size(); } @Override public int getItemViewType(int position) { IViewHandler handler = ViewHandlerFactory.getViewHandler(getItem(position).getViewHandlerName()); Integer id = itemTypeArr.get(handler.getClass().getName()); if (id == null) { id = itemIdOffset++; itemTypeArr.put(handler.getClass().getName(), id); } return id; } @Override public int getViewTypeCount() { return typeCount; } @Override public long getItemId(int position) { return position; } @Override public T getItem(int position) { return dataList.get(position); } @Override public View getView(int position, View convertView, ViewGroup parent) { IViewHandler handler = ViewHandlerFactory.getViewHandler(getItem(position).getViewHandlerName()); ListViewHolder holder = ListViewHolder.get(parent.getContext(), convertView, parent, handler.getResId()); handler.handleView(holder, position, getItem(position), parent); return holder.getItemView(); } }
这里需要注意一点:对于ListView,需要提前制定ViewTypeCount,RecyclerView则不需要。
以上实现全部是以ListView为例来进行的梳理,对于RecyclerView,实现上也是大同小异。但是对于ViewPager,我们则需要不同的处理。ViewPagerAdapter是没有自己处理View缓存的,所以需要我们先在ViewPagerAdapter上包装一层,处理缓存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public abstract class CacheViewAdapter<T extends IDataType> extends PagerAdapter { public Map<Integer, List<View>> viewMap; public CacheViewAdapter() { viewMap = new HashMap<>(); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public Object instantiateItem(ViewGroup container, int position) { View view; List<View> viewList = viewMap.get(getItemViewType(position)); if (viewList == null) { viewList = new ArrayList<>(); viewMap.put(getItemViewType(position), viewList); } if (viewList.size() != 0) { view = getView(position, viewList.remove(0), container); } else { view = getView(position, null, container); } view.setTag(R.id.item_viewtype, getItemViewType(position)); container.addView(view); return view; } protected abstract int getItemViewType(int position); @Override public void destroyItem(ViewGroup container, int position, Object object) { View view = (View) object; container.removeView(view); int itemType = (int) view.getTag(R.id.item_viewtype); List<View> cacheList = viewMap.get(itemType); if (cacheList == null) { cacheList = new ArrayList<>(); viewMap.put(itemType, cacheList); } cacheList.add(view); } /** * 创建新的条目 * * @return */ public abstract View getView(int position, View convertView, ViewGroup parent); @Override public int getItemPosition(Object object) { return POSITION_NONE; } }
通过自己建立一个View的缓存Map,来管理缓存,这里需要注意的是,在destroyItem中我们没有根据position,获取data,然后判断ViewType,是和ViewPager自身实现有关的,当dataList更新后,destroyItem才会触发,根据position获取的datatype和view本身的viewtype是不对应的。导致出现view缓存错乱。
通过以上改造,我们后续使用以上控件的时候,不再需要创建新的Adapter类,只需要创建相关数据的Handler即可,代码解耦和复用也得到了提升。
具体代码请参考: Github 项目地址 EasyAdapter