前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >BRAVH源码模拟

BRAVH源码模拟

作者头像
我就是马云飞
发布2018-02-05 10:49:51
8910
发布2018-02-05 10:49:51
举报
文章被收录于专栏:我就是马云飞我就是马云飞

简介

BRAVH是一个recyclerView的adapter,能够快速适配多种类型adapter,可定制,用的人挺多,下面我们就来分析分析他的源码,模拟来写一个我们的adapter。

我们将recyclerView的adapter与自定义viewholder联系在一起,使用了BaseQuickAdapter<T, K extends BaseViewHolder>来作为recycler.adapter

先看BRAVH的BaseViewHolder类

代码语言:javascript
复制
BaseViewHolder extends RecyclerView.ViewHolder

继承自ViewHolder,里面setXXX方法全是由itemview里面的view调用方法实现 存放了一个SparseArray<View> views成员变量用来初始化或者第一次遍历存放引用,添加快捷操作,省去下一次findview的时间

接下来看BaseQuickAdapter类

代码语言:javascript
复制
BaseQuickAdapter<T, K extends BaseViewHolder> extends RecyclerView.Adapter<K>

将K泛型传入给RecyclerView.Adapter作为viewholder T泛型分析: 传入的数据集List<T>的类型,用来绑定数据

自定义Adapter,ViewHolder

我们也来模拟一个adapter类型,使用自定义继承viewHolder的MyViewHolder作为Viewholder

这样我们就有了一个adapter,里面的Viewholder由外部传进来,我们可以继承该Viewholder自己做快捷操作,让外部继承实现MyViewholder逻辑,同BaseViewHolder,他也只是对itemview引用设置子View参数,这部分基本忽略。我们看onCreateViewHolder->VH createViewHolder(View view)这个方法,这是一个泛型类的实例化,这个直接上代码见git。

给Item添加加载动画

onViewAttachedToWindow(VH holder)方法: 每次Viewholder添加到window的时候contentView开始动画可以制作item加载效果 想要控制只让itemView进行一次动画,BRAVH里面设置了一个标志位,我们也写一个AnimOnce来做这个标志位,每次加载的时候得到viewholder的位置,并与上一次的加载过的位置比较,如果小,代表当前位置的contentview是新的,需要开启动画,否则如果AnimOnce只要一次,不开启动画,因为动画已经之前被加载过,我们可以这样实现:

这样,就能添加我们的itemview动画,如果需要外部定制,我们可以修改我们的animateView(View root)方法,给里面添加一个animation动画,并且提供外部接口,比如:

关于动画,可以使用animator或者animation都可以的,只要定制animateView方法

添加头部尾部空数据的布局

我们看BRAVH如何实现,

  1. 添加header,footer,loading的view
  2. GetItemCount需要返回的数据量为headercount + datas.size + footercount + loadingcount
  3. GetItemType如果position < headercount,返回头布局类型,得到普通布局的normal_position = position - headercount如果normal_position < datas.size [List数据集],返回普通列表类型,否则代表有尾布局footer

下面我们也来模拟一个头部尾部布局~ 写一个方法来添加头部尾部视图

GetItemType返回对应Type

根据对于Type构造Viewholder

根据position位置来绑定viewholder数据

这样我们的头部尾部布局就添加好了,添加loading布局与空布局也是一个道理,只是多加载了一种类型而已

加载更多的实现

这是在OnBindViewHolder->position判断位置 如果position已经在最后的位置,那么触发加载更多 下面我们可以写一个加载更多的方法:

实现拖拽,滑动删除

BRAVH是怎么实现呢?官方这样使用:

我们可以看到使用的类是ItemDragAndSwipeCallback ,这里面需要传入adapter需要BaseItemDraggableAdapter类型,我们进入ItemDragAndSwipeCallback 发现其实只是BaseItemDraggableAdapter回调用,其实本身并没有用到任何BaseItemDraggableAdapter属性。所以可以做出一个提取的过程,把传入类型BaseItemDraggableAdapter修改成interface,只要RecyclerAdapter实现这个接口就可以了,这样不必非要传入BaseItemDraggableAdapter类型。

这部分也可以直接用原生ItemTouchHelper,复写onMove实现item交换,onSwiped实现Item删除 我们开始写交换逻辑:

  1. 首先剔除header视图的位置获得在datas数据集中的位置realpos
  2. 交换数据集中的realpos位置数据
  3. Notifydatachanged 或许我们可以这样实现

下面我们实现删除逻辑:

  1. 首先剔除header视图的位置获得在datas数据集中的位置realpos
  2. 删除数据集中的realpos位置数据
  3. NotifyItemRemoved

自定义使用不同的Item类型 现在默认item类型有header,footer,loading,empty,default。如果想要自定义类型,那么我们可以修改自定义的adapter,在getItemType返回default类型的时候,使用抽象方法让子类实现,修改adapter为抽象类,这将影响:

  1. getItemtype 使用抽象方法getDefItemViewType(int realDataPos)
  2. oncreateViewholder 返回的View需要根据自定义itemtype实现自定义view,所以暴露抽象方法onCreateDefViewHolder(ViewGroup parent, int viewType)
  3. onBindViewHolder 绑定数据时需要根据自定义的ViewHolder来自定视图数据的绑定,所以暴露抽象方法onBindDefViewHolder(VH holder, int realDataPos)

这样我们便能够自定义itemType

getItemtype 使用抽象方法getDefItemViewType(int realDataPos)

暴露抽象方法onCreateDefViewHolder(ViewGroup parent, int viewType)

回顾的时候发现还是返回VH类型的Viewholder容易定制,这样自定义实现就不需要泛型实例化方法 abstract VH onCreateDefViewHolder(ViewGroup parent, int viewType);

暴露抽象方法onBindDefViewHolder(VH holder, int realDataPos)

我们的ItemType是由传入的数据类型决定的,可以定义一个接口,让传入的数据类型实现该接口并且实现getItemType

这样就可以在外部自定义数据类型了

添加分组

首先我们看BRAVH怎么介绍

  1. 实体数据集类型要继承SectionEntity

Stop,我们到这边似乎不必要去看他怎么实现了,我猜原理应该和上面添加自定义类型是一样的,上面更加广泛,所以这里我们只有2种类型而已,一种分组头类型。一种分组内容类型。 我们开始编写代码试试:

  1. 写一个数据集类型实现ItemType接口
  2. 数据集返回2种类型,第一种是分组头类型假定(GroupHeader),第二种是一种分组内容类型(GroupContent)
  3. 编写我们的Adapter传入参数类型为1的类型

我们先写一个数据集类型实现ItemType接口

这样添加分组就是2种自定义数据类型而已,我们可以写一个实体类,返回2种类型,使用MultiAdapter适配,实现方法:

  • onCreateDefViewHolder:根据viewType来inflate不同的layout的子view
  • onBindDefViewHolder:根据Viewholder的VH holder, int position来绑定itemview与数据

分组的伸缩栏

既然要实现分组,我的思路是这样的

  1. 是否需要提供不同的itemtype,然后根据不同itemtype提供不同级别的子View,这样视图倒是没有问题了。
  2. 下面需要思考的是数据,如果想要添加到列表的数据集中,之前我们定义的是T类型,那么不同级别也是要是T类型的才能加入到列表的数据集中
  3. 然后插入notifyitemInserted,删除notifyitemremoved这样就没问题了.

可是设计主实体类或许有些麻烦了,要求里面有个子数据集类型是实体类类型的: 我们先定义接口

代码语言:javascript
复制
public interface Expandable<E> extends ItemType{ 
   List<E> subItems();    
   boolean isExpandable();  
   void setExpand(boolean expand);
   boolean isExpand(); 
   }

这个接口要求子数据级,是否可以扩展

然后定义数据类型

代码语言:javascript
复制
public class ExpandEntity implements Expandable<ExpandEntity>{

    public final static int SUB1 = 0x111;
    public final static int SUB2 = 0x112;
//    public final static int SUB3 = 0x113;

    private int itemType;
    private List<ExpandEntity> subDatas;
    private String values;

    public ExpandEntity(int itemType, List<ExpandEntity> subDatas,String values) {
        this.itemType = itemType;
        this.subDatas = subDatas;
        this.values = values;
    }

    public String getValues() {
        return values;
    }

    @Override
    public int getItemType() {
        return itemType;
    }

    @Override
    public List<ExpandEntity> subItems() {
        return subDatas;
    }

    @Override
    public boolean isExpandable() {
        return subDatas != null && subDatas.size() > 0;
    }

    boolean isExpand = false;
    @Override
    public void setExpand(boolean expand) {
        isExpand = expand;
    }

    @Override
    public boolean isExpand() {
        return isExpand;
    }
}

这样就可以实现该接口,外部类自由继承,然后自由添加values属性

下面我们的接口实体类好了,需要制作adapter,可是adapter怎么写呢?我们需要传入的数据类型为Expandable类型,而且数据集合类型要是Expandable的实现类,是否可以这样写?

class ExpandAdapter<I extends Expandable<I>,VH extends MyViewHolder> extends BackQuickAdapter<I,VH>

这样保证数据集市I类型,I又是Expandable类型,这样可以遍历I的子数据集实现多级的展开与隐藏

Adapter具体实现该怎样呢? 我们可以在onBindDefViewHolder方法里面添加itemview的点击事件,然后为itemview添加tag,tag里面是绑定的数据,再实现点击事件的时候取出tag里面数据,判断单项是否可以展开,如果可以展开,得到子数据集放入主数据集中,然后notifyItemRangeInserted就能实现数据的多级展开

注意这里移除操作,去除datas中间的数据,实现折叠效果,然后notifyItemRangeMoved 这部分的逻辑也是对主数据集datas操作,这里没有具体实现,有心的小伙伴可以看BRAVH的expand与collapse方法,人家的折叠可是多级折叠的,将所有子集都移除然后notifyItemRangeMoved。这里我就不写这部分逻辑代码了。

我是分割线 这里我们写Adapter需要涉及到一个泛型的实例化,因为需要将View加入Viewholder,并把Viewholder实例化,而Viewholder又是VH类型的,所以下面方法我直接贴上代码

代码语言:javascript
复制
/**
     * 抽象类反射实例化
     * @param view
     * @return
     */
    protected VH createViewHolder(View view) {
        Class temp = getClass();
        Class z = null;
        while (z == null && null != temp) {
            z = getInstancedGenericKClass(temp);
            temp = temp.getSuperclass();
        }
        VH k;
        // 泛型擦除会导致z为null
        if (z == null) {
            k = (VH) new MyViewHolder(view);
        } else {
            k = createGenericKInstance(z, view);
        }
        return k != null ? k : (VH) new MyViewHolder(view);
    }
    /**
     * try to create Generic K instance
     *
     * @param z
     * @param view
     * @return
     */
    @SuppressWarnings("unchecked")
    private VH createGenericKInstance(Class z, View view) {
        try {
            Constructor constructor;
            // inner and unstatic class
            if (z.isMemberClass() && !Modifier.isStatic(z.getModifiers())) {
                constructor = z.getDeclaredConstructor(getClass(), View.class);
                constructor.setAccessible(true);
                return (VH) constructor.newInstance(this, view);
            } else {
                constructor = z.getDeclaredConstructor(View.class);
                constructor.setAccessible(true);
                return (VH) constructor.newInstance(view);
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * get generic parameter K
     *
     * @param z
     * @return
     */
    private static Class getInstancedGenericKClass(Class z) {
        Type type = z.getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            Type[] types = ((ParameterizedType) type).getActualTypeArguments();
            for (Type temp : types) {
                if (temp instanceof Class) {
                    Class tempClass = (Class) temp;
                    if (MyViewHolder.class.isAssignableFrom(tempClass)) {
                        return tempClass;
                    }
                }
            }
        }
        return null;
    }

这样泛型的实例化就完成了~

总结

我们在写Adapter的时候。将数据集类型以泛型的形式传入。 在Adapter中抽象出onBindViewHolder,onCreateViewHolder,除了处理预置的类型,比如头布局,尾布局,空布局,loading布局。其他的都需要自定义ItemType数据类型来实现定制view。 扩展与折叠也是属于自定义数据类型中的一种,但是要求传入的数据集类型T中还有T类型的子集合,用来得到子集,这样可以保持与Adapter 的数据类型一直,用于展开删除其实就是对Adapter中的数据集datas插入与删除然后通知刷新而已 拖拽与滑动删除默认ItemTouhHelper,在onmoved与onswipe中交换数据集中的位置或者删除某个位置来通知刷新

Source源代码

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-09-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 我就是马云飞 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
图数据库 KonisGraph
图数据库 KonisGraph(TencentDB for KonisGraph)是一种云端图数据库服务,基于腾讯在海量图数据上的实践经验,提供一站式海量图数据存储、管理、实时查询、计算、可视化分析能力;KonisGraph 支持属性图模型和 TinkerPop Gremlin 查询语言,能够帮助用户快速完成对图数据的建模、查询和可视化分析。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档