android loading封装_我们经常用的Loading动画居然还有这种姿势

72b7d7a6658022f4dce04c0792395049.png

背景

Loading动画几乎每个Android App中都有。

一般在需要用户等待的场景,显示一个Loading动画可以让用户知道App正在加载数据,而不是程序卡死,从而给用户较好的使用体验。

同样的道理,当加载的数据为空时显示一个数据为空的视图、在数据加载失败时显示加载失败对应的UI并支持点击重试会比白屏的用户体验更好一些。

加载中、加载失败、空数据的UI风格,一般来说在App内的所有页面中需要保持一致,也就是需要做到全局统一。

1. 传统的做法

  1. 定义一个(或多个)显示不同加载状态的控件或者xml布局文件(例如:LoadingView
  2. 每个页面的布局中都写上这个view
  3. BaseActivity/BaseFragment中封装LoadingView的初始化逻辑,并封装加载状态切换时的UI显示逻辑,暴露给子类以下方法:
  • void showLoading(); //调用此方法显示加载中的动画
  • void showLoadFailed(); //调用此方法显示加载失败界面
  • void showEmpty(); //调用此方法显示空页面
  • void onClickRetry(); //子类中实现,点击重试的回调方法
  1. BaseActivity/BaseFragment的子类中可通过上一步的封装比较方便地使用加载状态显示功能

这种使用方式耦合度太高,每个页面的布局文件中都需要添加LoadingView,使用起来不方便而且维护成本较高,一旦UI设计师需要更改布局,修改起来成本较高。

2. 好一点的封装方法

  1. 定义一个(或多个)显示不同加载状态的控件或者xml布局文件(例如:LoadingView
  2. 定义一个工具类(LoadingUtil)来管理LoadingView,不同状态显示不同的UI(或者在多个View之间切换显示)
  3. BaseActivity/BaseFragment中对LoadingUtil的使用进行封装,暴露给子类以下方法:
  • void showLoading(); //调用此方法显示加载中的动画
  • void showLoadFailed(); //调用此方法显示加载失败界面
  • void showEmpty(); //调用此方法显示空页面
  • void onClickRetry(); //子类中实现,点击重试的回调方法
  • abstract int getContainerId(); //子类中实现,LoadingUtil动态创建LoadingView并添加到该方法返回id对应的控件中
  1. BaseActivity/BaseFragment的子类中可通过上一步的封装比较方便地使用加载状态显示功能

这种封装的好处是通过封装动态地创建LoadingView并添加到指定的父容器中,让具体页面无需关注LoadingView的实现,只需要指定在哪个容器中显示即可,很大程度地进行了解耦。如果公司只在一个App中使用,这基本上就够了。

但是,这种封装方式还是存在耦合:页面与它所使用的LoadingView仍然存在绑定关系。如果需要复用到其它App中,因为每个App的UI风格可能不同,对应的LoadingView布局也可能会不一样,要想复用必须先将页面与LoadingView解耦。

如何解耦?

1. 梳理一下我们需要实现的效果

  • 页面的LoadingView可切换,且不需要改动页面代码
  • 页面中可指定LoadingView的显示区域(例如导航栏Title不希望被LoadingView覆盖)
  • 支持在Fragment中使用
  • 支持加载失败页面中点击重试
  • 兼容不同页面显示的UI有细微差别(例如提示文字可能不同)

2. 确定思路

说到View的解耦,很容易联想到Android系统中的AdapterView(我们常用的GridView和ListView都是它的子类)及support包里提供的ViewPager、RecyclerView等,它们都是通过Adapter来解耦的,将自身的逻辑与需要动态变化的子View进行分离。我们也可以按照这个思路来解耦LoadingView:

  • 创建一个工具类,用于管理LoadingView各个状态的UI展示
  • 创建一个Adapter接口,外部提供实现类,通过getView方法创建具体的LoadingView
  • 每个App提供一个Adapter的实现,并注册到工具类中
  • 工具类从Adapter.getView获取具体的LoadingView,所以页面中使用的代码无需改动
(已实现)页面的LoadingView可切换,且不需要改动页面代码
  • 由于每个页面或View的加载状态互相之间无关联关系,需要创建一个用于管理具体某个LoadingView的状态持有类:Holder
  • 指定LoadingView所需覆盖的View时,动态新建一个FrameLayout布局
  • 将原View从ParentView中移除,并用它的LayoutParams将FrameLayout添加到ParentView中替代原View在ParentView中的位置
  • 再将原View添加到FrameLayout中
  • 在Fragment.onCreateView/RecyclerView.Adapter.onCreateViewHolder等方法中创建的View时,由于View尚未添加到任何容器中,并无getParent()返回null,此时需要用动态生成的FrameLayout代替原View作为方法的返回值返回

上代码更容易理解:

public Holder wrap(View view) {FrameLayout wrapper = new FrameLayout(view.getContext());ViewGroup.LayoutParams lp = view.getLayoutParams();if (lp != null) {wrapper.setLayoutParams(lp);}if (view.getParent() != null) {ViewGroup parent = (ViewGroup) view.getParent();int index = parent.indexOfChild(view);parent.removeView(view);parent.addView(wrapper, index);}LayoutParams newLp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);wrapper.addView(view, newLp);return new Holder(mAdapter, view.getContext(), wrapper);
}
(已实现)页面中可指定LoadingView的显示区域
(已实现)支持在Fragment中使用
另外,还顺带支持在RecyclerView、ListView、GridView、ViewPager等情况下的使用
  • 为了不侵入UI,将加载失败点击重试的点击功能放在Adapter.getView中实现
  • 与Android系统中的Adapter不同的是,我们的Adapter是全局使用的,而失败重试所需执行逻辑每个页面都不一样
  • 因为Holder可以持有每个具体的LoadingView,可以将retryTask通过Holder传递给Adapter
  • 只需要在Adapter.getView时将Holder作为参数传入,即可在创建LoadingView时获取该retryTask对象,并在点击重试按钮时执行retryTask
  • 同理,可以通过Holder传递一些附加参数给Adapter,以兼容在不同页面上布局的细微差异
(已实现)支持加载失败页面中点击重试
(已实现)兼容不同页面显示的UI有细微差别(例如提示文字可能不同)

使用Gloading来轻松实现低耦合的全局LoadingView

Gloading是一个基于Adapter思路实现的深度解耦App中全局LoadingView的轻量级工具(只有一个java文件,不到300行,其中注释占100+行,aar仅6K)

1、 依赖Gloading

compile 'com.billy.android:gloading:1.0.0'

2、 创建Adapter,在getView方法中实现创建各种状态视图(加载中、加载失败、空数据等)的逻辑

Gloading不侵入UI布局,完全由用户自定义。示例如下:

public class GlobalAdapter implements Gloading.Adapter {@Overridepublic View getView(Gloading.Holder holder, View convertView, int status) {GlobalLoadingStatusView loadingStatusView = null;//convertView为可重用的布局//Holder中缓存了各状态下对应的View//  如果status对应的View为null,则convertView为上一个状态的View//  如果上一个状态的View也为null,则convertView为nullif (convertView != null && convertView instanceof GlobalLoadingStatusView) {loadingStatusView = (GlobalLoadingStatusView) convertView;}if (loadingStatusView == null) {loadingStatusView = new GlobalLoadingStatusView(holder.getContext(), holder.getRetryTask());}loadingStatusView.setStatus(status);return loadingStatusView;}class GlobalLoadingStatusView extends RelativeLayout {public GlobalLoadingStatusView(Context context, Runnable retryTask) {super(context);//初始化LoadingView//如果需要支持点击重试,在适当的时机给对应的控件添加点击事件}public void setStatus(int status) {//设置当前的加载状态:加载中、加载失败、空数据等//其中,加载失败可判断当前是否联网,可现实无网络的状态//      属于加载失败状态下的一个分支,可自行决定是否实现}}
}

3、 初始化Gloading的默认Adapter

Gloading.initDefault(new GlobalAdapter());

注:可以用AutoRegister在Gloading类装载进虚拟机时自动完成初始化注册,无需在app层执行注册,耦合度更低

4、在需要使用LoadingView的地方获取Holder

//在Activity中显示, 父容器为: android.R.id.content
Gloading.Holder holder = Gloading.getDefault().wrap(activity);//传递点击重试需要执行的task,该task在Adapter中用holder.getRetryTask()获取
Gloading.Holder holder = Gloading.getDefault().wrap(activity).withRetry(retryTask);//传递点击重试需要执行的task和一个任意类型的扩展参数,该参数在Adapter中用holder.getData()获取
Gloading.Holder holder = Gloading.getDefault().wrap(activity).withRetry(retryTask).withData(obj);

or

//为某个View显示加载状态
//Gloading会自动创建一个FrameLayout,将view包裹起来,LoadingView也显示在其中
Gloading.Holder holder = Gloading.getDefault().wrap(view);//传递点击重试需要执行的task,该task在Adapter中用holder.getRetryTask()获取
Gloading.Holder holder = Gloading.getDefault().wrap(view).withRetry(retryTask);//传递点击重试需要执行的task和一个任意类型的扩展参数,该参数在Adapter中用holder.getData()获取
Gloading.Holder holder = Gloading.getDefault().wrap(view).withRetry(retryTask).withData(obj);

5、 使用Holder来显示各种加载状态

//显示加载中的状态,通常是显示一个加载动画
holder.showLoading() //显示加载成功状态(一般是隐藏LoadingView)
holder.showLoadSuccess()//显示加载失败状态
holder.showFailed()//数据加载完成,但数据为空
holder.showEmpty()//如果以上默认提供的状态不能满足使用,可使用此方法调用其它状态
holder.showLoadingStatus(status)

更多API详情请查看 Gloading JavaDocs

更多Demo示例代码请查看 Gloading Demo, 也可下载Demo apk体验

6、封装到BaseActivity/BaseFragment中

  • 让BaseActivity和BaseFragment的子类中使用LoadingView更方便
  • 子类中使用LoadingView的业务逻辑与实现分离
  • 如果原来就是封装到BaseActivity/BaseFragment中的,那么可以无缝切换到Gloading
  • 如果以后需要将Gloading移除替换成其它实现,也无需修改业务代码

示例代码如下:

public abstract class BaseActivity extends Activity {protected Gloading.Holder mHolder;/*** make a Gloading.Holder wrap with current activity by default* override this method in subclass to do special initialization* @see SpecialActivity*/protected void initLoadingStatusViewIfNeed() {if (mHolder == null) {//bind status view to activity root view by defaultmHolder = Gloading.getDefault().wrap(this).withRetry(new Runnable() {@Overridepublic void run() {onLoadRetry();}});}}protected void onLoadRetry() {// override this method in subclass to do retry task}public void showLoading() {initLoadingStatusViewIfNeed();mHolder.showLoading();}public void showLoadSuccess() {initLoadingStatusViewIfNeed();mHolder.showLoadSuccess();}public void showLoadFailed() {initLoadingStatusViewIfNeed();mHolder.showLoadFailed();}public void showEmpty() {initLoadingStatusViewIfNeed();mHolder.showEmpty();}}

7、 兼容多App场景下的页面、View的复用

每个App的LoadingView可能会不同,只需为每个App提供不同的Adapter,不同App调用不同的Gloading.initDefault(new GlobalAdapter());,具体页面中的使用代码无需改动。

注:如果使用AutoRegister,则只需在不同App中创建各自的 Adapter实现类即可,无需手动注册。只需改动2处gradle文件即可:

  • 修改根目录build.gradle,添加对AutoRegister的依赖
buildscript {//...dependencies {//...classpath 'com.billy.android:autoregister:使用最新版'}
}
  • 修改主application module下的build.gradle,添加如下代码即可实现Adapter的自动注册
apply plugin: 'auto-register'
autoregister {registerInfo = [['scanInterface'             : 'com.billy.android.loading.Gloading$Adapter', 'codeInsertToClassName'   : 'com.billy.android.loading.Gloading', 'registerMethodName'      : 'initDefault']]
}

演示

dbddc238076ff1d7075be7267470eec7.gif

41f271df110a48df0daf7d2e06d68966.gif

c44f2477732431178ed5aff5fe9b7ce5.gif

为View添加加载状态

678b5af45a3f89bf7c915c6cae6b164f.gif

c591b35ff8427b445e09d44fa642c6b4.gif

aa3ca496b669cc155e062a8179279121.gif

8164b75a8ef31fcb5876c24dc83bcb49.gif

总结

本文介绍了全局LoadingView在实际使用过程中可能存在的一些耦合情况,并指出了由此会影响多个App的LoadingView的UI风格不一致导致页面难以复用的问题,同时给出了解决思路。

另外,本文着重介绍了如何使用Gloading来轻松实现低耦合的全局LoadingView,喜欢的同学请顺手甩个star支持一下 :)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/306802.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构——基于字符串模式匹配算法的病毒感染检测

实验四 基于字符串模式匹配算法的病毒感染检测 【实验目的】 1.掌握字符串的顺序存储表示方法。 2.掌握字符串模式匹配BF算法和KMP算法的实现。 【实验内容】 问题描述 医学研究者最近发现了某些新病毒,通过对这些病毒的分析,得知它们的DNA序列都是环状的。现在研究者已收集了…

WPF 使用 Expression Design 画图导出及使用 Path 画图

WPF 使用 Expression Design 画图导出及使用 Path 画图目录WPF 使用 Expression Design 画图导出及使用 Path 画图一、软件介绍二、Microsoft Expression Design 使用三、微语言和 Path 绘图1、"注释" 图形(中括号)2、"并行模式" 图…

数据结构——模式匹配kmp算法

暴力算法 //暴力算法 int index(SString S,SString T,int pos) {int ipos,j1;while(i<S[0]&&j<T[0]){if(S[i]T[j]){i;j;}else {ii-j2;j1;}}if(j>T[0])return i-T[0];else return 0;} kmp算法 next[]数组的求法&#xff1a; 例子&#xff1a;abaabcac 模式串…

互联网时代供应链

供应链是指围绕核心企业&#xff0c;从配套零件开始&#xff0c;制成中间产品以及最终产品&#xff0c;最后由销售网络把产品送到消费者手中的、将供应商&#xff0c;制造商&#xff0c;分销商直到最终用户连成一个整体的功能网链结构。供应链管理的经营理念是从消费者的角度&a…

win7 计算器 android,教你巧妙应用Win7计算器和时钟

正文最新的Win7是一种个性化设计极强的操作系统&#xff0c;在许多细节方面都做到了人性化设计。其功能的DIY性非常明显&#xff0c;是XP系统远远不能比的。今天我们要说的是Win7计算器和时钟&#xff0c;除了可以计算和时间之外我们还可以让他们有哪些妙用呢&#xff1f;我们左…

真实经历:整整一年了,他是这样从程序员转型做产品经理的

这是头哥侃码的第224篇原创每年年底&#xff0c;有不少企业都会对一年内辛勤劳作的员工量身定做一些奖项。发个奖杯&#xff0c;给点奖金&#xff0c;让那些没得奖的人看看&#xff0c;咱们公司有多么的关注员工的闪光点&#xff0c;优秀之处。用人所长&#xff0c;容人所短&am…

数据结构—— 基于二叉树的算术表达式求值

实验五 基于二叉树的算术表达式求值 数据结构——中序表达式求值&#xff08;栈实现&#xff09; 实验目的&#xff1a; 1.掌握二叉树的二叉链表存储表示和二叉树的遍历等基本算法。 2.掌握根据中缀表达式创建表达式树的算法 3.掌握基于表达式树的表达式求值算法。 实验内容&a…

数字标牌 android,【浩鑫推出全球首款英特尔方案+Android系统数字标牌播放器】PjTime.COM 新品快讯 Intel...

世界知名迷你准系统领导品牌&#xff0d;浩鑫Shuttle&#xff0c;秉承开拓创新&#xff0c;引领行业发展的传统&#xff0c;此次创造性的推出全球首款采用英特尔硬件方案搭载Android系统的NS01A数字标牌播放器&#xff0c;为整个数字标牌行业贡献了全新的硬件解决方案。英特尔方…

BCVP开发者说第3期:Adnc

沉静岁月&#xff0c;淡忘流年1项目简介AdncAdnc是一个轻量级的.NetCore微服务快速开发框架&#xff0c;同时也可以应用于单体架构系统的开发。框架基于JWT认证授权、集成了一系列微服务配套组件&#xff0c;代码简洁、易上手、学习成本低、开箱即用。    框架前端基于Vue、…

数据结构——二叉树的非递归算法

二叉树的非递归算法 先序遍历非递归算法1 先序遍历非递归算法2 非递归交换左右孩子算法 使用栈来实现二叉树的非递归算法 栈的基本算法 #include<stdio.h> #include<bits/stdc.h> typedef int Status; #define OK 1 #define ERROR 0 #define TRUE 1 #define …

python字符串的表示_Python字符串方法总结

Python字符串方法图示&#xff1a; &#xff08;温馨提示&#xff1a;对图片点右键——在新标签页中打开图片&#xff09;1、index() 定义&#xff1a;查找并返回指定str的索引位置&#xff0c;如果没找到则会抛异常&#xff08;查找的顺序是从左至右&#xff09;可以指定范围&…

Kuma 1.0 GA发布,70多项新功能和改进

喜欢就关注我们吧&#xff01;Kuma 1.0 GA 现已发布&#xff0c;包含了 70 多种新功能和改进。Kuma 是一个现代的通用服务网格控制平面&#xff0c;基于 Envoy 搭建&#xff0c;Envoy 是一个为云原生应用设计的强大的代理软件。Kuma 高效的数据平面和先进的控制平面&#xff0c…

还在犹豫是否迁移.NET5?这几个项目已经上线了!

.NET5正式发布有十多天&#xff0c;博客园、知乎、技术群都讨论的非常热烈。关于项目是否迁移.NET5的话题讨论的尤为热烈&#xff0c;作为.NET十年老司机要告诉你&#xff0c;.NET5的迁移势在必行&#xff0c;当下就是最好的时机&#xff01;犹豫项目是否升级到.NET5的&#xf…

Android切换泰语,Android应用内切换语言

首先扯点别的&#xff1a;这是第一次在简书上写东西&#xff0c;我突然明白为啥这么多人在简书上写东西了&#xff0c;因为没有广告啊&#xff0c;哈哈。最近接触到Android 应用内切换语言的问题&#xff0c;研究了两天&#xff0c;做个记录先。实现了中文&#xff0c;英文&…

工程勘察设计收费标准2002修订版_黑龙江省哈尔滨新区智能轨道快运系统1号线项目勘察设计招标...

黑龙江省哈尔滨新区智能轨道快运系统1号线项目勘察设计第一标段招标公告招标编号&#xff1a;JTZGSJ20200011.招标条件本招标项目黑龙江省哈尔滨新区智能轨道快运系统1号线项目勘察设计已由上级部门批准建设&#xff0c;项目业主为哈尔滨交通集团有限公司&#xff0c;建设资金来…

从 3.1 到 5.0 —— OpenReservation 更新记

OpenReservation 从 asp.net core 3.1 到 5.0IntroOpenReservation 是一个开源的预约系统&#xff0c;最初的版本是我们学校的活动室预约系统&#xff0c;现在正逐步变成一个更为通用的预约系统。.NET5 发布之后也是把这个项目更新到了 5.0。这个项目是一个做了很长时间的项目&…

数据结构——哈弗曼编码问题

实验六 基于哈夫曼树的数据压缩算法 【实验目的】 掌握哈夫曼树的构造算法。掌握哈夫曼编码的构造算法。 【实验内容】 问题描述 输入一串字符,根据给定的字符串中字符出现的频率建立相应的哈夫曼树, 构造哈夫曼编码表,在此基础上可以对压缩文件进行压缩(即编码),同时可以对 压…

relation does not exist报错是什么意思_为什么Zookeeper天生就是一副分布式锁的胚子?...

“ 什么是分布式锁&#xff1f;分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中&#xff0c;常常需要协调他们的动作。图片来自 Pexels如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源&#xff0c;那么访问这些资源的时候&#xff0…

netcore一键部署到linux服务器以服务方式后台运行

AntDeploy 是我开发一款开源一键发布插件将本地vs中的代码&#xff0c;一键打包&#xff0c;部署到任意的远程服务器部署方式支持 windows服务&#xff0c;linux服务&#xff0c;docker容器&#xff0c;iis支持增量发布(只更新有修改的)支持一键回滚(出了问题快速恢复)支持查看…

数据结构——基于 Dijsktra 算法的最短路径求解

实验七 基于 Dijsktra 算法的最短路径求解 【实验目的】 掌握图的邻接矩阵表示法&#xff0c;掌握采用邻接矩阵表示法创建图的算法。掌握求解最短路径的 Dijsktra 算法。 【实验内容】 问题描述 一张地图包括 n 个城市,假设城市间有 m 条路径(有向图),每条路径的长度 已知。给…