VLayout适配器的万能封装

作者: moushao | 来源:发表于2017-08-31 16:52 被阅读1313次

    本文已授权微信公众号 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;
           //布局文件资源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就恰逢其时的运用上了。

    ·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方法

    @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和单个数据源。

    使用

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

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


    更多问题加群:584275290

    Demo GitHub地址

    相关文章

      网友评论

      • 2cec5e0ef11c:position指的是recyclerView item的position,demo中在设置gridLayout之前,已经设置了13个item,所以grid中的position起始位置是13
        moushao:@SkySmile丶 这个是我弄错了,DelegetAdapter提供了一个getStartPostion()方法获取偏移位置.
      • Evan_we:楼主我想问下,那是不是可以在WaterHolder请求数据啊,
        moushao:@YuWoAmbrose 一般来说,holder中的控件是不会暴露出来的,当然,你可以将先将holer实例化成一个对象,然后在holder中添加get方法获取控件.
        Evan_we:@牟仯 那我在fragment里怎么找到holder中的控件呢
        moushao:@YuWoAmbrose 不是,holder只负责显示
      • 吕檀溪:这样处理emptyVIew的时候是不是好处里了呢
        moushao:什么意思?没动呢?
      • 46b1434d316d:有用这个做上啦加载嘛
        moushao:@潇潇的鱼儿 这个自己加就好,简单
      • 棒棒妍:手动给大佬点赞:+1:
        moushao:@棒棒妍 我是菜鸟,菜鸟飞呀飞呀飞呀飞呀飞
      • 9c8f80201013:点赞,棒棒的哦
        moushao:@来瓶82年的爽歪歪 你是大佬,哈哈哈
      • huangasys:给你点赞:+1:
        moushao:@s黄健 两个爽歪歪?
      • 小熊丶:地下室
        24K纯帅豆:@牟仯这个是小熊
        moushao:分不清你们谁是谁了!
      • 熊二阿阿:地铺
        24K纯帅豆:@牟仯 这个是熊二
        moushao:分不清你们谁是谁了!
      • eba6cd3f3618:板凳
        moushao:@24K纯帅豆 笑笑是棒棒妍
        24K纯帅豆:@牟仯 这个是笑笑或者歪姐
        moushao:分不清你们谁是谁了!

      本文标题:VLayout适配器的万能封装

      本文链接:https://www.haomeiwen.com/subject/sakqjxtx.html