记录一次修改刷新加载逻辑的思路
一、View 显示流程
- 创建 LoadingLayout,它是一个 FrameLayout,添加了四种状态的 View 显示:加载(mLoadingView)、错误(mErrorView)、空(mEmptyView)和成功(mSucceedView)。
BaseLoadingFragment # onCreateView
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, final Bundle savedInstanceState) {
//每次ViewPager要展示该页面时,均会调用该方法获取显示的View
LogUtils.i(TAG, " fragment on create view");
if (mContentView == null) {//为null时,创建一个
// 每次ViewPager要展示该页面时,均会调用该方法获取显示的View
LogUtils.i(TAG, " fragment on create loading layout");
mContentView = new LoadingLayout(UIUtils.getContext()) {
...
}
initView(mContentView.getSucceedView());
initCreated(savedInstanceState);
} else {
// 不为null时,需要把自身从父布局中移除,因为ViewPager会再次添加
ViewUtils.removeSelfFromParent(mContentView);
}
return mContentView;
}
LoadingLayout # init()
public LoadingLayout(Context context) {
super(context);
init();
}
private void init() {
mState = STATE_UNLOADED;//初始化状态
// 创建对应的View,并添加到布局中
// 该方法其实是加载一个布局
mLoadingView = createLoadingView();
if (mLoadingView != null) {
addView(mLoadingView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
// 同理,将其它两个布局加载并添加到当前 View
mErrorView = createErrorView();
...
mEmptyView = createEmptyView();
...
// SucceedView 的实例化与上面三个 View 不同
mSucceedView = createSucceedView();
...
}
protected View createLoadingView() {
return UIUtils.inflate(R.layout.loading_page_loading);
}
mSucceedView 的创建需要具体实现
public abstract View createSucceedView();
- 创建 mSucceedView。创建 LoadingLayout 时需要实现该抽象方法创建 mSucceedView。
BaseLoadingFragment # onCreateView()
mContentView = new LoadingLayout(UIUtils.getContext()) {
...
@Override
public View createSucceedView() {
LogUtils.i(TAG, "loading view create");
return BaseLoadingFragment.this.createSucceedView();
}
}
BaseLoadingFragment # createSucceedView() ==> BaseFragment # setContentView
// 加载完成的View
protected View createSucceedView() {
return setContentView();
}
protected abstract View setContentView();
该抽象方法需要子 Fragment 实现,从而通过子 Fragment 的 setContentView 方法创建各自的 SuccessView。
举例:
/**
* 类名称:NotArriveOrderListFragment
* 类功能:待送达列表
*/
public class NotArriveOrderListFragment extends BaseLoadingFragment {
...
@Override
protected View setContentView() {
return UIUtils.inflate(getActivity(), R.layout.fragment_not_arrive_order);
}
...
}
该子 Fragment 在 setContentView()
中加载了自己的布局,并作为 mSuccessView 返回给 LoadingLayout 进行管理。
- LoadingLayout 根据数据加载的不同状态来控制相关 View 的展示。
/**
* 获取待送达列表的返回信息
*/
public CHttpTask mNotArriveOrderListTask = new CHttpTask<NotArriveOrderListReq, NotArriveOrderListRes>() {
@Override
public void onTrueMsg(NotArriveOrderListReq request, NotArriveOrderListRes response) {
...
if (0 == mActivity.mUserInfo.workStatus) {
// 获取到的数据为空
if (ListUtils.isEmpty(mRowsList)) {
// 调用 show 方法展示 EMPTY 页面,隐藏其它页面
show(LoadingLayout.LoadResult.EMPTY);
return;
}
// 根据数据状态显示不同 View
show(check(mNotArriveOrderAdapter.getData()));
}else {
// 数据获取成功展示 SUCCEED 页面
show(LoadingLayout.LoadResult.SUCCEED);
...
}
}
...
};
show()
方法会调用 LoadingLayout(View 管理者)提供的 show()
方法。由于上面例子都是带参数的,所以只分析带参数的show()
方法。
LoadingLayout # show()
public synchronized void show(LoadResult result) {
mState = result.getValue();
showPageSafe();
...
}
这里 mState 已经获取到值了,也就获取到了当前的状态。接下来根据相应状态展示相关 View,隐藏无用 View。
/**
* 显示对应的View
*/
private void showPage() {
if (mLoadingView != null) {
mLoadingView.setVisibility(mState == STATE_UNLOADED || mState == STATE_LOADING ? View.VISIBLE : View.INVISIBLE);
}
if (mErrorView != null) {
mErrorView.setVisibility(mState == STATE_ERROR ? View.VISIBLE : View.INVISIBLE);
}
if (mEmptyView != null) {
mEmptyView.setVisibility(mState == STATE_EMPTY ? View.VISIBLE : View.INVISIBLE);
}
if (mSucceedView != null) {
mSucceedView.setVisibility(mState == STATE_SUCCEED ? View.VISIBLE : View.INVISIBLE);
if (mState == STATE_SUCCEED) {
loadViewSuccess();
}
}
}
到这里需要了解的流程就差不多走完了,接下来就是解决方案。
二、解决方案
方案一(放弃):
- 思路:将其它 VIew 都加载到 SuccessView 上,再根据状态进行展示。因为只有 SuccessView 上面有刷新和加载的控件。
- 过程:将其它 View 放到 SuccessView 上无法显示。
之后进行思考:每个 SuccessView 都是子 Fragment 独有的,如果这样做那么每个 Fragment 的 SuccessView 都要创建容器来装填其它 View,而且这个容器必须在刷新控件之中。
这样做每个 Fragment 的布局都要或多或少进行修改,或许可通过代码添加布局容器,但要保证代码添加的容器必须在刷新控件中,比较麻烦。
如果进行替换的话,其它 View 又不包含刷新控件,所以必须要替换到刷新控件的子 View 中才能实现刷新。 - 总结:修改代码量较大,改动较多,无法统一管理,废弃。
方案二(成功实现):
- 思路:LoadingLayout 添加刷新加载控件,里面包含子 View,让所有状态 View 都添加到这个容器 View 上。这样各种状态 View 的父 View 都在刷新控件中,方便统一管理。
- 过程:
- LoadingLayout 创建时加载布局文件,里面包含刷新控件和容器 View。
private void init() {
...
View mPView = createParentView();
// 先把加载的布局 View 添加进来
addView(mPView);
// 找到容器 View
mParentView = (PullFrameLayout) mPView.findViewById(R.id.fl_container);
// 把各种状态 View 添加到容器 View上
if (mLoadingView != null) {
mParentView.addView(mLoadingView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
...
mSucceedView = createSucceedView();
if (mSucceedView != null) {
mParentView.addView(mSucceedView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
}
protected View createParentView(){
return UIUtils.inflate(R.layout.parent_view);
}
- 添加成功后就可以进行刷新和加载了。
mPullRefreshLayout = (PullRefreshLayout) mPView.findViewById(R.id.pr_pull);
mPullRefreshLayout.setOnRefreshListener(mRefreshListener);
private PullRefreshLayout.OnRefreshListener mRefreshListener = new PullRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh(PullRefreshLayout pullRefreshLayout) {
loadData(true);
}
@Override
public void onLoadMore(PullRefreshLayout pullRefreshLayout) {
loadData(false);
}
};
public abstract void loadData(boolean isRefresh);
创建抽象方法传递刷新加载标记,以这种方式通知 Fragment 进行刷新和加载的操作。
- 子 Fragment 必须实现
loadData()
方法来执行刷新和加载操作。
举例:
NotArriveOrderListFragment # loadData()
@Override
protected void loadData(boolean isRefresh) {
if(isRefresh){
isLoadMore = false;
mCurrentPage = 1;
getNotArriveOrderListByHttp(mCurrentPage);
} else {
if (mCurrentPage < mPages) {
getNotArriveOrderListByHttp(++mCurrentPage);
} else {
UIUtils.showToastSafe(getResources().getString(R.string.not_more_data));
mContentView.setLoadMoreFinish();
}
}
}
当 mPullRefreshLayout 执行刷新和加载时会调用到具体的方法,子 Fragment 执行具体逻辑进行网络请求。
- 请求成功或失败时,停止 mPullRefreshLayout 的刷新加载操作。
public CHttpTask mNotArriveOrderListTask = new CHttpTask<NotArriveOrderListReq, NotArriveOrderListRes>() {
@Override
public void onTrueMsg(NotArriveOrderListReq request, NotArriveOrderListRes response) {
mContentView.setPullFinish();
...
}
}
mContentView 就是 LoadingLayout,提供停止刷新和加载的操作:
LoadingLayout # setPullFinish()
public void setPullFinish(){
UIUtils.runOnMainThreadDelay(new Runnable() {
@Override
public void run() {
mPullRefreshLayout.refreshFinish(PullRefreshLayout.SUCCEED);
mPullRefreshLayout.loadMoreFinish(PullRefreshLayout.SUCCEED);
}
},1000);
}
这里应根据 mPullRefreshLayout 的状态来确定是否停止刷新或加载,但是这个 mPullRefreshLayout 貌似没有获取状态的函数。。。
这样整个流程基本就结束了。
- 总结:虽然功能实现了但还是存在一些问题,有些功能还需扩展和优化,比如实现子 Fragment 修改参数进行定制。
方案三(建议):
- 针对使用此框架的项目:使用大家的解决方案,进行优化。
- 针对较新未使用此框架的项目:重新引入一个灵活性较高的刷新加载功能包,在需要使用的地方添加布局和进行控制即可。