本系列文章集合:从 MVP学习代码封装 (1) - 综述
大家在做项目的时候绝对要面对一个需求,stateView - 缺省页,也有叫状态页,我比较喜欢叫状态页,这里我们今天要把 stateView 这个功能集成到我们的 MVP 基础框架中。为啥,你傻啊,这 stateView 可是基础功能,明显要整合到基础业务框架中的呀,难道你要傻傻的和笔者一样,要每个页面都写一遍吗!!!呵呵,笔者在工作的第一年还真就干了这 SB 事,现在回头想想也是感谢过去的自己冒傻气。当时产品每天对这个 statview 改来改去的,真是要死的心都有啊,那满满的怨念现在都还记忆犹新啊。也是有了泽洋深刻的经历,我对代码封装这块才开始上心,真是不经历不知道啊,这也才有了我的 MVP 这个系列的文章。
我的 stateView
stateView 的实现方案很多,和 MVP 一样,一个公司有自己的一套,开源的库也是有不少的,貌似这个 stateView 还真是个经久不衰的话题,不时的总是可以在各种分享场合看到关于 stateView 的文章和库,毕竟太典型了,而且实现上还真是很有味道啊,先写好不容易啊。
先来看看我写的例子,我写的效果很简单,主要目的还是向大家表述 我写 stateView 过程,而不是绚丽的效果:
我写的例子在显示上还是很灵活的:
- 在基类中设定 stateview 每个页面的 layoutID
- 然后你可以在具体的某个 activity/fragment/viewGroup 上设定某个类型状态页对应的 layoutID
- 默认是在 activity/fragment/viewGroup 的根布局上显示 stateview ,也可以设定 stateview 显示在哪个布局上。
上面说了下我简单封装的这个功能,目前我看到的做 stateview 这个功能的实现思路有3种:
- 最简单的, 在每个 activity/fragment/viewGroup 中的 xml 布局中 include 相关的状态页的 xml 进来,这种方法最简单,减少了我们很多 xml 布局中的代码,但是 java 类中的代码是没有经过封装的,还是有大量重复代码
- 在 activity/fragment 所属的 window 上 addView ,这样的思路就是开始从根本上封装代码了,这也应该是我们在写代码时首先应该想到怎么去封装这个功能。这个在 window 上 addView 的方案,还是很有一些开源库方案在用的,但是我觉得很实际的, 就是这个思路不甚灵活,因为 stateview 的页面只能显示在 整个屏幕上,那么自定义的 viewGroup 怎么办,我们要是想 stateview 显示在屏幕中的某一部分怎么办呢。我觉得灵活性和可维护性,简单易用性是代码封装的第一要务。
- 承接上个思路,还是 addView ,这次不是写死在 window 上了, 而是由用户决定,我们在 base 基类中可以默认给一种方案,我想这也是大部分的需求了,然后在具体的 activity/fragment/viewGroup中,可以根据具体的业务需求指定不同的布局以容纳 stateview
当然上面我都简单介绍过我的实现了,会跟明显的我就是使用的第三种。这种方式我是没看有第三方库用,可以全当是我自己想出来的,这里说下,笔者也是见识有限,要是各位有不同意见请见谅,毕竟在没见过相同实现的情况下,我自己写出了就可以当做我自己的实现了不是嘛。
实现思路
记住核心的是:我们是在现有 MVP 框架上添加一个新功能 stateview
那么根据上一节我们抽象封装声明周期的做法,使用组合的思路,给外部提供一个 stateview 的控制器,而不是具体的逻辑代码。那么首先我要思考下,MVP 中哪个角色应该持有这个 stateview 控制器,显然应该是 V 层,而不是 P 层,V 层关心的是具体显示,那么 stateview 作为显示的一部分,有 V 层来持有控制是合理的。
想明白这个功能加在谁的身上后,我们来思考下,这个功能应该如何合理的对外表达,显然 stateview 是要给 view 添加各种意外状态的页面,那么对于具体的 view 使用者来说,这些添加各种意外状态的页面的功能是有 view 提供的,而不关心 view 是如何实现的。好了,说到这里我们应该清楚了,这是一种代理模式的思路,我们抽象出 stateview 的相关功能接口,然后 view 去实现接口,由 view 内部持有的具体的 stateview 实现类来实现相关方法。特点是 view 会实现 stateview 的功能接口,而不是抛出一个 stateview 对象,这是对外隐藏逻辑实现的一种做法。我们当然可以对外提供 这个stateview 对象的方法,但是这只是为了照顾一些低度需求,而主要的对外使用上我们要按照代理模式来。
上面啰嗦了几句,是因为我们在一开始在这点上犹豫了下, 思考这个 stateview 功能应该以何种形式对外服务,是按照代理模式的思路来,还是对外直接把这个 stateview 对象抛出来。想了想,结合 MVP 的封装思路,还是代理模式的思路更合适。
总体结构:
一共涉及到这几个类:
- IStateView:对外功能接口
- DefaultStateView:外层控制器,对外提供给功能
- IStateViewProvide:内部提供 各种view 资源的接口,视为一个内部子系统
- BaseStateViewProvide:这个子系统的 abs 基类,这里考虑要扩展,未来号维护抽象出一个功能基类出来
- DefaultStateViewProvide:具体的子系统实现类,这个是默认实现,有其他实现可以继承 DefaultStateViewProvide ,或是 BaseStateViewProvide
- StateCodes:各种 view 对应的识别码
看到这里,整个结构应该很清晰了,毕竟是个小系统,没多少类,但是很适合练手啊,想写好这个小功能,各方面考虑到位也是很困难的。这几个类分以下几个角色:
- 外层功能接口:IStateView
- 顶层主控制器:DefaultStateView
- 子系统:IStateViewProvide / BaseStateViewProvide / DefaultStateViewProvide
- 公共资源:StateCodes
不管多大型,多小型的框架,功能系统,都是由上面这几个角色组合而成的:
- 外层功能接口封装了我们对外提供哪些方法功能
- 顶层主控制器是实现了这个外层功能接口,是提供给调用者使用的,要求使用简单,扩展方便,内部包含 N 多子系统提供具体功能,顶层主控制器值关心核心主逻辑实现。
- 子系统是从主逻辑中抽象分离出的功能,封装好了提供给顶层主控制器使用,这里我只是抽象出了 提供各种 view 对象的子系统出来。
- 公共资源,这个就不用说了。
写这个 stateView 小功能,处处体现出了组合的思路啊,不同的子系统组合在一起就是一个大系统。大系统臃肿就可以拆分成不同的小系统。
下面我们开始具体代码部分:
这个 stateview 也算是一个小小的框架了,作为练手非常适合。从头开始编写一个框架,都是先开始写主干结构,然后再去填充一个个具体功能实现。主干写清楚了,首先我们可以清楚无误的审视我们的结构是否合理,需要不需要再大的调整,要是都写完了再去打动,那就是大给自己找事了。另外的好处就是我们不会混乱了,结构都有了,剩下的照着写就好了,会杜绝我们写着写着突然发现某个地方写错了,发生结构错误,说实话可以大大节省我们的重复工作。
- 我们先来抽象 stateview 功能接口
stateview 是 view 对外展示功能的核心,view 实现哪个接口,调用者才能通过 view 去使用哪个接口的方法,这里我们对外提供:显示loading,网络错误,数据异常,没有数据,显示内容几个功能
public interface IStateView {
void showContent();
void showDataError();
void showDataEmpty();
void shownNetError();
void showLoading();
}
- 把 stateview 功能提供给 V 层对象,当然是在 MVP 的框架下。
首先 V 层的跟接口 IBaseView 继承 IStateView 接口,这样 MVP 框架下的多有 view 对象就都有 stateview 的功能了
public interface IBaseView extends IStateView {
DefaultStateView getStateView();
}
然后我们在 V 层的 abs 抽象基类中实现相关的 IStateView 接口的方法,这里涉及到:V 层的 abs 抽象基类如何持有,初始化 IStateView 接口具体实现类,如何添加显示 stateview 功能的默认公共代码。代码只放了有关的代码,其余的取掉了,DefaultStateView 是 IStateView 接口的具体实现类
public abstract class BaseActivity implements IBaseView {
// 持有 IStateView 对象引用
protected DefaultStateView mStateView;
// 返回IStateView 对象引用
@Override
public DefaultStateView getStateView() {
if (mStateView == null) {
initStateView();
}
return mStateView;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 初始化时间节点
init();
}
// 初始化方法
public void init() {
initStateView();
}
// 初始化方法
public void initStateView() {
mStateView = createStateView();
mStateView.setRootView((ViewGroup) getWindow().getDecorView());
}
public DefaultStateView createStateView() {
return new DefaultStateView(this);
}
// 代理 stateview 功能实现
@Override
public void showContent() {
getStateView().showContent();
}
@Override
public void showDataError() {
getStateView().showDataError();
}
@Override
public void showDataEmpty() {
getStateView().showDataEmpty();
}
@Override
public void shownNetError() {
getStateView().shownNetError();
}
@Override
public void showLoading() {
getStateView().showLoading();
}
经过上面2部的编写,我们就完成了 stateview 功能 在MVP 框架中的部分,下面就是 stateview 自身的实现了。
- 实现 stateview 控制类
这个 stateview 控制类用于对外提供相关功能调用,多说点这个也可以叫门板类,是外观设计模式的概念,外观设计模式把一个复杂的框架或是功能称为:系统。这里我们的 stateview 功能也是可以称之为一个系统,不管这个系统内部实现有多么的复杂,对调用者来说我只要能简单的调用就好了,才不关心你怎么实现的,那么这个门板类就是给调用者使用的,简要求简单易用,不要拖泥带水。
DefaultStateView 这个是我们的控制类,实现 IStateView 接口
public class DefaultStateView implements IStateView {
public Context context;
private ViewGroup rootView;
private IStateViewProvide mStateViewProvide;
public DefaultStateView(Context context) {
this.context = context;
init();
}
private void init() {
initStateViewProvide(context);
}
private void initStateViewProvide(Context context) {
mStateViewProvide = new DefaultStateViewProvide(context);
}
public IStateViewProvide getStateViewProvide() {
return mStateViewProvide;
}
public void setContext(Context context) {
this.context = context;
invalidata(context);
}
@Override
public void showContent() {
if (rootView == null) {
return;
}
if (StateCodes.CONTENT.code != mStateViewProvide.getCurrentStateViewCode()) {
cleanCurrentStateView();
}
}
@Override
public void showDataError() {
if (rootView == null) {
return;
}
if (StateCodes.DATA_ERROR.code != mStateViewProvide.getCurrentStateViewCode()) {
addDataErrorStateView();
}
}
@Override
public void showDataEmpty() {
if (rootView == null) {
return;
}
if (StateCodes.DATA_EMPTY.code != mStateViewProvide.getCurrentStateViewCode()) {
addDataEmptyStateView();
}
}
@Override
public void shownNetError() {
if (rootView == null) {
return;
}
if (StateCodes.NET_ERROR.code != mStateViewProvide.getCurrentStateViewCode()) {
addNetErrorStateView();
}
}
@Override
public void showLoading() {
if (rootView == null) {
return;
}
if (StateCodes.LOADING.code != mStateViewProvide.getCurrentStateViewCode()) {
addloaingStateView();
}
}
public void cleanCurrentStateView() {
View currentStateView = mStateViewProvide.getCurrentStateView();
if (currentStateView == null || rootView == null) {
return;
}
rootView.removeView(currentStateView);
mStateViewProvide.setCurrentStateViewCode(StateCodes.CONTENT.code);
}
public void setRootView(ViewGroup rootView) {
this.rootView = rootView;
}
private void addDataErrorStateView() {
cleanCurrentStateView();
addStateViewByStateViewCode(StateCodes.DATA_ERROR.code);
}
private void addDataEmptyStateView() {
cleanCurrentStateView();
addStateViewByStateViewCode(StateCodes.DATA_EMPTY.code);
}
private void addNetErrorStateView() {
cleanCurrentStateView();
addStateViewByStateViewCode(StateCodes.NET_ERROR.code);
}
private void addloaingStateView() {
cleanCurrentStateView();
addStateViewByStateViewCode(StateCodes.LOADING.code);
}
private void addStateViewByStateViewCode(int stateViewCode) {
View view = mStateViewProvide.getStateViewByCode(stateViewCode);
if (view == null || rootView == null) {
return;
}
rootView.addView(view);
mStateViewProvide.setCurrentStateViewCode(stateViewCode);
}
private void invalidata(Context context) {
if (context == null) {
return;
}
mStateViewProvide = new DefaultStateViewProvide(context);
}
}
- 实现 stateview 相关页面提供器
看过第三部的,可以看到有一个关键功能类: mStateViewProvide,这是提供相关 view 的功能类,这里觉得应该把 view 提供的功能从控制器类分离出来,再抽象出一个功能类来,因为外层控制器封装的是整个系统的逻辑,而期中较为复杂的代码片段可以再次封装成为一个这个系统中一个子系统,这样代码结构可以更清晰,编写页简单容易一些,不容易乱。这里 mStateViewProvide 这个对象就是对外提供 各种状态 view 的子系统对象。
考虑到这里以后会有扩展,所以灵活一些,封装出一个 abs 基类,放一些基础功能代码
public abstract class BaseStateViewProvide implements IStateViewProvide {
protected Context context;
protected int curState = StateCodes.CONTENT.code;
protected Map<Integer, Integer> stateIDs;
protected Map<Integer, View> stateViews;
public BaseStateViewProvide(Context context) {
this.context = context;
init();
}
@Override
public void setContext(Context context) {
this.context = context;
}
public int getCurState() {
return curState;
}
public Map<Integer, Integer> getStateIDs() {
if (stateIDs == null) {
initMap();
}
return stateIDs;
}
public Map<Integer, View> getStateViews() {
if (stateViews == null) {
initMap();
}
return stateViews;
}
public View getStateView(int stateViewCode) {
if (stateViews == null) {
init();
}
if (stateViews == null || stateViews.size() == 0) {
return null;
}
return stateViews.get(stateViewCode);
}
public int getStateViewID(int stateViewCode) {
if (stateIDs == null) {
init();
}
if (stateIDs == null || stateIDs.size() == 0) {
return 0;
}
return stateIDs.get(stateViewCode);
}
public View inflateView(int stateViewCode) {
if (context == null) {
return null;
}
int stateViewCode2 = stateViewCode;
Integer integer = getStateIDs().get(stateViewCode);
View view = LayoutInflater.from(context).inflate(integer, null, false);
if (view == null) {
return null;
}
getStateViews().put(stateViewCode, view);
return view;
}
private void init() {
initMap();
initData();
}
private void initMap() {
if (stateIDs == null) {
stateIDs = new HashMap<>();
}
if (stateViews == null) {
stateViews = new HashMap<>();
}
}
protected abstract void initData();
protected void invalidateData() {
if (stateViews == null || stateIDs == null) {
initMap();
}
stateViews.clear();
stateIDs.clear();
initData();
}
protected void setStateVIewIDAndCleanStateView(int stateVIewCode,int stateVIewID) {
getStateIDs().remove(stateVIewCode);
getStateIDs().put(stateVIewCode, stateVIewID);
getStateViews().remove(stateVIewCode);
}
然后具体实现这个功能 DefaultStateViewProvide ,核心就在于 重写 initData() 这个方法,在这个方法里绑定各种 状态 view 的 layoutID,未来扩展时可以考虑继承这个 DefaultStateViewProvide 类,重写 initData() ,也可以继承 BaseStateViewProvide 这个 abs 基类提供更多的功能,这里涉及到重写 外层控制器了,要不也不会设计到写个新的功能类了
public class DefaultStateViewProvide extends BaseStateViewProvide {
public DefaultStateViewProvide(Context context) {
super(context);
}
@Override
protected void initData() {
getStateIDs().put(StateCodes.LOADING.code, R.layout.layout_stateview_loading);
getStateIDs().put(StateCodes.DATA_ERROR.code, R.layout.layout_stateview_dataerror);
getStateIDs().put(StateCodes.DATA_EMPTY.code, R.layout.layout_stateview_dataempty);
getStateIDs().put(StateCodes.NET_ERROR.code, R.layout.layout_stateview_neterror);
}
public void setmDataErrorViewID(int mDataErrorViewID) {
setStateVIewIDAndCleanStateView(StateCodes.DATA_ERROR.code, mDataErrorViewID);
}
public void setmDataEmptyViewID(int mDataEmptyViewID) {
setStateVIewIDAndCleanStateView(StateCodes.DATA_EMPTY.code, mDataEmptyViewID);
}
public void setmNetErrorViewID(int mNetErrorViewID) {
setStateVIewIDAndCleanStateView(StateCodes.NET_ERROR.code, mNetErrorViewID);
}
public void setmloadingViewID(int mLoadingViewID) {
setStateVIewIDAndCleanStateView(StateCodes.LOADING.code, mLoadingViewID);
}
@Override
public View getCurrentStateView() {
return getStateViews().get(curState);
}
@Override
public int getCurrentStateViewCode() {
return curState;
}
@Override
public void setCurrentStateViewCode(int stateViewCode) {
curState = stateViewCode;
}
@Override
public View getStateViewByCode(int stateViewCode) {
View view = getStateViews().get(stateViewCode);
if (view == null) {
view = inflateView(stateViewCode);
}
return view;
}
}
总结
最后了,也没啥说的,大伙没事多看看设计模式,设计模式要灵活应用,不必分得按照设计模式的例子来,只要设计模式的恩那个给我们提供思路就行。
项目地址: BW-MVPDemo / step2_2 这部分。
其他资料
这个单元写完有些时间了,又看到一些新的文章,这里整理一下
- 有个朋友在总结自己的 stateview 时有一些感想很赞
- 找到一个开源项目和我的思路基本吻合,却别在于管理 stateview 地方不太一样,我的是在页面 base 层提供管理的,这位兄弟的是在页面层里,想了想,各有利弊,应该在页面层之外也能提供灵活优雅的使用,这点之后会考虑重构一下