电子市场总结(二)
1、封装一个LoadingPager
什么是LoadingPager?
在项目中我们可以看到,大多数页面都有五种情况发生
- 未加载
- 正在加载(转圈的那种)
- 加载成功了
- 加载失败了
- 加载成功但是没有任何数据
可以想象,我们的项目中需要大量的去构建这样的界面,如果我们简单只是将每个状态对应的页面先写完整,再在每一个Fragment中整合那样的工作量很大,而且不易维护。
此时为了适应这种大规模使用的现象,我们利用一个具备五种状态的FrameLayout来简化这些操作。
自定义LoadingPager的详细解释
1.自定义一个FrameLayout,命名为LoadingPager
2.五种数据加载的状态
public static final int
STATE_EMPTY = 0, // 未加载
STATE_ERROR = -1, // 加载失败
STATE_SUCCESS = 1, // 加载成功
STATE_LOADING = 2, // 正在加载(转圈的那种)
STATE_NO_DATA = -2; // 没有加载到数据(加载成功的第二种状态)
同时五种状态对应五种布局
private View mEmptyLayout, // 空布局
mErrorLayout, // 错误布局
mLoadLayout, // 加载中布局
mNoDataLayout, // 未加载到数据的布局
mSuccessLayout; //成功的布局,由调用者自定义
3.加载成功的布局
1.控制显示
可以看出LoadingPager虽然可以封装这些状态,并且可以根据不同的状态展示不同的布局,但是这些布局仍然可以自定义,并由调用者根据自己的需求更换对应状态的布局。
因此可以暴漏一些方法来让调用者去自定义这些布局。此处仅对成功的布局进行了拓展,当我们的状态变为 STATE_SUCCESS 时,切换成调用者提供的布局。代码如下
/**
* 展示当前的状态对应的布局,隐藏其他布局
*/
private void showCurrentLayout() {
mEmptyLayout.setVisibility(currentState == STATE_EMPTY ? VISIBLE : GONE);
mErrorLayout.setVisibility(currentState == STATE_ERROR ? VISIBLE : GONE);
mLoadLayout.setVisibility(currentState == STATE_LOADING ? VISIBLE : GONE);
mNoDataLayout.setVisibility(currentState == STATE_NO_DATA ? VISIBLE : GONE);
// 加载 数据成功 且是在布局初次加载的状态
if (currentState == STATE_SUCCESS && mSuccessLayout == null) {
mSuccessLayout = initSuccessLayout();
// 所加载的布局不能为空
if (mSuccessLayout != null)
addView(mSuccessLayout);
}
// 当加载成功的布局成功设置后,对其状态进行判断
if (mSuccessLayout != null)
mSuccessLayout.setVisibility(currentState == STATE_SUCCESS ? VISIBLE : GONE);
}
通过使用抽象方法的形式,强制要求调用者去实现加载成功的布局
2.控制当前状态
提供设置状态的方法,让调用者可以在自己想要的时间里去展示成功的布局
/**
* 设置Loadingpager当前的状态
*
*/
public void setCurrentState(int state) {
// 先判断当前结果是否为空
this.currentState = state;
showCurrentLayout();
}
3.重新加载的方法
暴漏一个可以设置重新加载事件的方法
public void setOnReloadClickListener(OnClickListener cl){
btn_load_again.setOnClickListener(cl);
}
2、封装一个BaseFragment
1.为什么还要封装一个BaseFragment?
在前面我们已经封装了一个LoadingPager,我们可以很方便的控制这个自定义view去设置当前的状态和布局。
那么这个view应该在那里显示呢?view本身不能去控制当前的状态,调用者控制其显示的逻辑又在哪里呢?
提出这两个问题的同时,我们还要考虑,这么多的fragment他们都要用到LoadingPager因此我们应将这个两个问题的解决方法统一的封装起来,在一个BaseFragment中去处理,其他想要使用这种LoadingPager模式的Fragment只要继承BaseFragment就可以只关心自己的数据加载和界面展示了。
2.具体的封装
1.onCreateView中去展示LoadingPager,同时要求子类实现initSuccessLayout()方法来设置加载成功的布局,因为BaseFragment虽然实现了LoadingPager,但是他仍然需要将这个问题交给子类。
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//统一使用自定义的具备状态的loadingpager
mRootLayout = new LoadingPager(mActivity) {
@Override
public View initSuccessLayout() {
mSuccessLayout = BaseFragment.this.initSuccessLayout();
return mSuccessLayout;
}
};
//设置重新加载的监听器
mRootLayout.setOnReloadClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loading();
}
});
return mRootLayout;
}
2.同时我们可以将Activity在BaseFragment中拿出来,这样子类可以直接使用
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.mActivity = getActivity();
}
3.加载数据的逻辑
这里考虑到所有的数据加载都需要IO处理或者是通过网络来加载,那么可以BaseFragment里使用子线程这件事统一包办了,子类只需要完成 loadData() 方法返回他的加载状态即可。子类来返回加载状态的原因是,BaseFragment并不知道是怎么去加载的,所以这个逻辑仍然要抛给子类。
/**
* BaseFragment统一具备的加载方法,子线程加载
*/
public void loading() {
// 考虑到大多数的加载过程都是请求网络,所以采用子线程
ThreadManager.ThreadPool instance = ThreadManager.getInstance();
instance.execute(new Runnable() {
@Override
public void run() {
// 子类加载后返回对应的状态,如何去控制显示的问题父类来解决即可
final BaseFragment.State state = loadData();
// 设置loadingpager的状态
UIUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
if (mRootLayout != null && state != null)
mRootLayout.setCurrentState(state.getState());
}
});
}
});
}
4.为子类中对数据的加载成功与否提供一个默认的判断
/**
* 提供给子类进行检查初次加载成功与否
*
*/
protected State checkLoad(List list) {
if (list != null) {
if (list.size() == 0)
return State.STATE_NO_DATA;
else
return State.STATE_SUCCESS;
}
return State.STATE_ERROR;
}
5.重新加载的逻辑,即对重新加载的按钮设置点击事件
//设置重新加载的监听器
mRootLayout.setOnReloadClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loading();
}
});
6.总结,子类需要完成这些事情
- protected abstract View initSuccessLayout(); // 加载成功后是什么样的布局
- protected abstract State loadData(); // 加载数据的逻辑
- protected abstract void initListener(); // 监听的初始化
封装完成后的效果如下:
3、其他的知识点
1.枚举的用法
枚举他实际相当于一个变相的class类,他可以设置构造器,但是他的主要目的还是定一些不会被改变的值,如下将LoadingPager对应的状态进行了封装,那么子类在加载数据的时候就可以只返回枚举值而不是int数值了:
/**
* 枚举的用法
*/
public static enum State {
STATE_ERROR(LoadingPager.STATE_ERROR),
STATE_LOADING(LoadingPager.STATE_LOADING),
STATE_SUCCESS(LoadingPager.STATE_SUCCESS),
STATE_NO_DATA(LoadingPager.STATE_NO_DATA);
int state;
// 枚举的构造器实际上只能自己使用,可以通过这种方式,为自己设置数据
State(int state) {
this.state = state;
}
// 获取枚举存储的数据,调用者可以通过get方式去获取这个数据
int getState() {
return state;
}
}
4、封装思想的总结
LoadingPager和BaseFragment的封装抽取思想仅仅是整个项目的开始,其中LoadingPager是对布局显示的封装,BaseFragment是对如何使用LoadingPager和控制LoadingPager的封装。
通过这两层封装,子类所有集成自BaseFragment的对象只需要关心自己要显示什么和数据的加载即可。
在这里并没有涉及任何新的知识点,而是基本的封装思想的搭建。