EMvp-基于AOP的一种RecyclerView多楼层开发模式,支持组件化,全局楼层打通,MVP等高拓展性功能

前言

RecyclerView作为Google替代ListView的一个组件,其强大的拓展性和性能,现在已经成为无数App核心页面的主体框架。RecyclerView的开发模式一般来说都是多Type类型的ViewHolder——后面就称为楼层(感觉很形象)。但是使用多了,许多问题就暴露出来了,经常考虑有这么几个问题:

    1. 如何更便捷的使用Adapter和ViewHolder的开发模式?
    1. 如何和他人的楼层做到楼层的复用?
    1. 如何做到全局楼层的打通?
    1. 楼层本身如何做到逻辑闭合,做到MVP的组件化模式?

功能特性

  • 基于编译期注解,不影响性能
  • 使用简单,楼层耦合度低
  • 代码侵入性低
  • 支持全局楼层打通,多人楼层打通
  • 楼层支持点对点MVP模式
  • 事件中心模式,楼层只是事件的传递者。
  • 生命周期监听,支持逻辑的生命周期感知。
  • 丰富的API,支持多方面拓展。
  • 提供组件化工程使用方案
  • 不用每次再写Adapter了~

项目地址

EMvp

欢迎Star~
欢迎提issue讨论~

使用方式

这里就介绍一下基于自己对于RecyclerView的理解,开发的一款基于AOP的,适用于多楼层模式的RecyclerView的开发框架。

核心注解

@Documented()
// 表示是基于编译时注解的
@Retention(RetentionPolicy.CLASS)
// 表示可以作用于成员变量,类、接口
@Target(ElementType.TYPE)
public @interface ComponentType {
    //ComponentId
    int value() default -1;

    //LayoutId,当为ViewHolder类型需要
    int layout() default -1;
    //组件化项目时,注解父View,通过LayoutInflater创建布局
    Class view() default Object.class;

    //是否利用反射创建,默认打开的(复杂的,性能相关的,数量大的当然建议关闭咯)
    boolean autoCreate() default true;

    //楼层绑定的类,通过类来寻找楼层的可用范围
    Class attach() default Object.class;
}

一.单样式列表

1.定义楼层(支持三种模式)
  • 继承Component类型
@ComponentType(
        value = ComponentId.SIMPLE,
        layout = R.layout.single_text
)
public class SimpleVH extends Component {
    public SimpleVH(Context context, View itemView) {
        super(context, itemView);
    }

    @Override
    public void onBind(int pos, Object item) {
    }
    
    @Override
    public void onUnBind() {
    }
}

  • 继承原生ViewHolder类型
@ComponentType(
        value = PersonId.VIEWHOLDER,
        layout = R.layout.person_item_layout
)
public class PersonVH extends RecyclerView.ViewHolder implements IComponentBind {
    private TextView tvName;

    public PersonVH(View itemView) {
        super(itemView);
        tvName = itemView.findViewById(R.id.tv_name);
    }

    @Override
    public void onBind(int pos, PersonModel item) {
        tvName.setText(item.name);
    }

    @Override
    public void onUnBind() {
    }
}
  • 自定义View类型
@ComponentType(PersonId.CUSTOM)
public class CustomView extends LinearLayout implements IComponentBind {
    public CustomView(Context context) {
        super(context);
        LayoutInflater.from(context).inflate(R.layout.cutom_view_vh, this, true);
        setBackgroundColor(Color.BLACK);
    }

    @Override
    public void onBind(int pos, PersonModel item) {
    }

    @Override
    public void onUnBind() {

    }
}

很清晰,不用再每次在复杂的if else中寻找自己楼层对应的布局文件。(熟悉的人应该都懂)
注意:

  1. value:楼层的唯一标示,int型
  2. layout:楼层的布局文件
  3. 继承ViewHolder和自定义View类型需要实现IComponentBind接口即可

对于R文件不是常量在组件化时遇到的问题的解决方案 Wiki
这里没有选用butterknife将R文件复制一份成R2的方式,我个人感觉不是特别优雅,最终我选择的是在注解中增加一种View类型的注解,可以在注解中注解父View的Class,然后在构造函数通过LayoutInflater加入布局文件。

@ComponentType(
        value = ComponetId.BANNER,
        view = FrameLayout.class
)
public BannerVH(Context context, View itemView) {
        super(context, itemView);
        fgContainer = (FrameLayout) itemView;
        //再利用LayoutInflater
        LayoutInflater.from(context).inflate()
    }
2.定义Model
@BindType(ComponentId.SIMPLE)
public class SimpleModel {
    
}

BindType:当是单样式时,model直接注解对应的楼层的唯一标示,int型

3.绑定RecyclerView
@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.common_layout);
        mRcy = findViewById(R.id.rcy);
        mRcy.setLayoutManager(new LinearLayoutManager(this));
        new ToolKitBuilder<>(this, mData).build().bind(mRcy);
    }

使用对应的API,利用build()方法构建SlotsContext实体最后利用bind()方法绑定ReyclerView.

二.多楼层模式

1.定义ViewHolder(同前一步)
2.多样式判断逻辑(两种方式)

2.1 Model实现HandlerType接口处理逻辑
public class CommonModel implements HandlerType {
    public int pos;
    public String tips;
    public String eventId;

    @Override
    public int handlerType() {
        if (pos > 8) {
            pos = pos % 8;
        }
        switch (pos) {
            case 1:
                return ComponentId.VRCY;
            case 3:
                return ComponentId.DIVIDER;
            case 4:
                return ComponentId.WEBVIEW;
            case 5:
                return ComponentId.TEXT_IMG;
            case 6:
                return ComponentId.IMAGE_TWO_VH;
            case 7:
                return ComponentId.IMAGE_VH;
            case 8:
                return ComponentId.USER_INFO_LAYOUT;
        }
        return ComponentId.VRCY;
    }
}

返回定义的ItemViewType,这里封装在Model内部,是由于平时我们总是将java中的Model当作一个JavaBean,而导致我们赋予Model的职责过于轻,所以就会出现更多的其实和Model紧密相关的逻辑放到了Activity,Presenter或者别的地方,但是其实当我们将Model当作数据层来看待,其实可以将许多与Model紧密相关的逻辑放到Model中,这样我们其实单模块的逻辑内聚度就很高,便于我们理解。
(这里思路其实来源于IOS开发中的胖Model的概念,大家可以Goolge一下)

好处:当我们需要确定楼层之间和Model的关系,直接按住ctrl,进入Model类,一下就可以找到相关逻辑。

2.2 实现IModerBinder接口自定义处理类

一款好的框架肯定是对修改关闭,对拓展开放的,当我们认为放到Model中处理过于粗暴,或者Model中已经有过多的逻辑了,我们也可以将逻辑抽出来,实现IModerBinder接口。

public interface IModerBinder {
    int getItemType(int pos, T t);
}

对应的利用ToolKitBuilder.setModerBinder(IModerBinder moderBinder)构建即可。例如:

.setModerBinder(new ModelBinder() {
                    @Override
                    protected int bindItemType(int pos, PersonModel obj) {
                        //处理Type的相关逻辑
                       return type;
                    }
                })

个人模式

当涉及到大型项目时,多人协作往往是一个问题,当所有人都维护一套ComponentId,合并代码时解决冲突往往是很大的问题,并且不可能所有的楼层都是全局打通的类型,所以这里提供一种个人开发模式。

用法

  • 1.使用attach注解,绑定对应class
@ComponentType(
        value = PersonId.VIEWHOLDER,
        layout = R.layout.person_item_layout,
        //class类型,对应到映射表的key
        attach = PersonModel.class
)
public class PersonVH extends RecyclerView.ViewHolder implements IComponentBind {
    private TextView tvName;

    public PersonVH(View itemView) {
        super(itemView);
        tvName = itemView.findViewById(R.id.tv_name);
    }

    @Override
    public void onBind(int pos, PersonModel item) {
        //tvName.findViewById(R.id.tv_name);
        tvName.setText(item.name);
    }

    @Override
    public void onUnBind() {

    }
}
  • 2.调用SlotContext.attachRule绑定对应的Class
SlotContext slotContext =
                new ToolKitBuilder(this)
                        //注册绑定的类型,对应获取映射表
                        .attachRule(PersonModel.class).build();

进阶使用

项目利用Build模式构建SlotContext实体,SlotContext原理基于Android中的Context思想,作为一个全局代理的上下文对象,通过SlotContext,我们可以获取对应的类,进而实现对应类的获取和通信。

避免反射创建

框架本身利用反射进行创建,内部利用LruCache对反射对构造器进行缓存,优化反射性能。如果想要避免反射对创建,也是可以自定义创建过程。

@ComponentType(
            value = PersonId.INNER,
            view = TextView.class,
            //注解不使用反射
            autoCreate = false
    )
    public static class InnerVH extends RecyclerView.ViewHolder implements IComponentBind {
       ....
    }

可以将不需要反射创建对ViewHolder的autoCreate=false,然后通过ToolKitBuilder. setComponentFactory()自定义创建过程。
具体方式->Wiki

事件中心

事件中心其实本质就是一个继承于View.OnClickListener的类,所有和ViewHolder本身无关的事件,统一传递给事件中心,再由事件中心处理,对应于一条准则:

ViewHolder只是一个专注于展示UI的壳,只做事件的传递者,不做事件的处理者。

使用方式:

@ComponentType(
        value = ComponetId.SINGLE_TEXT,
        layout = R.layout.single_text
)
public class TextVH extends Component implements InjectCallback {
    private TextView tv;
    private View.OnClickListener onClickListener;
    public TextVH(Context context, View itemView) {
        super(context, itemView);
        tv = (TextView) itemView;
    }
    @Override
    public void onBind(int pos, Text item) {
        tv.setText(item.title);
        //此处所有的数据和事件类型通过setTag传出
        tv.setTag(item.eventId);
        tv.setOnClickListener(onClickListener);
    }
    @Override
    public void onUnBind() {

    }
    @Override
    public void injectCallback(View.OnClickListener onClickListener) {
        this.onClickListener = onClickListener;
    }
}

仿照依赖注入的思想,只不过代码侵入性没有那么强,当然只能在onBind的时候才能绑定,构造函数的时候,事件中心对象还没有注入进来。

    1. ViewHolder实现InjectCallback接口,在onBind生命周期就可以拿到事件中心对象。
    1. 通过View.setTag,将事件类型(int型等,唯一性)和相关需要的数据传出。

事件中心的思想就是:ViewHolder单纯的只传递事件,完全由数据驱动事件,View不感知事件类型,也就是说,这个ViewHolder的事件是可变的

MVP的拆分

关于MVP是什么这里就不多讲了,这里讲一讲MVP的拆分,常规的MVP我们经常做的就是一个P完成所有的逻辑,但是这时带来的问题就时P层过于大,这时我的理解就是对P进行拆分,具体拆分的粒度要根据不同的业务场景来区分(这个就比较考验开发者对于设计模式的理解)。而ViewHolder自身可以完成一套MVP体系,想一想,当一个特殊的楼层,涉及复杂的业务逻辑,这时完全将这个楼层拆分成MVP模式,这时其他页面需要使用的时候,只需要new对应的MVP即可。

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        slotContext = new ToolKitBuilder<>(this, mData).build();
        //1.注册对应的逻辑类
        slotContext.registerLogic(new CommonLogic(slotContext));
        ...
    }


@ComponentType(value = ComponentId.TEXT_IMG)
//2.注解对应的逻辑类
@ILogic(CommonLogic.class)
//3.实现IPresenterBind接口
public class TextImgLayout extends LinearLayout implements IComponentBind,IPresenterBind {
    private View root;
    private TextView tvInfo;
    private CommonLogic logic;
    ...
    @Override
    public void onBind(int pos, CommonModel item) {
        tvInfo.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (logic != null) {
                //对应的P,处理业务逻辑
                    logic.pageTransfer();
                }
            }
        });
    }
    ...
    @Override
    public void injectPresenter(CommonLogic commonLogic) {
        this.logic = commonLogic;
    }
}

对应的需要三步:

    1. slotContext.registerLogic(IPresenter presener),这里IPresenter只是一个空接口,用于表明这是一个逻辑层的类。
    1. 在ViewHolder利用@ILogic注解对应的P的Class
    1. ViewHolder实现IPresenterBind接口,注入注册给SlotContext对应的Presenter.

生命周期感知

无论是Presenter还是任何其他类,当脱离的Activity,对于生命周期的感知时非常重要的,所以SlotContext提供的有两个API

pushLife(ILifeCycle lifeCycle)
pushGC(IGC gc)

需要感知生命周期,或者仅仅感知OnDestroy的类,只需实现相应的接口,并利用api注册观察者即可。

MIX模式,多楼层打通

对于多楼层打通,我们需要利用ToolKitBuilder实现IMixStrategy策略。

public interface IMixStrategy {
    //通过type得到真正的映射表中的ComponentId
    int getComponentId(int type);

    //通过Type确定对应的映射表
    Class attachClass(int type);

    //传入ViewHolder的Bind中的实体类
    Object getBindItem(int pos, T t);
}

具体方案->Wiki

ToolKitBuilder的构造函数

public ToolKitBuilder(Context context, List data)
public ToolKitBuilder(Context context)

ToolKitBuilder的API

方法名 描述 备注
setData(List data) 设置绑定的数据集 空对象,对应的构造的size=0
setModerBinder(IModerBinder moderBinder) 处理多样式时Model对应的Type 处理优先级优先于HandlerType和注解BindType
setEventCenter(View.OnClickListener onClickListener) 设置事件中心 ViewHolder的事件绑定后都会回调到这个事件中心
setComponentFactory(CustomFactory componentFactory) 设置自定义创建ViewHolder的工厂 可以自定义创建三种类型
setMixStrategy(IMixStrategy mixStrategy) 设置混合模式处理策略 多人楼层打通
attachRule(Class clazz) 注册楼层映射表 个人模式和混合模式
SlotContext build() 构建出SlotContext对象

SlotContext的构造函数

public SlotContext(Context context, List data)
public SlotContext(ToolKitBuilder builder)

SlotContext的API

方法名 描述 备注
Context getContext() 获取Context对象
setData(List data) 绑定数据集 这里不会刷新数据,仅仅是设置
notifyDataSetChanged() 刷新数据 只提供了全局刷新的方式,局部刷新可以通过获取Adapter使用
attachRule(Class clazz) 注册楼层映射表 个人模式和混合模式
registerLogic(IPresent logic) 注册Presenter逻辑 可注册多个,需要实现IPresenter空接口
obtainLogic(Class clazz) 获取对应注册的Presenter实例 以class作为key
bind(RecyclerView rcy) 绑定Adapter 会重新创建Adapter并绑定
RecyclerView.Adapter getAdapter() 获取Adapter
pushLife(ILifeCycle lifeCycle) 注册任何对象监听生命周期 实现ILifeCycler接口
pushGC(IGC gc) 监听Destroy生命周期

更多拓展

更多使用方式详见Wiki

项目源码解析

Python自动生成10000个java类使用APT注解后引发的问题

项目地址:EMvp
欢迎Star
欢迎大家提issues提意见~

你可能感兴趣的:(EMvp-基于AOP的一种RecyclerView多楼层开发模式,支持组件化,全局楼层打通,MVP等高拓展性功能)