源码拆解 LoadingAndRetryLayout一个 FrameLayout 如何巧妙实现加载、重试、空页面四态切换【免费下载链接】LoadingAndRetryManager无缝为Activity、Fragment、任何View设置加载loading、重试(retry)和无数据empty页面。项目地址: https://gitcode.com/gh_mirrors/lo/LoadingAndRetryManagerLoadingAndRetryManager 是一个轻量级的 Android 状态管理库核心只有两个类LoadingAndRetryLayout和LoadingAndRetryManager。它能把「加载中loading、重试retry、内容content、空数据empty」四种页面状态无缝地附加到任意 Activity、Fragment 甚至任何 View 上。很多新手好奇它凭什么能做到「一行代码接管整个页面」答案就藏在一个朴素的 FrameLayout 里。这篇文章带你逐行拆解它的源码看懂之后你会发现所谓巧妙的四态切换本质就是「四个子 View 叠放 可见性切换」的组合拳。一、先认识它四态到底是什么在拆源码之前先明确这个库管理的四个状态Loading加载中请求数据时的转圈等待页通常是一个 ProgressBarRetry重试请求失败后的错误提示页带一个「重试」按钮Content内容你自己的真实业务界面比如列表、详情页Empty空数据接口返回空列表时的占位页提示「暂无数据」。日常开发里这四个页面几乎所有 App 都要写一遍。这个库的目标就是让你只写一次到处复用。项目默认提供了三个基础布局分别是 base_loading.xml、base_retry.xml 和 base_empty.xml里面就是一个居中的 ProgressBar、一组错误文案加重试按钮、一行 No Data 提示。效果怎么样直接看官方的演示动图Activity/Fragment 场景下点击菜单即可在四种状态间来回切换。而「任意 View」场景更夸张哪怕只是一个 TextView也能被这套状态管理接管二、核心类 LoadingAndRetryLayout为什么选 FrameLayout打开 LoadingAndRetryLayout.java第一行就揭晓了谜底public class LoadingAndRetryLayout extends FrameLayout选 FrameLayout 是这个库最聪明的一个决定。FrameLayout 允许所有子 View 在同一个坐标位置叠加显示后添加的盖在前面的上面。这意味着四个页面可以同时存在于布局树中谁想显示就把谁设为VISIBLE其余全部设为GONE。切换状态不需要反复 inflate、removeView、addView性能开销几乎为零这也是它切换起来特别流畅的原因。再看它的成员变量四个状态页各占一个引用private View mLoadingView; // 加载中页面 private View mRetryView; // 重试页面 private View mContentView; // 内容页面 private View mEmptyView; // 空数据页面 private LayoutInflater mInflater;三、四态切换的核心逻辑showView 方法整个库最精华的部分就是 LoadingAndRetryLayout.java 中的showView(View view)私有方法。它的思路极其直白把目标 View 设为可见把其余三个全部隐藏。private void showView(View view) { if (view null) return; if (view mLoadingView) { mLoadingView.setVisibility(View.VISIBLE); if (mRetryView ! null) mRetryView.setVisibility(View.GONE); if (mContentView ! null) mContentView.setVisibility(View.GONE); if (mEmptyView ! null) mEmptyView.setVisibility(View.GONE); } // ... retry / content / empty 三个分支逻辑完全相同 }这段代码有四个近乎对称的分支进入哪个分支就把对应的那个 View 显示出来其他三个全部GONE。虽然写法上有点「重复」但胜在零抽象、零嵌套、一目了然——新手拿到手 10 秒就能看懂这也是这个库广受欢迎的原因之一。更贴心的是showLoading()、showRetry()、showContent()、showEmpty()这四个对外方法都做了主线程判断如果在子线程调用会自动通过post()切回主线程再更新 UI避免新手踩「不能在子线程操作 UI」的坑public void showLoading() { if (isMainThread()) { showView(mLoadingView); } else { post(new Runnable() { Override public void run() { showView(mLoadingView); } }); } }isMainThread()的实现也很巧妙直接比较当前 Looper 与主 Looperprivate boolean isMainThread() { return Looper.myLooper() Looper.getMainLooper(); }四、View 是怎么塞进来的setXXXView 系列方法四个 View 从哪来看 LoadingAndRetryLayout.java 的setXXXView系列方法就清楚了。每个方法都有两个重载一个接收 layoutId一个接收 View 对象。以加载页为例public View setLoadingView(int layoutId) { return setLoadingView(mInflater.inflate(layoutId, this, false)); } public View setLoadingView(View view) { View loadingView mLoadingView; if (loadingView ! null) { Log.w(TAG, you have already set a loading view and would be instead of this new one.); } removeView(loadingView); // 先移除旧的 addView(view); // 再加新的 mLoadingView view; return mLoadingView; }逻辑很简单先移除旧 View再 addView 新 View最后更新引用。注意 inflate 时的第三个参数是false意思是先不挂到父容器等addView时再挂——这样避免了双重 attach 的崩溃隐患。五、幕后操盘手 LoadingAndRetryManager偷梁换柱的魔术LoadingAndRetryLayout只是个容器真正把它「包」到页面上的是 LoadingAndRetryManager.java。它的构造函数接收一个参数这个参数可以是Activity、Fragment 或者任意 View然后执行三步「偷梁换柱」找到宿主Activity 用findViewById(android.R.id.content)Fragment 用fragment.getView().getParent()普通 View 用view.getParent()找到内容容器摘除原内容把原来的内容 View 从容器里removeView出来换上新容器在相同的位置、相同的 LayoutParams下把新建的LoadingAndRetryLayout插进去再把原内容通过setContentView(oldContent)放回里面。关键代码就这几行contentParent.removeView(oldContent); LoadingAndRetryLayout loadingAndRetryLayout new LoadingAndRetryLayout(context); ViewGroup.LayoutParams lp oldContent.getLayoutParams(); contentParent.addView(loadingAndRetryLayout, index, lp); loadingAndRetryLayout.setContentView(oldContent);因为位置和 LayoutParams 都没变用户肉眼看不出任何变化原有的内容 View 就像被「无缝包裹」了一层外壳。之后调用generate()返回的 manager 的showLoading()等方法就能在四态间任意切换了。六、自定义与回调OnLoadingAndRetryListener 接口想让某个页面用不一样的加载页/重试页只需实现 OnLoadingAndRetryListener.java 这个抽象类重写对应方法即可generateLoadingLayoutId()/generateLoadingLayout()自定义加载页generateRetryLayoutId()/generateRetryLayout()自定义重试页generateEmptyLayoutId()/generateEmptyLayout()自定义空数据页setRetryEvent(View retryView)绑定重试按钮的点击事件必须实现。isSetXXXLayout()方法会自动判断你是否提供了自定义布局如果没提供就回退到全局默认布局。全局默认布局在Application里设置一次即可见 MyApplication.java整个 App 的加载/重试/空页面风格就统一了。七、实战使用三分钟接入一个页面接入有多简单看 MainActivity.java 的示例核心就两行mLoadingAndRetryManager LoadingAndRetryManager.generate(this, listener); loadData();generate()的第一个参数传thisActivity或 Fragment 或任意 View第二个参数传监听器。在loadData()里按需调用四个 show 方法mLoadingAndRetryManager.showLoading(); // 开始加载 mLoadingAndRetryManager.showContent(); // 成功显示内容 mLoadingAndRetryManager.showRetry(); // 失败显示重试 mLoadingAndRetryManager.showEmpty(); // 无数据显示空页在setRetryEvent回调里给重试按钮绑上重新加载的逻辑一个完整的「加载-成功/失败-重试」闭环就完成了。整个接入过程不需要改动任何业务布局侵入性极低。八、总结这个 FrameLayout 教会我们什么回过头看LoadingAndRetryManager 的成功并没有依赖高深的技术而是把 Android 基础组件用到了极致FrameLayout 的叠加特性天然适合做「多页面共存、一键切换」View.GONE / VISIBLE 切换代替频繁的 addView/removeView兼顾性能与简单包装器模式LoadingAndRetryManager把复杂度封装掉对外只暴露 4 个 show 方法回退机制全局默认布局 局部自定义兼顾了统一风格与灵活定制。全文核心代码加起来不过 400 行却解决了几十上百个页面反复重复的痛点。下次再有人问「加载、重试、空数据页面怎么写」你大可以自信地回答一个 FrameLayout四个子 View一套 visibility 切换足矣。如果你想看更多扩展还可以参考 AnyViewTestActivity.java任意 View 场景和 FragmentTestActivity.javaFragment 场景把源码 clone 下来跑一遍 demo理解会更深。【免费下载链接】LoadingAndRetryManager无缝为Activity、Fragment、任何View设置加载loading、重试(retry)和无数据empty页面。项目地址: https://gitcode.com/gh_mirrors/lo/LoadingAndRetryManager创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考