Android框架篇—— 从零开始搭建一个完善的MVP开发框架(四) —对View(Activity,Fragment等)层组件进行封装简化View层的开发

原创声明: 该文章为原创文章,未经博主同意严禁转载。

摘要: 通过上面三篇文章所提到的关于MVP框架的封装,我们已经能够大大简化MVP模式中MP层的开发流程。但是还有一个问题,就是在开发的时候我们的 View层组件还需要处理较多的事情,例如错误处理,进度条显示等。所以我们需要对View层的组建进行封装,优化开发的流程。

对View组建进行优化

笔者根据封装好的MVP的特点,对View层的组建进行了不同的封装。里面包含2个Activity、两个Fragment和一个Adapter基类。它们的名称分别是:MvpActivity 、MvpListActivity、MvpFragment、MvpListFragment、BaseListAdapter。它们的目的都是为了优化项目的代码和结构,加快项目的开发速度。
在这里笔者只对MvpActivity 这个类进行解析,其他的可以通过查看笔者github上的源码来学习实现方式。MvpActivity中封装了前面我们提到的一些公共方法,它的作用是结合我们前面封装好的BasePresenter使用,实现对项目进行优化的目的。使用普通的presenter获取数据的activity可以通过继承这个类来减少需要自己手动实现的代码。
如果读者想进一步了解其他几个组件的具体实现的话,请到笔者的github上下载。

处理Toolbar

笔者在开发这个框架的时候是采用Material Design风格进行开发的,采用这个风格进行开发的时候会有一个问题,就是每个Activity都需要重新实现一个toolbar。所以对Activity进行优化的第一项任务就是解决这个问题。
我们先来看看MvpActivity的界面实现:
activity_mvp.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layoutwidth="matchparent"
android:layoutheight="matchparent"
android:fitsSystemWindows="true"
tools:context="com.mvp.framework.module.base.view.activity.MvpActivity">
<android.support.design.widget.AppBarLayout
android:layoutwidth="matchparent"
android:layoutheight="wrapcontent"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layoutwidth="matchparent"
android:layoutheight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/contentmvp" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layoutwidth="wrapcontent"
android:layoutheight="wrapcontent"
android:layoutgravity="bottom|end"
android:layoutmargin="@dimen/fabmargin"
android:visibility="gone"
app:srcCompat="@android:drawable/icdialogemail" />
</android.support.design.widget.CoordinatorLayout>

上面的代码和我们平时的Material Design页面布局时一样的。下面我们来看下content_mvp的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.mvp.framework.module.base.view.activity.MvpActivity"
tools:showIn="@layout/activity_mvp">
<FrameLayout
android:id="@+id/content_base"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
>
<ProgressBar
android:id="@+id/base_progress_bar"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
style="?android:attr/progressBarStyle"
/>
<LinearLayout
android:id="@+id/base_error_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:id="@+id/base_error_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"/>
<TextView
android:id="@+id/base_error_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="网络连接错误,点击从新连接"/>
</LinearLayout>
</FrameLayout>
</android.support.v4.widget.SwipeRefreshLayout>

从布局可以看到,content_mvp中包含了SwipeRefreshLayout、ProgressBar、和显示错误的layout等。那么是如何实现让子Activity不需要处理toolbar的呢?笔者这里用了个简单暴力的办法。你看见上面布局中的FrameLayout了吗?看见了对吧?我就是用FrameLayout的addView方法把子Activity中的布局加进去的。
就好像下面这样:

1
2
3
4
contentView = onCreateView(getLayoutInflater(),contentViewGroup,savedInstanceState);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCHPARENT,FrameLayout.LayoutParams.MATCHPARENT);
contentViewGroup.addView(contentView,lp);`

这个onCreateView就是在子Activity中需要实现的方法,和fragment的onCreateView使用方法是一样的。而contentViewGroup就是我们上面说的fragment。

这样就可以通过MvpActivity基类简化这一步操作。

实现IMvpActivity接口

IMvpActivity的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public interface IMvpActivity extends IActivity{
/
@Method: onCreateView
@author create by Tang
@date date 16/10/20 下午2:32
@Description: 创建子activity布局
/
@NonNull
View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState);
/
@Method: onReconnection
@author create by Tang
@date date 16/10/20 下午4:00
@Description: 重新连接
这里要注意的是,如果一个页面里面有多个获取数据的presenter,
需要确认获取数据失败的presenter
具体需要根据实际业务来处理
/
void onReconnection();
/
@Method: setProgressType
@author create by Tang
@date date 16/10/25 上午10:54
@Description:
设置加载进度的样式
初始化为默认模式,
在子类中调用该方法可以重设进度条的样式
/
void setProgressType(int progressType);
}

IMvpActivity继承了IActivity接口,下面我们来看看IActivity接口的代码
IActivity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface IActivity {
/
@Method: setTitle
@author create by Tang
@date date 16/10/20 上午11:06
@Description: 设置页面标题
/
void setTitle(String title);
/
@Method: setDisplayHomeAsUpEnabled
@author create by Tang
@date date 16/10/20 上午11:16
@Description: ActionBar上是否显示返回按钮
/
@NonNull
boolean setDisplayHomeAsUpEnabled();
FloatingActionButton getFloatingActionButton();
}

上面的两个接口中大部分方法都十分简单,只是对activity执行一些初始化操作而已。其中setProgressType(int type)方法是用于设置我们在使用presenter发起获取数据请求的时候显示的进度条样式的。在这里笔者定义了三种样式,分别由dialog样式,默认样式和SwipeRefreshLayout样式。这个进度条样式在收到IMvpView接口的showProgress(final boolean show)的时候会显示/隐藏进度条。

实现IMvpView接口

我们先来看看IMvpView接口的定义:

1
2
3
4
5
6
7
8
9
10
11
public interface IMvpView extends IBaseMvpView{
/
@Method: isSucceed
@author create by Tang
@date date 16/10/26 下午4:33
@Description: 通知View层获取数据成功
在获取简单数据的时候用到
/
void showSucceed(boolean isSucceed);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public interface IBaseMvpView {
/
@Method: showProgress
@author create by Tang
@date date 16/10/26 上午11:06
@Description: 是否显示加载进度
/
void showProgress(final boolean show);
/
@Method: showNetworkError
@author create by Tang
@date date 16/10/26 上午11:08
@Description: 网络连接错误时Presenter层会调用该方法通知View层
/
void showNetworkError(int errorCode, String errorDesc, String ApiInterface);
/
@Method: showServerError
@author create by Tang
@date date 16/10/26 上午11:09
@Description: 服务器返回错误时Presenter层会调用该方法通知View层
/
void showServerError(int errorCode, String errorDesc);
}

可以看到,这就是我们前面的文章中提到的IBaseView接口,这个接口的主要是用于处理错误回调与进度条的显示/隐藏。

下面我们来看看几个方法的实现:
showProgress(boolean show)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Override
public void showProgress(boolean show) {
switch (progressType){
case PROGRESSTYPEDEFAULT:
if (show){
if (contentView != null){
contentView.setVisibility(View.GONE);
}
defaultProgress.setVisibility(View.VISIBLE);
}else {
defaultProgress.setVisibility(View.GONE);
if(contentView != null){
contentView.setVisibility(View.VISIBLE);
}
}
break;
case PROGRESSTYPEDIALOG:
if(show){
dialogProgress.show();
}else {
dialogProgress.dismiss();
}
break;
case PROGRESSTYPEDROPDOWN:
refreshLayout.setRefreshing(show);
break;
}
}

由于我们已经生成了不同样式的进度条实例,所以我们在这里根据设置的进度条样式来显示具体的进度条。这里需要注意的是,为了更好的人机交互体验,我们要根据不同的进度条样式来选择不同重连机制的处理方式,这里分别实现了点击错误提示重连和下拉重连,更多的实现读者可以自己进行拓展。

showNetworkError和showServerError

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void showNetworkError(int errorCode, String errorDesc, String ApiInterface) {
showProgress(false);
LogUtil.e(getClass(), "showNetworkError: " + ApiInterface);
onError(errorCode,errorDesc);
}
@Override
public void showServerError(int errorCode, String errorDesc) {
onError(errorCode,errorDesc);
}

可以看到这两个错误回调的最终实现时onError方法,下面是onError的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Override
public void onError(int errorCode, String errorDesc) {
showProgress(false);
LogUtil.e(getClass(), "showServerError: error code = "
+ errorCode + " & error desc = " + errorDesc);
switch (progressType) {
case PROGRESSTYPEDEFAULT:
if (setErrorImageResource() != 0) {
errorImage.setImageDrawable(getResources().getDrawable(setErrorImageResource()));
}
if (!TextUtils.isEmpty(errorDesc)) {
errorText.setText(errorDesc);
}
contentView.setVisibility(View.GONE);
errorLayout.setVisibility(View.VISIBLE);
break;
case PROGRESSTYPEDIALOG:
contentView.setVisibility(View.GONE);
errorLayout.setVisibility(View.VISIBLE);
break;
case PROGRESSTYPEDROPDOWN:
if (setErrorImageResource() != 0) {
errorImage.setImageDrawable(getResources().getDrawable(setErrorImageResource()));
}
if (!TextUtils.isEmpty(errorDesc)) {
errorText.setText(errorDesc);
}
if (!isSucceed) {
contentView.setVisibility(View.GONE);
errorLayout.setVisibility(View.VISIBLE);
}else {
Snackbar.make(fab,errorDesc,Snackbar.LENGTHSHORT).show();
}
break;
}
}

错误信息的展现形式也是和进度条的样式有关的,这样做是为了优化交互体验。下面我们来看看这三种不同设置的实际效果。
Demo展示图片

对前面文章的补充

前几篇文章中,关于BasePresenter的设计有一个小小的补充,这是由于笔者编写代码的时候的一个小的bug。
错误代码:

1
2
3
4
5
6
7
8
9
10
11
@Override
public void accessServer(Params params) {
this.params = params;
mvpView.showProgress(true);
baseModel.sendRequestToServer();
}
@Override
public void cancelRequest() {
baseModel.cancelRequest();
}

错误的地方在于关于baseModel的调用,在这里我们应该使用getModel获取Model实例。为什么要这样做呢?因为BasePresenter默认使用的是VolleyModle,当在子Presenter需要使用其它类型的Model的时候,可以通过覆盖getModel()这个方法来重设Modle实例。
修改后的代码为:

1
2
3
4
5
6
7
8
9
10
11
@Override
public void accessServer(Params params) {
this.params = params;
mvpView.showProgress(true);
getModel().sendRequestToServer();
}
@Override
public void cancelRequest() {
getModel() cancelRequest();
}

小结

限于篇幅,笔者在这里只是对MvpActivity作了一个简单的介绍,但是核心思想是一样的,通过对一些公共方法的封装(不同activity的初始化,错误处理,进度条的现实隐藏都是类似的),实现简化我们的代码的目的。这个项目涉及的技术点较多,但是都是相对简单的,所以笔者在这里就不对其他view组件展开讨论了。大家可以通过阅读源码来加深体会。在这里再强调一遍,该项目的github地址是:https://github.com/DobbyTang/mvp-android-framework

下一篇预告

到目前为止,我们已经完成了MVP开发框架的搭,如果我们单单只是处理好MVP的基类开发的话,还不能满足我们的要求。当我们的项目体积大到一定的程度的话,维护起来还是有困难的。所以在下一篇我介绍如何把我们的框架改造成一个高内聚低耦合的组件化框架。
相信组件化这个概念在2016年大家也听过不少了,笔者会在下一篇文章中提供一个实现的思路给大家。如果大家有更好的想法欢迎在下面的留言板留言哦,或者直接pull request也可以。
这一章的彩蛋是:关于这个组件化的框架,笔者已经完成了,目前github上的主分之就是一个组件化的框架。因为笔者做鸡的写作能力,所以代码更新一般会比文章快一点,欢迎大家关注项目以获取最新动态哦。