美文网首页listviewAndroidAndroid技术知识
MultiItem用法与详解-优雅的实现多类型RecyclerV

MultiItem用法与详解-优雅的实现多类型RecyclerV

作者: free46000 | 来源:发表于2017-03-19 23:59 被阅读1897次

    前言

    RecyclerView是一个大家常用的列表控件,在列表中不免会出现多种类型的布局,这时adapter中多种类型的判断就会充满着switch的坏味道,可怕的是需求变更,增加或修改新的类型时,所有的改动都在adapter中进行,没有一个良好的扩展性。
    MutliItem主要就是解决这些问题,在正常使用中做到了Adapter零编码,解放了复杂的Adapter类,本库提供了多类型和ViewHolder创建绑定的管理器,这样Adapter通过依赖倒置与列表中的多类型解耦,还提高了扩展性。在本库中不同实体类可以直接当成数据源绑定到adapter中,你不用去担心item type的计算,并且对每种类型的ViewHolder也做到了隔离。
    本库的定位并不是大而全,但是会尽量做到简单易用。

    源码地址

    Github地址:MultiItem,请大家多多关注,更多更新会首先在GitHub上体现,也会在第一时间在本平台发布

    效果截图

    multi_itemmulti_item chatchat

    下一步要做什么

    • DataBinding特性支持
    • 录入界面的复用和封装的demo代码(录入业务较多同学可以多多关注)
    • 思考动画分割线等一些功能封装

    用法

    添加依赖

    • 配置gradle:

    Project rootbuild.gradle中添加:

    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }
    

    Module中添加(最新版本请在源码地址查看):

    dependencies {
        compile 'com.github.free46000:MultiItem:0.9.3'
    }
    
    • 或者你也可以直接克隆源码

    多种类型列表用法

    这里由于单一和多种类型写法上没有差别,所以就不单独贴出单一类型的列表代码了。
    注册多种类型ViewHolderManager,并为adapter设置多种类型数据源:

    //初始化adapter
    BaseItemAdapter adapter = new BaseItemAdapter();
    //为TextBean数据源注册ViewHolderManager管理类
    adapter.register(TextBean.class, new TextViewManager());
    //为更多数据源注册ViewHolderManager管理类
    adapter.register(ImageTextBean.class, new ImageAndTextManager());
    adapter.register(ImageBean.class, new ImageViewManager());
    
    //组装数据源list
    List<Object> list = new ArrayList<>();
    list.add(new TextBean("AAA"));
    list.add(new ImageBean(R.drawable.img1));
    list.add(new ImageTextBean(R.drawable.img2, "BBB" + i));
           
    //为adapter注册数据源list
    adapter.setDataItems(list);
    
    recyclerView.setAdapter(adapter);
    
    

    ViewHolder管理类的子类TextViewManager类,其他类相似,下面贴出本类全部代码,是不是非常清晰:

    public class ImageViewManager extends BaseViewHolderManager<ImageBean> {
    
        @Override
        public void onBindViewHolder(BaseViewHolder holder, ImageBean data) {
            //在指定viewHolder中获取控件为id的view
            ImageView imageView = getView(holder, R.id.image);
            imageView.setImageResource(data.getImg());
        }
    
        @Override
        protected int getItemLayoutId() {
            //返回item布局文件id
            return R.layout.item_image;
        }
    }
    

    至此本库的多种类型列表用法已经完成,并没有修改或继承RecyclerView Adapter类,完全使用默认实现BaseItemAdapter即可。

    相同数据源对应多个ViewHolder(聊天界面)

    这是一种特殊的需求,需要在运行时通过数据源中的某个属性,判断加载的布局,典型的就是聊天功能,相同消息数据对应左右两种气泡视图,在此处贴出注册时的关键代码,其他和多种类型列表类似:

    //初始化adapter
    BaseItemAdapter adapter = new BaseItemAdapter();
    
    //为XXBean数据源注册XXManager管理类组合
    adapter.register(MessageBean.class, new ViewHolderManagerGroup<MessageBean>(new SendMessageManager(), new ReceiveMessageManager()) {
        @Override
        public int getViewHolderManagerIndex(MessageBean itemData) {
            //根据message判断是否本人发送并返回对应ViewHolderManager的index值
            return itemData.getSender().equals(uid) ? 0 : 1;
        }
    });
    
    recyclerView.setAdapter(adapter);
    

    设置点击监听

    点击监听:

    adapter.setOnItemClickListener(new OnItemClickListener() {
        @Override
        public void onItemClick(BaseViewHolder viewHolder) {
            //通过viewHolder获取需要的数据
            toastUser(String.format("你点击了第%s位置的数据:%s", viewHolder.getItemPosition()
            , viewHolder.getItemData()));
        }
    });
    

    长按监听:

    adapter.setOnItemLongClickListener(new OnItemLongClickListener() {
        @Override
        public void onItemLongClick(BaseViewHolder viewHolder) {
            //通过viewHolder获取需要的数据
            toastUser(String.format("你长按了第%s位置的数据:%s", viewHolder.getItemPosition()
                    , viewHolder.getItemData()));
        }
    });
    

    详解

    主要流程

    • 为指定的数据源注册ViewHolderManager提供视图创建绑定等工作
    • 在列表创建的过程中通过数据源在ItemTypeManager找到对应的ViewHolderManager
    • 按照需要创建与刷新视图并对视图做一些通用处理

    ViewHolder管理

    ViewHolder管理源码类为ViewHolderManager,使用者会首先注册数据源和本实例的对应关系,由类型管理类提供统一管理。

    • 提供了参数类,会在adapter调用本类方法的时候传入并做出通用处理
    • 本类的设计使用泛型,是为了在后续回调方法中有更直观的类型体现,这也是强类型和泛型带来的好处,给人在编写代码的时候带来确定感
    • 本类为抽象类需要重写ViewHolder的创建与绑定方法,为了方便后续使用,写了一个简单的BaseViewHolderManager实现类,请读者根据业务自行决定是否需要使用更灵活的基类,这里贴出需要复写的两个方法,延续了Adapter中的命名规则,在使用中减少一些认知成本:
    /**
     * 创建ViewHolder
     * {@link android.support.v7.widget.RecyclerView.Adapter#onCreateViewHolder}
     */
    @NonNull
    public abstract V onCreateViewHolder(@NonNull ViewGroup parent);
    
    /**
     * 为ViewHolder绑定数据
     * {@link android.support.v7.widget.RecyclerView.Adapter#onBindViewHolder}
     *
     * @param t 数据源
     */
    public abstract void onBindViewHolder(@NonNull V holder, @NonNull T t);
    

    ViewHolder管理组合(相同数据源对应多个ViewHolderManager)

    组合管理源码类为ViewHolderManagerGroup,本实例需要一个ViewHolderManager集合,并增加通过数据源指定哪个ViewHolderManager的方法,使用者同样会注册数据源和本实例的对应关系,由类型管理类对本类中的ViewHolderManager集合进行统一注册管理。下面贴出关键代码:

     private ViewHolderManager[] viewHolderManagers;
    
    /**
     * @param viewHolderManagers 相同数据源对应的所有ViewHolderManager
     */
    public ViewHolderManagerGroup(ViewHolderManager... viewHolderManagers) {
        if (viewHolderManagers == null || viewHolderManagers.length == 0) {
            throw new IllegalArgumentException("viewHolderManagers can not be null");
        }
        this.viewHolderManagers = viewHolderManagers;
    }
    
    /**
     * 根据item数据源中的属性判断应该返回的对应viewHolderManagers的index值
     *
     * @param itemData item数据源
     * @return index值应该是在viewHolderManagers数组有效范围内
     */
    public abstract int getViewHolderManagerIndex(T itemData);
    

    类型管理

    类型管理源码类为ItemTypeManager,通过数据源className ListviewHolderManager List两组集合对类型进行管理,并对Adapter提供注册和对应关系查找等方法的支持,这里并没有把这个地方设计灵活,如果有一些变化还是希望可以在ViewHolderManager做出适配。

    • 数据源一对一viewHolderManager时比较简单,关键代码:
     /**
     * 通过数据源`className List`和`viewHolderManager List`两组集合对类型进行管理
     *
     * @param cls     数据源class
     * @param manager ViewHolderManager
     * @see com.freelib.multiitem.adapter.BaseItemAdapter#register(Class, ViewHolderManager)
     */
    public void register(Class<?> cls, ViewHolderManager manager) {
        register(getClassName(cls), manager);
    }
    
    • 数据源一对多viewHolderManager时,关键代码:
    /**
     * 通过group获取一组ViewHolderManager循环注册,并生成不同的className作为标识<br>
     * 其他类似{@link #register(Class, ViewHolderManager)}
     *
     * @param cls   数据源class
     * @param group 多个ViewHolderManager的组合
     * @see com.freelib.multiitem.adapter.BaseItemAdapter#register(Class, ViewHolderManagerGroup)
     */
    public void register(Class<?> cls, ViewHolderManagerGroup group) {
        ViewHolderManager[] managers = group.getViewHolderManagers();
        for (int i = 0, length = managers.length; i < length; i++) {
            register(getClassNameFromGroup(cls, group, managers[i]), managers[i]);
        }
        itemClassNameGroupMap.put(getClassName(cls), group);
    }
    

    希望大家会喜欢,多多留言交流

    相关文章

      网友评论

      • 4144043b82ea:你好 我想问下panel 拖拽滑动item的时候在item会有个偏移量角度使得item是向右下倾斜的,我想取消这个偏移的角度,让拖动的时候是item视图是水平的,请问要修改哪里啊?我研究了半天也没找到,能指教下么,谢谢🙏
        今天吃什么腻:@Rednecks 谢了,我找到了。
        4144043b82ea:我是找到了 但那个项目太久没看了,忘了在哪修改了...:joy:
        今天吃什么腻:同求,有个很小的倾斜角度,在哪里设置啊,找的眼花了
      • 浮游大虾:如果一种类型的数据 需要根据其中一个变量的不同类型展示不同的页面 这种情况该怎么配置
        free46000:@浮游大虾 我看了你的库,确实不错把匹配数据创建Item这块下放到调用方的ItemFactory,这样利用isTarget方法就可以清楚的对应不同的Item:+1: ,可能我也会在ViewHolderManager注册的时候借鉴下你的思路:grin:
        浮游大虾:@free46000 我的解决办法是每一种Item配一个ItemFactory,Item负责UI和设置数据,ItemFactory负责匹配数据,创建Item,定义和处理事件等。对照你现有的设计ItemFactory就是ViewHolderManager,只不过少了匹配数据的方法,Item就像是你还需要在ViewHolderManager中定义的ViewHolder。这就是我的AssemblyAdapter https://github.com/xiaopansky/AssemblyAdapter
        free46000:@浮游大虾 像聊天界面这种情况避免不了的是需要逻辑判断的,目前的想法就是提供一个ViewHolderGroupManager类,在内部抽象一个方法,通过数据源的变量去自动匹配。但是这种方式用起来确实不太舒服,不知道同学你有什么好的想法没

      本文标题:MultiItem用法与详解-优雅的实现多类型RecyclerV

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