本文已授权微信公众号 [Android技术经验分享] 独家发布
转载请注明出处 VLayout适配器的万能封装
前言
传统的RecyclerView高级应用,还是挺麻烦的,阿里开源了Vlayout,采用代理模式独立承担各式各样的布局,大大的减少了程序媛的工作量,阿弥陀佛。
本片文章主要是针对Vlayout的DelegateAdapter.Adapter封装的VBaseAdapter介绍,“一行”代码搞定各式各样的布局,同时独立的VBaseHolder解耦各个模块的逻辑代码,思路清晰,减少代码量
当然,这个封装并没有什么技术含量,也是用之前网上的万能BaseRecyclerAdapter稍加改造的。
封装
1效果图
用封装后的VBaseAdapter写了个奇丑无比的界面,大家将就着看吧
2 关于VLayout
VLayout全称VirtualLayout,通过DelegateAdapter去管理不同的Adapter,实现布局的多饰多样化.
开源后无论是官方文档和网上的教程讲的都很详尽,我这里就不过多的阐述了,当然,我也是看各路大神的博客学会的,谢谢他们,谢谢阿里,合十感恩!如果你还不了解什么是VLayout的话,先去看看这些文章,不然我这篇你看了也白看!留下几个传送门:
·一步步教你实现完整的复杂列表布局
· android VLayout 全面解析
·VLayout:淘宝、天猫都在用的UI框架,赶紧用起来吧!
3封装
VBaseAdapter
先把源码贴出来,可以通过链式创建,也可以通过构造方法创建adapter,主要参数要布局id,回调mListener,viewholder,数据源mDatas等。为了更直观点,后面结合VBaseHoler一起讲用法。
public class VBaseAdapter<T> extends DelegateAdapter.Adapter<VBaseHolder<T>> {//上下文private Context mContext;//布局文件资源IDprivate int mResLayout;private VirtualLayoutManager.LayoutParams mLayoutParams;//数据源private List<T> mDatas;//布局管理器private LayoutHelper mLayoutHelper;//继承VBaseHolder的Holderprivate Class<? extends VBaseHolder> mClazz;//回调监听private ItemListener mListener;public VBaseAdapter(Context context) {mContext = context;}/*** <br/> 方法名称:VBaseAdapter* <br/> 方法详述:构造函数* <br/> 参数:<同上申明>*/public VBaseAdapter(Context context, List<T> mDatas, int mResLayout, Class<? extends VBaseHolder> mClazz,LayoutHelper layoutHelper, ItemListener listener) {if (mClazz == null) {throw new RuntimeException("clazz is null,please check your params !");}if (mResLayout == 0) {throw new RuntimeException("res is null,please check your params !");}this.mContext = context;this.mResLayout = mResLayout;this.mLayoutHelper = layoutHelper;this.mClazz = mClazz;this.mListener = listener;this.mDatas = mDatas;//this.mLayoutParams = new VirtualLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);}/*** <br/> 方法名称: VBaseAdapter* <br/> 方法详述: 设置数据源* <br/> 参数: mDatas,数据源* <br/> 返回值: VBaseAdapter*/public VBaseAdapter setData(List<T> mDatas) {this.mDatas = mDatas;return this;}/*** <br/> 方法名称: setItem* <br/> 方法详述: 设置单个数据源* <br/> 参数: mItem,单个数据源* <br/> 返回值: VBaseAdapter*/public VBaseAdapter setItem(T mItem) {this.mDatas.add(mItem);return this;}/*** <br/> 方法名称: setLayout* <br/> 方法详述: 设置布局资源ID* <br/> 参数: mResLayout, 布局资源ID* <br/> 返回值: VBaseAdapter*/public VBaseAdapter setLayout(int mResLayout) {if (mResLayout == 0) {throw new RuntimeException("res is null,please check your params !");}this.mResLayout = mResLayout;return this;}/*** <br/> 方法名称: setLayoutHelper* <br/> 方法详述: 设置布局管理器* <br/> 参数: layoutHelper,管理器* <br/> 返回值: VBaseAdapter*/public VBaseAdapter setLayoutHelper(LayoutHelper layoutHelper) {this.mLayoutHelper = layoutHelper;return this;}/*** <br/> 方法名称: setHolder* <br/> 方法详述: 设置holder* <br/> 参数: mClazz,集成VBaseHolder的holder* <br/> 返回值: VBaseAdapter*/public VBaseAdapter setHolder(Class<? extends VBaseHolder> mClazz) {if (mClazz == null) {throw new RuntimeException("clazz is null,please check your params !");}this.mClazz = mClazz;return this;}/*** <br/> 方法名称: setListener* <br/> 方法详述: 传入监听,方便回调* <br/> 参数: listener* <br/> 返回值: VBaseAdapter*/public VBaseAdapter setListener(ItemListener listener) {this.mListener = listener;return this;}/*** <br/> 方法名称: onCreateLayoutHelper* <br/> 方法详述: 继承elegateAdapter.Adapter后重写方法,告知elegateAdapter.Adapter使用何种布局管理器* <br/> 参数:* <br/> 返回值: VBaseAdapter*/@Overridepublic LayoutHelper onCreateLayoutHelper() {return mLayoutHelper;}public HashMap<Integer, Object> tags = new HashMap<>();/*** <br/> 方法名称: setTag* <br/> 方法详述: 设置mObject* <br/> 参数: mObject* <br/> 返回值: VBaseAdapter*/public VBaseAdapter setTag(int tag, Object mObject) {if (mObject != null) {tags.put(tag, mObject);}return this;}/*** <br/> 方法名称: onCreateViewHolder* <br/> 方法详述: 解析布局文件,返回传入holder的构造器*/@Overridepublic VBaseHolder<T> onCreateViewHolder(ViewGroup parent, int viewType) {View view = Constants.inflate(parent.getContext(), parent, mResLayout);if (tags != null && tags.size() > 0) {for (int tag : tags.keySet()) {view.setTag(tag, tags.get(tag));}}try {Constructor<? extends VBaseHolder> mClazzConstructor = mClazz.getConstructor(View.class);if (mClazzConstructor != null) {return mClazzConstructor.newInstance(view);}} catch (Exception e) {e.printStackTrace();}return null;}/*** <br/> 方法名称: onBindViewHolder* <br/> 方法详述: 绑定数据* <br/> 参数:* <br/> 返回值: VBaseAdapter*/@Overridepublic void onBindViewHolder(VBaseHolder holder, int position) {holder.setListener(mListener);holder.setContext(mContext);holder.setData(position, mDatas.get(position));}@Overridepublic int getItemCount() {return mDatas.size();}}
看看源码,DelegateAdapter.Adapter需要传入一个泛型holder,这刚好满足了我们的条件,所以,我们的VBaseHolder就恰逢其时的运用上了。

VBaseHolder
public class VBaseHolder<T> extends RecyclerView.ViewHolder {public ItemListener mListener;public Context mContext;public View mView;public T mData;public int position;public VBaseHolder(View itemView) {super(itemView);mView = itemView;ButterKnife.bind(this, itemView);init();}public void init() {}public void setContext(Context context) {mContext = context;}public void setListener(ItemListener listener) {mListener = listener;}public void setData(int ps, T mData) {this.mData = mData;position = ps;}}
VBaseHolder中方法和参数,都是public类型,方便子类在集成后,可以直接使用数据。
封装原理
无论是VbaseAdapter还是VBaseHolder,都是用了泛型T,这是万能封装的基础。为了数据源的统一性,传入数据都是List类型(也可设置单个数据),而T则为具体对象,当然,ItemListener 中也使用了泛型,与此类型一致,具体看Demo,此处就不再贴出。
首先我们来看onCreateViewHolder方法
@Overridepublic VBaseHolder<T> onCreateViewHolder(ViewGroup parent, int viewType) {View view = Constants.inflate(parent.getContext(), parent, mResLayout);if (tags != null && tags.size() > 0) {for (int tag : tags.keySet()) {view.setTag(tag, tags.get(tag));}}try {Constructor<? extends VBaseHolder> mClazzConstructor = mClazz.getConstructor(View.class);if (mClazzConstructor != null) {return mClazzConstructor.newInstance(view);}} catch (Exception e) {e.printStackTrace();}return null;}
从代码中不难看出,先解析了xml布局文件创建视图,然后将我们传入的holder获得构造器,返回holder实例
其次,看看onBindViewHolder方法:
@Overridepublic void onBindViewHolder(VBaseHolder holder, int position) {holder.setListener(mListener);holder.setContext(mContext);holder.setData(position, mDatas.get(position));}
此处我们不做任何的逻辑处理,只是为holder传入了监听,上下文,position和单个数据源。
使用
高潮来了,现在就该告知如何使用了,相信各位都是大神,直接贴代码吧
waterfallAdapter = new VBaseAdapter(mContext)//.setData(new ArrayList<WaterCargo>())//.setLayout(R.layout.recyc_water)//.setHolder(WaterHolder.class)//.setLayoutHelper(getWaterHelper())//.setListener(new ItemListener<WaterCargo>() {@Overridepublic void onItemClick(View view, int position, WaterCargo mData) {Toast.makeText(mContext, "瀑布流布局,只卖" + mData.getPrice(), Toast.LENGTH_SHORT).show();}});
getWaterHelper()获取布局管理器
getWaterHelper() {StaggeredGridLayoutHelper staggerHelper = new StaggeredGridLayoutHelper(2, 8);staggerHelper.setMargin(0, 20, 0, 10);return staggerHelper;}
此处我选择创建一个瀑布流,setData先传入都是一个size为0的list,setLayout传入的是item的布局,setLayoutHelper传入一个布局管理器,setHolder传入继承了VBaseHolder的 WaterHolder,
那我们在看看WaterHolder的代码
public class WaterHolder extends VBaseHolder<WaterCargo> {@BindView(R.id.pic) ImageView mPic;@BindView(R.id.title) TextView mTitle;@BindView(R.id.price) TextView mPrice;@BindView(R.id.num) TextView mNum;public WaterHolder(View itemView) {super(itemView);}@Overridepublic void setData(int ps, WaterCargo mData) {super.setData(ps, mData);//随机设置item高度实现瀑布流效果ViewGroup.LayoutParams params = mPic.getLayoutParams();params.width = ScreenUtil.getScreenWidth(mContext) / 2 - 2;params.height = ScreenUtil.getScreenHeight(mContext) / 4 + (int)(Math.random()*100);Glide.with(mContext).load(mData.getPic_url()).centerCrop().error(R.mipmap.ic_launcher).diskCacheStrategy(DiskCacheStrategy.ALL).into(mPic);mTitle.setText(mData.getTitle());mPrice.setText("¥ " + mData.getPrice());mNum.setText(mData.getBuynum() + "人购买");}@Overridepublic void init() {super.init();mView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mListener.onItemClick(mView, position, mData);}});}}
重写父类VBaseHolder的init方法和setData方法,进行初始化监听和数据绑定,此处建议,WaterHolder的构造函数中,别写一行代码,初始化放到init中,数据绑定放在setData中去。
到此位置,差不多完了。我也是按照之前的万能Adapter稍加改造而来,没啥技术含量,多看几遍demo就懂了。
总结
- 整个封装的基础,是对泛型的合理应用。相信很多大哥们也在用万能Adapter,瞄一眼就够了
- 此封装还有很多明显的不足,各位大哥可自行扩展和完善
- VLayout的强大超乎你的想想,会用上瘾的
- GridLayoutHelper有Bug,在自定义设置个别item占比时候,重写setSpanSizeLookup方法,position的位置不对,log打印出来很吓人,Demo中haohuoAdapter,设置第一个item占一整行,positon不应该为0吗?而且数据源一共才7个,但为什么会从13开始?而且,log为什么前后打印了一百多次?表示费解!或许不是bug,可能是我哪儿理解错误了

