VLayout适配器的万能封装

作者:牟仯

地址:http://www.jianshu.com/p/6383b182092e

声明:本文是牟仯原创,已获其授权发布,未经原作者允许请勿转载

一、前言

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

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

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

二、介绍

1 、效果图

用封装后的VBaseAdapter写了个奇丑无比的界面,大家将就着看吧

2、关于VLayout

VLayout全称VirtualLayout,通过DelegateAdapter去管理不同的Adapter,实现布局的多饰多样化。

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

(1)、一步步教你实现完整的复杂列表布局

http://blog.csdn.net/guanyingcao/article/details/75170921

(2)、android VLayout 全面解析

http://blog.csdn.net/sw950729/article/details/67634747

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

http://blog.csdn.net/carson_ho/article/details/71077193?winzoom=1&tn=96100419_hao_pg

三、封装

1、VBaseAdapter

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

    public class VBaseAdapter<T> extends DelegateAdapter.Adapter<VBaseHolder<T>> {
        //上下文
        private Context mContext;
       //布局文件资源ID
       private int mResLayout;
       private VirtualLayoutManager.LayoutParams mLayoutParams;
       //数据源
       private List<T> mDatas;
       //布局管理器
       private LayoutHelper mLayoutHelper;
       //继承VBaseHolder的Holder
       private 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
        */
       @Override
       public 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的构造器
        */
       @Override
       public 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
        */
       @Override
       public void onBindViewHolder(VBaseHolder holder, int position) {
           holder.setListener(mListener);
           holder.setContext(mContext);
           holder.setData(position, mDatas.get(position));
       }

       @Override
       public int getItemCount() {
           return mDatas.size();
       }}

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

2、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类型,方便子类在集成后,可以直接使用数据。

3、封装原理

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

首先我们来看onCreateViewHolder方法

@Override
public 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方法:

@Override
public void onBindViewHolder(VBaseHolder holder, int position) {
    holder.setListener(mListener);
    holder.setContext(mContext);
    holder.setData(position, mDatas.get(position));
}

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

4、使用

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

  waterfallAdapter = new VBaseAdapter(mContext)//
            .setData(new ArrayList<WaterCargo>())//
            .setLayout(R.layout.recyc_water)//
            .setHolder(WaterHolder.class)//
            .setLayoutHelper(getWaterHelper())//
            .setListener(new ItemListener<WaterCargo>() {
                @Override
                public 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);
    }

        @Override
        public 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() + "人购买");
}

        @Override
        public void init() {
            super.init();
            mView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mListener.onItemClick(mView, position, mData);
                }
            });
        }}      

重写父类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,可能是我哪儿理解错误了。

原文发布于微信公众号 - Android先生(cyg_24kshign)

原文发表时间:2017-09-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏蜉蝣禅修之道

android之listview使用方法(一)

1472
来自专栏向治洪

android之listview缓存图片(缓存优化)

网上关于这个方面的文章也不少,基本的思路是线程+缓存来解决。下面提出一些优化: 1、采用线程池 2、内存缓存+文件缓存 3、内存缓存中网上很多是采用SoftRe...

3559
来自专栏everhad

ViewPager无限滑动

前言 View轮播效果在app中很常见,一想到左右滑动的效果就很容易想到使用ViewPager来实现。对于像我们常说的banner这样的效果,具备无限滑动的功能...

4017
来自专栏10km的专栏

jface databinding:可多选的widget List组件selection项目与java.util.List对象的双向数据绑定

需求说明 如下图,一个可多选的List组件,初始表有3个值,希望实现与一个java.util.List对象(保存选中的值)的双向数据绑定。当List组件中选中的...

2458
来自专栏一直在跳坑然后爬坑

RxJava2操作符之“Buffer”

这里贴一下观察者,为了能更清晰的看到发射出来的内容,我们将每一个item都打印出来

731
来自专栏猿份到

浅谈Retrofit 源码

现在的项目界面都是按iOS的风格,虽然界面无法发挥出我们大Android的风格,网络框架动不动就是mvp+ okhttp +retrofit+rxjava,m...

3668
来自专栏Android干货

Android项目实战(三十七):Activity管理及BaseActivity的实现

5936
来自专栏Android开发指南

2.抽取代码(BaseActivity)

3588
来自专栏青蛙要fly的专栏

项目需求讨论— ButterKnife初级小结

在没有使用DataBinding之前,我的项目都是使用ButterKnife,当然对于ButterKnife大家估计都熟悉的不要太熟悉了。本文我也就当自己的一个...

823
来自专栏郭耀华‘s Blog

android 一些常用的功能方法代码块

我们这些苦逼的程序员在工作中,每一个老板都希望我们都能把手头的工作做好的,而且是越快越好,那我们要怎么样才能快起来呢?对于开发中常用的代码块无限复做是我们工作...

2774

扫码关注云+社区

领取腾讯云代金券