RecyclerView作为Google替代ListView的一个组件,其强大的拓展性和性能,现在已经成为无数App核心页面的主体框架。RecyclerView的开发模式一般来说都是多Type类型的ViewHolder——后面就称为楼层(感觉很形象)。但是使用多了,许多问题就暴露出来了,经常考虑有这么几个问题:
EMvp
欢迎Star:clap:~
欢迎提issue讨论~
这里就介绍一下基于自己对于RecyclerView的理解,开发的一款基于AOP的,适用于多楼层模式的RecyclerView的开发框架。
@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() { } } 复制代码
@ComponentType( value = PersonId.VIEWHOLDER, layout = R.layout.person_item_layout ) public class PersonVH extends RecyclerView.ViewHolder implements IComponentBind<PersonModel> { 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() { } } 复制代码
@ComponentType(PersonId.CUSTOM) public class CustomView extends LinearLayout implements IComponentBind<PersonModel> { 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
中寻找自己楼层对应的布局文件。(熟悉的人应该都懂)
注意:
IComponentBind
接口即可 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.多样式判断逻辑(两种方式)
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类,一下就可以找到相关逻辑。
一款好的框架肯定是对修改关闭,对拓展开放的,当我们认为放到Model中处理过于粗暴,或者Model中已经有过多的逻辑了,我们也可以将逻辑抽出来,实现IModerBinder接口。
public interface IModerBinder<T> { int getItemType(int pos, T t); } 复制代码
对应的利用 ToolKitBuilder.setModerBinder(IModerBinder<T> moderBinder)
构建即可。例如:
.setModerBinder(new ModelBinder<PersonModel>() { @Override protected int bindItemType(int pos, PersonModel obj) { //处理Type的相关逻辑 return type; } }) 复制代码
当涉及到大型项目时,多人协作往往是一个问题,当所有人都维护一套ComponentId,合并代码时解决冲突往往是很大的问题,并且不可能所有的楼层都是全局打通的类型,所以这里提供一种个人开发模式。
@ComponentType( value = PersonId.VIEWHOLDER, layout = R.layout.person_item_layout, //class类型,对应到映射表的key attach = PersonModel.class ) public class PersonVH extends RecyclerView.ViewHolder implements IComponentBind<PersonModel> { 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() { } } 复制代码
SlotContext slotContext = new ToolKitBuilder<PersonModel>(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<PersonModel> { .... } 复制代码
可以将不需要反射创建对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<Text> 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的时候才能绑定,构造函数的时候,事件中心对象还没有注入进来。
事件中心的思想就是:ViewHolder单纯的只传递事件,完全由数据驱动事件,View不感知事件类型,也就是说,这个ViewHolder的事件是 可变的 !
关于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<CommonModel>,IPresenterBind<CommonLogic> { 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; } } 复制代码
对应的需要三步:
slotContext.registerLogic(IPresenter presener)
,这里IPresenter只是一个空接口,用于表明这是一个逻辑层的类。 无论是Presenter还是任何其他类,当脱离的Activity,对于生命周期的感知时非常重要的,所以SlotContext提供的有两个API
pushLife(ILifeCycle lifeCycle) pushGC(IGC gc) 复制代码
需要感知生命周期,或者仅仅感知OnDestroy的类,只需实现相应的接口,并利用api注册观察者即可。
对于多楼层打通,我们需要利用ToolKitBuilder实现IMixStrategy策略。
public interface IMixStrategy<T> { //通过type得到真正的映射表中的ComponentId int getComponentId(int type); //通过Type确定对应的映射表 Class<?> attachClass(int type); //传入ViewHolder的Bind中的实体类 Object getBindItem(int pos, T t); } 复制代码
具体方案-> Wiki
public ToolKitBuilder(Context context, List<T> data) public ToolKitBuilder(Context context) 复制代码
方法名 | 描述 | 备注 |
---|---|---|
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对象 |
public SlotContext(Context context, List<T> data) public SlotContext(ToolKitBuilder<T> builder) 复制代码