移动互联网时代的应用离不开网络。大多数的移动应用界面都离不开下面的流程。
是以大多数界面都会有这么几个状态
- 载入状态,也就是正在与服务器交互数据(通常是个梅花旋转图)
- 网络出错状态(提示用户调整网络,或者重新加载)
- 数据异常状态(可能服务器出了问题,或者没有用户想要的数据,这里需要对用户做出特殊的提示)
- 正常的展示状态。
在一个应用中虽然每个页面展示的内容千差万别,但是网络请求,网络异常,数据异常却大多是一样的。所以有必要通过一个简单框架把这些页面管理起来,使我们专注于正常页面的开发,异常页面通过一定简单设置即可。
- 接口定义
这样一个需求可以用下面的接口描述
public interface LayoutManager {
void setOverallLayout(int layoutResId); //设置整体的布局文件,必须给业务页面预留空间,并包含异常页面
void setContentView(int layoutResId); //设置业务界面的布局文件
void setContentView(View view); //设置业务界面的布局文件
void setTitleContentView(int layoutResId); //设置title的布局
void setTitle(String title); //设置title的文字
void setEmptyDataHint(String hint); //设置数据为空页面的提示文案
void setPartView(int layoutResId, int coreViewId);//设置一个布局,异常页面只代表该布局中部分数据异常,其他部分异常时仍正常显示
View getContentView(); //获得完整的view
void showLoading(); //当前显示载入界面
void showNetWorkError();//当前显示网络错误界面
void showEmptyData(); //当前显示数据为空页面
void showEmptyData(String hint,String buttonText,View.OnClickListener listener);//显示数据为空页面,并设置
void showCoreContent(); //当前显示业务页面
}
- 整体的布局文件
其中setOverallLayout函数必须指定一个整体的布局文件,这个布局文件需要有个大的FrameLayout将几个异常页面及给正常页面预留的位置都包含在内。如下图:
其中我们可以看到这个布局已经包含了title(非必须)、body。body又有 corecontent(业务界面)、loading_page、loading_err、withoutdata(空数据)。title下面的placeholder比较特殊,后面我们会再提到。
- 接口的实现
针对不同的使用场景,共有4个类实现该接口:
- LayoutManagerHelper 是一个虚类,完成了大部分的接口实现。主要为了重用代码。
- FrameLayoutImpl 是为Fragment使用的接口实现。
- ActivityLayoutManagerImpl 是为使用自定义Title的Activity使用的实现。
- ActionBarActivityManagerImpl 是为使用ActionBar的Activity使用的实现。
核心函数及代码
核心函数是LayoutManagerHelper 中的setContentView函数。该函数非常简单:
@Override
public void setContentView(int layoutResId) {
//载入整体的布局文件
contentView = inflater.inflate(mOverallLayout, null);
//将业务布局文件挂载到整体布局文件中预留的位置中。
inflater.inflate(layoutResId, (ViewGroup) contentView.findViewById(R.id.ltm_corecontent));
}
结合上面整体布局文件。这个框架的意图就是每个界面都是先加载整体的布局文件,然后将业务布局文件挂载到整体布局文件的特定位置上。这样每个页面都有了自己的异常页面也有了各自的千差万别的业务界面。然后我们通过showLoading、showEmptyData等函数将整体布局中不同的ViewGroup设置为GONE或者VISIBLE即可方便的实现切换。
- 关于setPartView 函数
特别要说明下的就是setPartView函数。这个函数源于一个经常遇到的需求,界面上半部分无论正常还是异常状态都不必,下半部分则会根据不同的状态显示成载入页、业务页、空页面等。
以下是实现代码:
@Override
public void setPartView(int layoutResId, int coreViewId) {
//首先载入整体布局
contentView = inflater.inflate(mOverallLayout, null);
//载入业务布局
View coreView = inflater.inflate(layoutResId, null);
//找到业务布局中的核心View,也就是需要被覆盖成loading,loading_err等页面的部分
View centerView = coreView.findViewById(coreViewId);
//从业务布局中将核心View移除
((ViewGroup) centerView.getParent()).removeView(centerView);
//到这里,业务布局中只剩下无论什么状态都必须显示的部分
//将业务布局挂载到placeholder中
((ViewGroup) contentView.findViewById(R.id.ltm_placeholder)).addView(coreView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
//将核心View挂载到corecontent中,
((ViewGroup) contentView.findViewById(R.id.ltm_corecontent)).addView(centerView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
根据代码我们可以找到整体布局中placeholder的作用。
- 落地
public abstract class BaseActivity extends ActionBarActivity{
......
private LayoutManager layoutManager;
public LayoutManager getLayoutManager() {
if (layoutManager == null) {
layoutManager = new ActionBarActivityManagerImpl(this);
layoutManager.setOverallLayout(R.layout.ltm_frame_notitle);
}
return layoutManager;
}
}
这样,只要继承自BaseActivity的类直接调用getLayoutMananger函数即可完成我们期望的工作。