1. 为什么一个加载动画库值得单独拿出来聊做Android开发的人都有一个共同的体验列表在加载数据的那两三秒里界面是空白的。用户不知道是在加载、卡死了、还是根本没反应。这时候一个转圈动画就能解决大问题。但真到自己动手写的时候你会发现事情没那么简单——用ProgressBar吧样式太单调自己写属性动画吧又要处理生命周期、内存泄漏、不同尺寸适配一堆破事。AVLoadingIndicatorView就是冲着这个痛点来的。它是一个开源的Android加载指示器集合库把常见的几十种加载动画效果封装成了可以直接在XML里引用的View组件。你不需要写一行动画代码加个依赖、写个标签、调个方法就能用。我第一次在项目里用它的时候从引入到跑通大概花了不到五分钟。这个库适合谁用如果你正在做Android App开发尤其是需要频繁展示网络请求等待状态的场景比如列表下拉刷新、页面首次加载、提交表单后的等待反馈那它基本可以帮你省掉自己写动画的时间。对于刚入门的Android开发者来说它也是一个很好的学习素材——你可以直接看它的源码了解属性动画在实际项目中是怎么组织的。需要提前说明的是这个库已经有些年头了最近一次实质性更新在几年前。但它的核心功能不依赖什么新API在目前主流的Android版本上依然能正常工作。我在Android 14的设备上实测过没有出现兼容性问题。当然如果你追求更现代的Compose方案那可能需要另找轮子但在传统View体系下它依然是个靠谱的选择。接下来我会从引入方式、核心用法、源码设计思路、实际项目中的踩坑经验几个角度把这个库彻底拆开讲清楚。不管你是第一次听说它还是已经在用但没深究过原理应该都能找到有用的东西。2. 把AVLoadingIndicatorView跑起来从依赖到第一个动画2.1 依赖引入的两种方式与选择逻辑引入这个库有两种方式我分别说一下适用场景。第一种是通过Gradle依赖。在模块级的build.gradle文件里添加dependencies { implementation com.wang.avi:library:2.1.3 }这是最省事的方式版本号2.1.3是Maven Central上比较稳定的一个版本。加完之后同步一下Gradle就能用。但这里有个坑要注意这个库托管在Maven Central上如果你公司内网把Maven Central镜像到了私服需要确认私服有没有同步这个包。我有一次在客户现场就遇到了拉不下来的情况后来是让运维手动同步的。第二种方式是把源码直接拷进项目。这个库的代码量不大核心就是几个类加一堆动画资源文件。如果你只需要其中某几个效果或者项目有严格的第三方库审查流程直接拷源码反而更省事。具体做法是从GitHub上把项目clone下来把library模块下的src/main/java和src/main/res里相关文件复制到你自己的模块中。注意res/anim目录下的动画XML文件也要一起拷否则运行时会报资源找不到。提示如果你用的是AndroidX这个库本身不涉及support库的依赖所以不需要做Jetifier处理。但如果你项目里开了android.enableJetifiertrue也不会有冲突。2.2 在布局文件中使用最简配置引入之后在XML布局里就可以直接用了。最基本的写法是这样com.wang.avi.AVLoadingIndicatorView android:idid/avi_loading android:layout_widthwrap_content android:layout_heightwrap_content app:indicatorNameBallPulse app:indicatorColor#FF4081 /这里有两个自定义属性需要关注。indicatorName指定用哪种动画效果indicatorColor指定颜色。如果不写indicatorName默认是BallPulse不写indicatorColor默认取主题的强调色。layout_width和layout_height建议用wrap_content因为每种动画的固有尺寸不一样写死固定值可能导致动画被裁切。这个库内部会根据动画类型计算合适的测量尺寸用wrap_content让它自己决定就好。2.3 代码中控制显示与隐藏在Activity或Fragment里拿到这个View的引用后通过两个方法控制AVLoadingIndicatorView avi findViewById(R.id.avi_loading); // 显示并开始动画 avi.show(); // 隐藏并停止动画 avi.hide();show()内部会调用setVisibility(VISIBLE)并启动动画hide()则相反。这里有一个设计细节值得注意hide()不仅把View隐藏了还会把动画停掉。这个很重要——如果只隐藏不停动画动画会一直在后台跑白白消耗CPU和电量。我见过有同事直接用setVisibility(GONE)来隐藏结果动画线程还在跑页面卡顿查了半天才找到原因。如果你需要更细粒度的控制还有smoothToShow()和smoothToHide()两个方法带淡入淡出效果视觉上更平滑。不过实测下来在快速切换的场景下比如列表频繁刷新淡入淡出反而会让人觉得响应慢直接用show()和hide()更干脆。2.4 可用动画效果一览与选型建议这个库内置了三十多种动画效果我挑几个常用的说一下适用场景动画名称视觉效果推荐场景BallPulse三个小球依次缩放通用加载最百搭BallSpinFadeLoader八个球旋转渐隐页面级加载LineSpinFadeLoader线条旋转渐隐列表底部加载更多BallBeat三个球交替跳动轻量级操作等待Pacman吃豆人动画趣味性强的AppSemiCircleSpin半圆旋转简洁风格TriangleSkewSpin三角形翻转科技感界面选型的时候有个原则动画的视觉重量要和等待时间匹配。如果是按钮点击后零点几秒的等待用BallPulse这种轻量的就够了如果是页面首次加载可能要好几秒可以用BallSpinFadeLoader这种视觉上更忙的效果让用户感觉系统在努力工作。另外要注意动画越复杂GPU渲染开销越大。在低端设备上同时跑多个复杂动画可能会导致掉帧。我的经验是一个界面上同时最多出现一个加载动画不要在一个列表的每个item里都放一个。3. 这个库内部是怎么运转的3.1 整体架构一个View加一个动画绘制器AVLoadingIndicatorView的架构其实很清晰核心就两层外层是一个继承自View的容器内层是一个Indicator接口的实现类负责具体的绘制逻辑。外层AVLoadingIndicatorView继承自View它负责测量、布局、以及把绘制工作委托给内部的Indicator对象。当你设置indicatorName的时候它通过反射创建对应的Indicator实例。比如设置BallPulse它就会去找BallPulseIndicator这个类并实例化。内层的Indicator是一个抽象类定义了draw、onBoundsChange等几个关键方法。每个具体的动画效果比如BallPulseIndicator都继承自它并实现自己的绘制逻辑。这种设计的好处是新增一种动画只需要写一个Indicator子类然后在AVLoadingIndicatorView的静态初始化块里注册一下就行完全符合开闭原则。3.2 动画驱动机制ValueAnimator的角色动画的驱动靠的是ValueAnimator。在AVLoadingIndicatorView的构造函数里它创建了一个ValueAnimator从0到1循环播放每一帧回调里调用invalidate()触发重绘。// 简化后的核心逻辑 animator ValueAnimator.ofFloat(0f, 1f); animator.setDuration(1000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { Override public void onAnimationUpdate(ValueAnimator animation) { // 通知Indicator当前进度 mIndicator.setAnimationProgress(animation.getAnimatedFraction()); invalidate(); } });每个Indicator子类在draw方法里根据当前进度值计算每个元素的位置、大小、透明度然后画出来。比如BallPulseIndicator就是根据进度值计算三个球的缩放比例形成脉冲效果。这种设计的巧妙之处在于所有动画共享同一个ValueAnimator不需要每种动画各自维护一套计时逻辑。而且进度值是标准化的0到1每个Indicator自己决定怎么解释这个值。3.3 测量逻辑为什么用wrap_content最合适AVLoadingIndicatorView重写了onMeasure方法。当宽高设置为wrap_content时它会向内部的Indicator询问建议尺寸。每个Indicator根据自己的绘制需求返回一个合适的尺寸通常是基于dp的一个固定值。如果你在XML里写死了宽高比如android:layout_width50dp那么View会被强制设成这个尺寸但Indicator绘制的时候还是按照自己的逻辑来可能导致动画被裁切或者周围留白过多。所以我在前面强调用wrap_content这是最省心的做法。3.4 反射创建Indicator的性能考量前面提到indicatorName是通过反射创建实例的。反射本身有性能开销但这个开销只发生在View初始化的时候而且创建一次之后就缓存起来了不会在每一帧里反复反射。所以对运行时性能几乎没有影响。不过有一个细节如果你在RecyclerView的onBindViewHolder里动态设置indicatorName每次绑定都会触发一次反射。虽然单次开销不大但在快速滑动列表时累积起来还是能感觉到。我的做法是在item布局里就把indicatorName写死不要在代码里动态改。4. 实际项目里踩过的坑和对应解法4.1 内存泄漏动画没停导致的连锁反应这是我在实际项目里遇到的最严重的问题。场景是这样的一个页面在加载数据时显示AVLoadingIndicatorView数据回来后调用hide()。但如果用户在数据还没回来时就按了返回键退出页面hide()就不会被调用动画会一直跑下去。更麻烦的是如果这个View被一个延迟任务持有比如网络回调而回调在Activity销毁后才执行就会导致Activity无法被回收。我在LeakCanary里看到过好几次这个库相关的泄漏报告。解法有两个层面。第一在onDestroy里确保调用hide()Override protected void onDestroy() { super.onDestroy(); if (avi ! null) { avi.hide(); } }第二如果是在RecyclerView的ViewHolder里使用在onViewRecycled里也要调用hide()。因为ViewHolder被回收时如果动画还在跑同样会持有引用。注意hide()方法内部会判断当前是否可见如果已经隐藏了再调用不会有副作用所以可以放心地在多个地方调用。4.2 在RecyclerView中复用的显示异常RecyclerView的复用机制会导致一个典型问题某个item的加载动画还没结束item被滑出屏幕回收了然后被复用到新位置。如果不在onBindViewHolder里重置状态新位置的item会莫名其妙地显示一个动画。我的处理方式是在onBindViewHolder开头统一重置Override public void onBindViewHolder(ViewHolder holder, int position) { // 先隐藏避免复用带来的状态残留 holder.avi.hide(); // 根据数据状态决定是否显示 if (data.isLoading()) { holder.avi.show(); } // ... 其他绑定逻辑 }这个先隐藏再按需显示的模式看起来有点笨但实测是最稳妥的。不要试图去判断当前是否正在显示因为复用场景下状态很难准确追踪。4.3 颜色设置不生效的几种情况indicatorColor这个属性有时候设了没反应常见原因有三个。第一个是主题覆盖。如果你在XML里设了indicatorColor但代码里又调用了setIndicatorColor()以代码设置为准。反过来也一样后设置的会覆盖先设置的。第二个是颜色格式问题。这个属性接受的是颜色值不是资源引用。如果你想用R.color.xxx需要先通过ContextCompat.getColor()转成int再传进去。在XML里可以直接写color/xxx但代码里不行。第三个比较隐蔽某些Indicator子类在绘制时会对颜色做透明度处理比如BallSpinFadeLoader会让每个球有不同透明度。如果你设的颜色本身透明度就很高叠加之后可能几乎看不见。建议用不透明的颜色值。4.4 与下拉刷新控件的配合问题很多项目会同时用下拉刷新和加载指示器。这两者如果配合不好会出现动画重叠或者闪烁。我的经验是下拉刷新用刷新控件自带的指示器页面级的加载用AVLoadingIndicatorView两者不要在同一区域同时出现。具体做法是下拉刷新时隐藏AVLoadingIndicatorView刷新结束后如果需要展示内容加载状态再显示。用一个状态变量来协调两者的可见性避免出现刷新圈和加载圈一起转的尴尬画面。4.5 低端设备上的性能表现我在一台2016年的低端机上做过测试同时显示三个BallSpinFadeLoader时帧率从60fps掉到了40fps左右。虽然不至于卡死但能感觉到不流畅。优化思路是在低端设备上降级使用更简单的动画。可以通过Build.VERSION.SDK_INT或者读取设备内存信息来判断但更简单的做法是统一用BallPulse这种轻量动画。它的绘制逻辑简单每帧只画三个圆对GPU压力很小。另外如果页面本身已经在做复杂绘制比如有大量图片加载加载动画的优先级应该降低。可以考虑用静态的占位图代替动画等页面稳定后再显示动画。5. 从使用到改造几个进阶玩法5.1 自定义一个Indicator的完整步骤虽然内置了三十多种效果但总有需要定制的时候。自己写一个Indicator其实不难分三步。第一步继承Indicator抽象类实现draw方法public class MyIndicator extends Indicator { Override public void draw(Canvas canvas, Paint paint) { // 在这里根据getAnimationProgress()的进度值绘制 float progress getAnimationProgress(); // 示例画一个逐渐变大的圆 float radius getWidth() / 4f * progress; canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, radius, paint); } Override public ListAnimator createAnimation() { // 如果不需要额外的animator返回空列表 return Collections.emptyList(); } }第二步在AVLoadingIndicatorView的静态注册表里加上你的类。找到源码里的INDICATORS这个Map添加一行映射INDICATORS.put(MyIndicator, MyIndicator.class);第三步在XML里用app:indicatorNameMyIndicator引用。如果你不想改源码也可以通过反射的方式在运行时注册但那样代码会比较绕。直接改源码重新打包更直接。5.2 用代码动态切换动画效果有时候需要根据场景切换不同的动画。比如正常加载用BallPulse出错重试时用BallBeat来引起注意。可以通过setIndicatorName()方法动态切换avi.setIndicatorName(BallBeat); avi.show();但要注意切换indicatorName会重新创建Indicator实例如果当前正在显示动画会有一个短暂的重置过程。建议先hide()再切换再show()视觉上更连贯。5.3 把动画进度暴露给外部使用Indicator的getAnimationProgress()返回的是0到1的进度值。如果你想让外部也能感知这个进度比如同步更新一个进度文字可以通过自定义Indicator加一个回调接口来实现。这个用法比较小众但在做自定义加载页的时候挺有用。5.4 与ViewBinding的配合现在大部分项目都用ViewBinding了。AVLoadingIndicatorView作为一个自定义ViewViewBinding会自动为它生成类型正确的引用不需要手动findViewById。但要注意ViewBinding生成的字段类型是AVLoadingIndicatorView你可以直接调用show()和hide()不需要强转。如果你用的是DataBinding还可以在XML里通过表达式控制可见性但show()和hide()这种命令式的方法调用就不太适合放在绑定表达式里了。我的建议是DataBinding负责布局和数据展示动画控制还是放在代码里。6. 这个库现在还值得用吗6.1 与同类方案的对比市面上做加载动画的方案不止这一个。我简单对比一下我接触过的几种方案优点缺点AVLoadingIndicatorView效果多、接入简单、源码可读更新不活跃、无Compose版本Lottie动画效果丰富、设计师可参与体积大、需要JSON资源ProgressBar自定义无额外依赖、完全可控开发成本高、效果有限Compose自带指示器与Compose生态无缝集成仅适用于Compose项目如果你的项目是传统View体系且只需要常见的加载效果AVLoadingIndicatorView依然是最省事的选择。如果你需要更复杂的动画或者项目已经全面转向Compose那可以考虑其他方案。6.2 在Android 14上的实测表现我在Android 14的Pixel设备上跑了这个库的所有内置动画没有遇到崩溃或渲染异常。唯一需要注意的是Android 12之后引入了SplashScreenAPI如果你的启动页用了AVLoadingIndicatorView要确保它和系统的启动画面不冲突。我的做法是启动页直接用静态图进入主界面后再用加载动画。6.3 长期维护的考量这个库的GitHub仓库最近几年没有大的更新但Issues里也没有严重的未解决问题。它的代码量小、依赖少即使不更新在可预见的未来也不会突然不能用。如果你担心维护问题可以把源码拷进项目自己维护反正代码量不大改起来也方便。我个人的做法是在项目的README里记录一下这个库的引入方式和版本号方便后续接手的人知道这是什么。如果哪天真的需要替换因为用法简单迁移成本也不高。6.4 一个替代思路自己封装一层如果你不想直接依赖这个库但又喜欢它的效果可以把它作为一个参考实现自己封装一个类似的组件。核心思路就是前面说的一个View加一个ValueAnimator加一个绘制逻辑。自己写的好处是完全可控可以根据项目需求裁剪功能。坏处是要花时间调试各种边界情况比如测量、复用、生命周期处理。我的建议是如果项目时间紧直接用这个库如果有充裕时间且想深入学习动画机制可以自己照着写一遍。写完之后你对Android属性动画的理解会上一个台阶。最后分享一个我在实际使用中的小技巧如果你觉得某个动画的速度太快或太慢可以修改AVLoadingIndicatorView源码里ValueAnimator的setDuration值。默认是1000毫秒改成1500会让动画更舒缓改成800会更急促。这个值没有绝对的好坏根据你的App调性来调就行。
企业数字化 ERP 产品动态
相关推荐
WarcraftHelper内存操控底层揭秘:Detours内联Hook、字节补丁与NOP指令的完整实践 WarcraftHelper内存操控底层揭秘:Detours内联Hook、字节补丁与NOP指令的完整实践 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper
Warcraft… · 2026/9/25 2:06:29
STM32项目完整开源:代码、原理图与仿真三件套齐全 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 2:06:29
JFlash实战:从零开始用J-Link烧录STM32固件完整教程 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 2:06:23
2026年选网站建设公司全指南:从需求定义到合同避坑的实操策略 1. 选网站建设公司之前,先定义你的官网是干什么用的很多老板来找我咨询时,开口第一句话就是:“想做个官网,预算3万,帮我推荐一家靠谱的公司。”这话听着爽快,其实是最难接的需求,因为“官网”两… · 2026/9/25 3:05:55
Learn-Algorithms 链表排序专题:单链表归并排序、相交检测与环入口定位 教程 【免费下载链接】Learn-Algorithms 算法学习笔记 项目地址: https://gitcode.com/gh_mirrors/le/Learn-Algorithms 点击查看 免费下载 导读:本文围绕仓库 9 Algorithms Job Interview/2.1 链表-排序.md 中记录的微软面试题展开——给定单链表头指针… · 2026/9/25 3:05:55
SAP安全审计日志实战:SM19配置与SM20查询全指南 做 SAP 运维这些年,被问得最多的问题之一就是:“帮我查一下某某用户昨天登录了系统没有?他到底跑了哪些事务码?”每次接到这类需求,我第一反应就是打开 SM19 和 SM20 这对组合。很多顾问平时不太碰这两个事务码&#x… · 2026/9/25 3:05:55
使用宝塔面板部署 MediaGo:Docker 应用商店一键安装与服务端口详解 音视频桌面应用后端 【免费下载链接】mediago 跨平台视频提取工具:支持流媒体下载、视频下载、m3u8 下载及 B站视频下载,提供 Windows 和 Mac 桌面客户端。Cross-platform video extraction tool: Supports streaming download, video download, m3u8 do… · 2026/9/25 3:05:49
Havoc Demon Agent 源码结构深度解析:C2 植入体的模块化设计与实现 网络安全 【免费下载链接】Havoc The Havoc Framework 项目地址: https://gitcode.com/gh_mirrors/ha/Havoc 点击查看 免费下载 Demon 是 Havoc 框架(Havoc Framework)中的 Windows 端植入体(Agent),其源码… · 2026/9/25 3:05:49
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37