本文已授权微信公众号 [Android技术经验分享] 独家发布

转载请注明出处 VLayout适配器的万能封装

前言

传统的RecyclerView高级应用,还是挺麻烦的,阿里开源了Vlayout,采用代理模式独立承担各式各样的布局,大大的减少了程序媛的工作量,阿弥陀佛。

本片文章主要是针对Vlayout的DelegateAdapter.Adapter封装的VBaseAdapter介绍,“一行”代码搞定各式各样的布局,同时独立的VBaseHolder解耦各个模块的逻辑代码,思路清晰,减少代码量

当然,这个封装并没有什么技术含量,也是用之前网上的万能BaseRecyclerAdapter稍加改造的。

封装

1效果图

用封装后的VBaseAdapter写了个奇丑无比的界面,大家将就着看吧
VLayout适配器的万能封装 - 图1

2 关于VLayout

VLayout全称VirtualLayout,通过DelegateAdapter去管理不同的Adapter,实现布局的多饰多样化.
VLayout适配器的万能封装 - 图2

开源后无论是官方文档和网上的教程讲的都很详尽,我这里就不过多的阐述了,当然,我也是看各路大神的博客学会的,谢谢他们,谢谢阿里,合十感恩!如果你还不了解什么是VLayout的话,先去看看这些文章,不然我这篇你看了也白看!留下几个传送门:

·一步步教你实现完整的复杂列表布局

· android VLayout 全面解析

·VLayout:淘宝、天猫都在用的UI框架,赶紧用起来吧!

3封装

VBaseAdapter

先把源码贴出来,可以通过链式创建,也可以通过构造方法创建adapter,主要参数要布局id,回调mListener,viewholder,数据源mDatas等。为了更直观点,后面结合VBaseHoler一起讲用法。

  1. public class VBaseAdapter<T> extends DelegateAdapter.Adapter<VBaseHolder<T>> {
  2. //上下文
  3. private Context mContext;
  4. //布局文件资源ID
  5. private int mResLayout;
  6. private VirtualLayoutManager.LayoutParams mLayoutParams;
  7. //数据源
  8. private List<T> mDatas;
  9. //布局管理器
  10. private LayoutHelper mLayoutHelper;
  11. //继承VBaseHolder的Holder
  12. private Class<? extends VBaseHolder> mClazz;
  13. //回调监听
  14. private ItemListener mListener;
  15. public VBaseAdapter(Context context) {
  16. mContext = context;
  17. }
  18. /**
  19. * <br/> 方法名称:VBaseAdapter
  20. * <br/> 方法详述:构造函数
  21. * <br/> 参数:<同上申明>
  22. */
  23. public VBaseAdapter(Context context, List<T> mDatas, int mResLayout, Class<? extends VBaseHolder> mClazz,
  24. LayoutHelper layoutHelper, ItemListener listener) {
  25. if (mClazz == null) {
  26. throw new RuntimeException("clazz is null,please check your params !");
  27. }
  28. if (mResLayout == 0) {
  29. throw new RuntimeException("res is null,please check your params !");
  30. }
  31. this.mContext = context;
  32. this.mResLayout = mResLayout;
  33. this.mLayoutHelper = layoutHelper;
  34. this.mClazz = mClazz;
  35. this.mListener = listener;
  36. this.mDatas = mDatas;
  37. //this.mLayoutParams = new VirtualLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
  38. }
  39. /**
  40. * <br/> 方法名称: VBaseAdapter
  41. * <br/> 方法详述: 设置数据源
  42. * <br/> 参数: mDatas,数据源
  43. * <br/> 返回值: VBaseAdapter
  44. */
  45. public VBaseAdapter setData(List<T> mDatas) {
  46. this.mDatas = mDatas;
  47. return this;
  48. }
  49. /**
  50. * <br/> 方法名称: setItem
  51. * <br/> 方法详述: 设置单个数据源
  52. * <br/> 参数: mItem,单个数据源
  53. * <br/> 返回值: VBaseAdapter
  54. */
  55. public VBaseAdapter setItem(T mItem) {
  56. this.mDatas.add(mItem);
  57. return this;
  58. }
  59. /**
  60. * <br/> 方法名称: setLayout
  61. * <br/> 方法详述: 设置布局资源ID
  62. * <br/> 参数: mResLayout, 布局资源ID
  63. * <br/> 返回值: VBaseAdapter
  64. */
  65. public VBaseAdapter setLayout(int mResLayout) {
  66. if (mResLayout == 0) {
  67. throw new RuntimeException("res is null,please check your params !");
  68. }
  69. this.mResLayout = mResLayout;
  70. return this;
  71. }
  72. /**
  73. * <br/> 方法名称: setLayoutHelper
  74. * <br/> 方法详述: 设置布局管理器
  75. * <br/> 参数: layoutHelper,管理器
  76. * <br/> 返回值: VBaseAdapter
  77. */
  78. public VBaseAdapter setLayoutHelper(LayoutHelper layoutHelper) {
  79. this.mLayoutHelper = layoutHelper;
  80. return this;
  81. }
  82. /**
  83. * <br/> 方法名称: setHolder
  84. * <br/> 方法详述: 设置holder
  85. * <br/> 参数: mClazz,集成VBaseHolder的holder
  86. * <br/> 返回值: VBaseAdapter
  87. */
  88. public VBaseAdapter setHolder(Class<? extends VBaseHolder> mClazz) {
  89. if (mClazz == null) {
  90. throw new RuntimeException("clazz is null,please check your params !");
  91. }
  92. this.mClazz = mClazz;
  93. return this;
  94. }
  95. /**
  96. * <br/> 方法名称: setListener
  97. * <br/> 方法详述: 传入监听,方便回调
  98. * <br/> 参数: listener
  99. * <br/> 返回值: VBaseAdapter
  100. */
  101. public VBaseAdapter setListener(ItemListener listener) {
  102. this.mListener = listener;
  103. return this;
  104. }
  105. /**
  106. * <br/> 方法名称: onCreateLayoutHelper
  107. * <br/> 方法详述: 继承elegateAdapter.Adapter后重写方法,告知elegateAdapter.Adapter使用何种布局管理器
  108. * <br/> 参数:
  109. * <br/> 返回值: VBaseAdapter
  110. */
  111. @Override
  112. public LayoutHelper onCreateLayoutHelper() {
  113. return mLayoutHelper;
  114. }
  115. public HashMap<Integer, Object> tags = new HashMap<>();
  116. /**
  117. * <br/> 方法名称: setTag
  118. * <br/> 方法详述: 设置mObject
  119. * <br/> 参数: mObject
  120. * <br/> 返回值: VBaseAdapter
  121. */
  122. public VBaseAdapter setTag(int tag, Object mObject) {
  123. if (mObject != null) {
  124. tags.put(tag, mObject);
  125. }
  126. return this;
  127. }
  128. /**
  129. * <br/> 方法名称: onCreateViewHolder
  130. * <br/> 方法详述: 解析布局文件,返回传入holder的构造器
  131. */
  132. @Override
  133. public VBaseHolder<T> onCreateViewHolder(ViewGroup parent, int viewType) {
  134. View view = Constants.inflate(parent.getContext(), parent, mResLayout);
  135. if (tags != null && tags.size() > 0) {
  136. for (int tag : tags.keySet()) {
  137. view.setTag(tag, tags.get(tag));
  138. }
  139. }
  140. try {
  141. Constructor<? extends VBaseHolder> mClazzConstructor = mClazz.getConstructor(View.class);
  142. if (mClazzConstructor != null) {
  143. return mClazzConstructor.newInstance(view);
  144. }
  145. } catch (Exception e) {
  146. e.printStackTrace();
  147. }
  148. return null;
  149. }
  150. /**
  151. * <br/> 方法名称: onBindViewHolder
  152. * <br/> 方法详述: 绑定数据
  153. * <br/> 参数:
  154. * <br/> 返回值: VBaseAdapter
  155. */
  156. @Override
  157. public void onBindViewHolder(VBaseHolder holder, int position) {
  158. holder.setListener(mListener);
  159. holder.setContext(mContext);
  160. holder.setData(position, mDatas.get(position));
  161. }
  162. @Override
  163. public int getItemCount() {
  164. return mDatas.size();
  165. }}

看看源码,DelegateAdapter.Adapter需要传入一个泛型holder,这刚好满足了我们的条件,所以,我们的VBaseHolder就恰逢其时的运用上了。

VLayout适配器的万能封装 - 图3

VBaseHolder
  1. public class VBaseHolder<T> extends RecyclerView.ViewHolder {
  2. public ItemListener mListener;
  3. public Context mContext;
  4. public View mView;
  5. public T mData;
  6. public int position;
  7. public VBaseHolder(View itemView) {
  8. super(itemView);
  9. mView = itemView;
  10. ButterKnife.bind(this, itemView);
  11. init();
  12. }
  13. public void init() {
  14. }
  15. public void setContext(Context context) {
  16. mContext = context;
  17. }
  18. public void setListener(ItemListener listener) {
  19. mListener = listener;
  20. }
  21. public void setData(int ps, T mData) {
  22. this.mData = mData;
  23. position = ps;
  24. }}

VBaseHolder中方法和参数,都是public类型,方便子类在集成后,可以直接使用数据。

封装原理

无论是VbaseAdapter还是VBaseHolder,都是用了泛型T,这是万能封装的基础。为了数据源的统一性,传入数据都是List类型(也可设置单个数据),而T则为具体对象,当然,ItemListener 中也使用了泛型,与此类型一致,具体看Demo,此处就不再贴出。

首先我们来看onCreateViewHolder方法

  1. @Override
  2. public VBaseHolder<T> onCreateViewHolder(ViewGroup parent, int viewType) {
  3. View view = Constants.inflate(parent.getContext(), parent, mResLayout);
  4. if (tags != null && tags.size() > 0) {
  5. for (int tag : tags.keySet()) {
  6. view.setTag(tag, tags.get(tag));
  7. }
  8. }
  9. try {
  10. Constructor<? extends VBaseHolder> mClazzConstructor = mClazz.getConstructor(View.class);
  11. if (mClazzConstructor != null) {
  12. return mClazzConstructor.newInstance(view);
  13. }
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. }
  17. return null;
  18. }

从代码中不难看出,先解析了xml布局文件创建视图,然后将我们传入的holder获得构造器,返回holder实例

其次,看看onBindViewHolder方法:

  1. @Override
  2. public void onBindViewHolder(VBaseHolder holder, int position) {
  3. holder.setListener(mListener);
  4. holder.setContext(mContext);
  5. holder.setData(position, mDatas.get(position));
  6. }

此处我们不做任何的逻辑处理,只是为holder传入了监听,上下文,position和单个数据源。

使用

高潮来了,现在就该告知如何使用了,相信各位都是大神,直接贴代码吧

  1. waterfallAdapter = new VBaseAdapter(mContext)//
  2. .setData(new ArrayList<WaterCargo>())//
  3. .setLayout(R.layout.recyc_water)//
  4. .setHolder(WaterHolder.class)//
  5. .setLayoutHelper(getWaterHelper())//
  6. .setListener(new ItemListener<WaterCargo>() {
  7. @Override
  8. public void onItemClick(View view, int position, WaterCargo mData) {
  9. Toast.makeText(mContext, "瀑布流布局,只卖" + mData.getPrice(), Toast.LENGTH_SHORT).show();
  10. }
  11. });

getWaterHelper()获取布局管理器

  1. getWaterHelper() {
  2. StaggeredGridLayoutHelper staggerHelper = new StaggeredGridLayoutHelper(2, 8);
  3. staggerHelper.setMargin(0, 20, 0, 10);
  4. return staggerHelper;
  5. }

此处我选择创建一个瀑布流,setData先传入都是一个size为0的list,setLayout传入的是item的布局,setLayoutHelper传入一个布局管理器,setHolder传入继承了VBaseHolder的 WaterHolder,

那我们在看看WaterHolder的代码

  1. public class WaterHolder extends VBaseHolder<WaterCargo> {
  2. @BindView(R.id.pic) ImageView mPic;
  3. @BindView(R.id.title) TextView mTitle;
  4. @BindView(R.id.price) TextView mPrice;
  5. @BindView(R.id.num) TextView mNum;
  6. public WaterHolder(View itemView) {
  7. super(itemView);
  8. }
  9. @Override
  10. public void setData(int ps, WaterCargo mData) {
  11. super.setData(ps, mData);
  12. //随机设置item高度实现瀑布流效果
  13. ViewGroup.LayoutParams params = mPic.getLayoutParams();
  14. params.width = ScreenUtil.getScreenWidth(mContext) / 2 - 2;
  15. params.height = ScreenUtil.getScreenHeight(mContext) / 4 + (int)(Math.random()*100);
  16. Glide.with(mContext).load(mData.getPic_url()).centerCrop().error(R.mipmap.ic_launcher).diskCacheStrategy
  17. (DiskCacheStrategy.ALL).into(mPic);
  18. mTitle.setText(mData.getTitle());
  19. mPrice.setText("¥ " + mData.getPrice());
  20. mNum.setText(mData.getBuynum() + "人购买");
  21. }
  22. @Override
  23. public void init() {
  24. super.init();
  25. mView.setOnClickListener(new View.OnClickListener() {
  26. @Override
  27. public void onClick(View v) {
  28. mListener.onItemClick(mView, position, mData);
  29. }
  30. });
  31. }
  32. }

重写父类VBaseHolder的init方法和setData方法,进行初始化监听和数据绑定,此处建议,WaterHolder的构造函数中,别写一行代码,初始化放到init中,数据绑定放在setData中去。
到此位置,差不多完了。我也是按照之前的万能Adapter稍加改造而来,没啥技术含量,多看几遍demo就懂了。

总结

  1. 整个封装的基础,是对泛型的合理应用。相信很多大哥们也在用万能Adapter,瞄一眼就够了
  2. 此封装还有很多明显的不足,各位大哥可自行扩展和完善
  3. VLayout的强大超乎你的想想,会用上瘾的
  4. GridLayoutHelper有Bug,在自定义设置个别item占比时候,重写setSpanSizeLookup方法,position的位置不对,log打印出来很吓人,Demo中haohuoAdapter,设置第一个item占一整行,positon不应该为0吗?而且数据源一共才7个,但为什么会从13开始?而且,log为什么前后打印了一百多次?表示费解!或许不是bug,可能是我哪儿理解错误了

VLayout适配器的万能封装 - 图4
VLayout适配器的万能封装 - 图5

Demo地址