记录一次修改刷新加载逻辑的思路

一、View 显示流程

  1. 创建 LoadingLayout,它是一个 FrameLayout,添加了四种状态的 View 显示:加载(mLoadingView)、错误(mErrorView)、空(mEmptyView)和成功(mSucceedView)。
LoadingLayout结构图

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();
  1. 创建 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 进行管理。

  1. 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 都在刷新控件中,方便统一管理。
方案二
  • 过程:
  1. 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);
}
  1. 添加成功后就可以进行刷新和加载了。
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 进行刷新和加载的操作。

  1. 子 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 执行具体逻辑进行网络请求。

  1. 请求成功或失败时,停止 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 修改参数进行定制。

方案三(建议):

  • 针对使用此框架的项目:使用大家的解决方案,进行优化。
  • 针对较新未使用此框架的项目:重新引入一个灵活性较高的刷新加载功能包,在需要使用的地方添加布局和进行控制即可。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,930评论 1 332
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,642评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,928评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,639评论 0 193
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,806评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,698评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,139评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,864评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,485评论 0 230
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,955评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,355评论 1 243
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,811评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,300评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,840评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,464评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,577评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,628评论 2 249

推荐阅读更多精彩内容