当前位置: 首页 > news >正文

Android动画小补充

Android动画小补充

文章目录

  • Android动画小补充
    • 背景:
    • 一、动画实现全流程
      • 1. 初始化阶段(ProtectBurnView2 主导)
      • 2. 动画启动阶段(Anim 基类 + EnterAnimLayout 协作)
      • 3. 动画运行阶段(EnterAnimLayout + AnimLadder 核心)
      • 4. 交互停止阶段(事件处理 + 跨组件通信)
      • 5. 资源清理阶段(生命周期管理)
    • 二、核心技术点解析
      • 1. 自定义 View 绘制机制
      • 2. 动画状态管理
      • 3. 事件分发与拦截
      • 4. 跨组件通信
      • 5. 性能优化
      • 6. 内存管理
    • 三、关键机制详解
      • 1. 动画下方内容可见性保障
      • 2. `invalidate()` 绘制循环原理
    • 四、QA
    • invalidate小总结
      • 一、`invalidate()` 的核心作用
      • 二、绘制循环的触发与执行流程
        • 1. 初始触发:启动动画时首次调用 `invalidate()`
        • 2. 系统处理:从 `invalidate()` 到 `dispatchDraw()` 的调用链
        • 3. 循环核心:在 `dispatchDraw()` 中再次调用 `invalidate()`
        • 4. 动画帧更新:每一次循环都是一帧
      • 三、循环的停止条件
      • 四、与代码结合的关键细节
      • 总结

背景:

之前做了一个润屏效果的小动画,今天总结一下里面的知识点,效果是随机生成水波纹扩散。

一、动画实现全流程

整个动画流程基于 ProtectBurnView2EnterAnimLayoutAnimLadderAnim 四个类协作,分为五个阶段:

1. 初始化阶段(ProtectBurnView2 主导)

  • 创建子视图 EnterAnimLayout 并设置透明背景、充满父容器布局参数;
  • 注册交互监听器(OnUserInteractionListener)实现跨组件通信;
  • 预加载图片资源(预留扩展);
  • 通过 Handler 延迟 200ms 发送启动消息,避免布局未完成导致的尺寸异常。

2. 动画启动阶段(Anim 基类 + EnterAnimLayout 协作)

  • ProtectBurnView2 接收启动消息后,创建 AnimLadder(具体动画实现)并关联到 EnterAnimLayout
  • 记录动画开始时间,标记 mIsAnimaionRun = true 开启动画状态;
  • 调用 invalidate() 触发 EnterAnimLayoutdispatchDraw 启动绘制循环。

3. 动画运行阶段(EnterAnimLayout + AnimLadder 核心)

  • EnterAnimLayout 重写 dispatchDraw 作为调度中心:若动画运行,则调用 AnimLadder.handleCanvas 绘制当前帧,再通过 invalidate() 触发下一次重绘,形成循环;
  • AnimLadder 实现具体绘制逻辑:动态生成波纹圆、清理已完成圆、计算每帧半径 / 透明度,绘制带模糊效果的波纹扩散效果。

4. 交互停止阶段(事件处理 + 跨组件通信)

  • 用户交互(触摸 / 悬停)被 ProtectBurnView2EnterAnimLayout 捕获;
  • EnterAnimLayout 调用 handleUserInteraction 停止动画(mIsAnimaionRun = false),并通过监听器通知 ProtectBurnView2
  • ProtectBurnView2 清理资源并移除整个视图。

5. 资源清理阶段(生命周期管理)

  • 视图从窗口移除时(onDetachedFromWindow),清理 AnimLadder 圆形列表、回收画笔和 Bitmap 资源,避免内存泄漏。

二、核心技术点解析

1. 自定义 View 绘制机制

  • dispatchDrawonDraw 的区别EnterAnimLayout 作为 ViewGroup 重写 dispatchDraw(而非 onDraw),因 dispatchDraw 负责子视图绘制调度,可在子视图绘制前处理动画画布,确保动画覆盖子视图;onDraw 用于 View 自身绘制,ViewGroup 默认不触发(除非设背景)。
  • 绘制循环触发:通过 invalidate() 在主线程触发重绘,dispatchDraw 中通过 mIsAnimaionRun 控制循环启停。

2. 动画状态管理

  • 核心标志位:mIsAnimaionRunEnterAnimLayout 中)控制动画启动 / 停止;
  • 模板方法模式:Anim 基类封装通用逻辑(启动 / 停止),子类(如 AnimLadder)实现具体绘制(handleCanvas),保证接口一致性与个性化扩展。

3. 事件分发与拦截

  • 优先级原则:dispatchTouchEvent(分发)> onInterceptTouchEvent(拦截)> onTouchEvent(处理);
  • 交互保障:重写事件方法优先捕获用户操作,onInterceptTouchEvent 返回 true 阻止事件传递到子视图,确保动画及时停止。

4. 跨组件通信

  • 接口回调:通过 OnUserInteractionListener 实现 EnterAnimLayoutProtectBurnView2 的通知,解耦父子视图;
  • Handler 作用:主线程内延迟任务(如延迟启动)和线程间通信(确保 UI 操作在主线程)。

5. 性能优化

  • 资源复用:复用画笔避免频繁创建对象;
  • 过度绘制控制:限制活跃圆形数量(MAX_ACTIVE_CIRCLES = 10);
  • 帧率平衡:TARGET_FRAME_TIME = 33ms(约 30 帧 / 秒);
  • 渲染兼容:禁用硬件加速(LAYER_TYPE_SOFTWARE)避免模糊效果异常。

6. 内存管理

  • 生命周期绑定:onDetachedFromWindow 中回收 Bitmaprecycle())、清理画笔和圆形列表;
  • 避免泄漏:Handler 及时移除回调,AnimLadder.cleanup() 清空列表释放引用。

三、关键机制详解

1. 动画下方内容可见性保障

  • 透明容器:ProtectBurnView2EnterAnimLayout 均设置透明背景(Color.TRANSPARENT),避免遮挡下层内容;
  • 半透明元素:波纹圆通过动态计算透明度(alpha)绘制,确保仅视觉叠加不遮挡下层交互。

2. invalidate() 绘制循环原理

  • 触发逻辑:invalidate() 标记视图为 “脏区域”,等待下一次 VSync 信号触发重绘;
  • 循环形成:EnterAnimLayout.dispatchDraw 中,动画运行时(mIsAnimaionRun = true)绘制当前帧后再次调用 invalidate(),形成 “绘制→触发下一次绘制” 的循环;
  • 帧更新:每帧通过当前时间与开始时间计算进度,更新波纹圆半径、透明度,实现连续动画效果;
  • 停止条件:mIsAnimaionRun = false 或视图销毁时,停止调用 invalidate() 终止循环。

四、QA

问题核心答案要点
为什么 EnterAnimLayout 重写 dispatchDrawViewGroupdispatchDraw 负责子视图绘制调度,可在子视图绘制前处理动画,onDraw 默认不触发。
动画循环如何实现?通过 dispatchDrawinvalidate() 反复触发重绘,mIsAnimaionRun 控制循环启停;invalidate() 用于主线程更高效。
Anim 抽象类的设计目的?模板方法模式:封装通用逻辑(启动 / 停止),抽象 handleCanvas 强制子类实现具体绘制,保证一致性与扩展性。
如何避免内存泄漏?onDetachedFromWindow 回收 Bitmap 和画笔;AnimLadder.cleanup() 清空列表;Handler 及时移除回调。
为什么限制最大活跃圆形数量?避免过度绘制(Overdraw),减少 GPU 负担,平衡视觉效果与性能(经验值 10 个)。
如何扩展新动画类型?新建类继承 Anim 实现 handleCanvas,替换 AnimLadder 实例即可,符合开闭原则。
延迟 200ms 启动动画的原因?确保视图完成测量布局,获取正确宽高,避免波纹圆位置计算错误。
为什么禁用硬件加速?部分设备对 BlurMaskFilter(模糊效果)支持不完善,软件渲染可避免闪烁或异常。

invalidate小总结

在 Android 视图系统中,invalidate() 是触发视图重绘的核心方法,而 “绘制循环” 则是通过反复调用 invalidate() 持续触发重绘,从而实现动画效果的机制。结合你提供的代码(尤其是 EnterAnimLayoutAnimLadder),可以从 “触发→处理→循环” 三个层面详细解释:

一、invalidate() 的核心作用

invalidate() 是 View 类的方法,其本质是向系统发送 “视图需要重绘” 的请求。当调用 invalidate() 后,系统会将该视图标记为 “脏区域”(需要更新的区域),并在合适的时机(通常是下一次屏幕刷新信号,即 VSync 信号)触发视图的绘制流程。

  • 核心目标:更新视图的视觉表现(如位置、颜色、形状变化)。
  • 注意invalidate() 必须在主线程调用(子线程需用 postInvalidate()),因为 UI 操作只能在主线程执行。

二、绘制循环的触发与执行流程

在你的动画代码中,绘制循环的核心逻辑在 EnterAnimLayout.dispatchDraw() 中实现,完整流程如下:

1. 初始触发:启动动画时首次调用 invalidate()

当动画启动(Anim.startAnimation())时,会调用 view.invalidate(),这是循环的起点:

// Anim.startAnimation() 中
view.setmIsAnimaionRun(true);
view.invalidate(); // 首次触发重绘,启动循环
2. 系统处理:从 invalidate()dispatchDraw() 的调用链

invalidate() 触发后,系统会经历以下步骤(简化版):

  • 标记视图为 “脏区域”,并合并父视图的脏区域(避免重复绘制)。
  • 等待下一次 VSync 信号(由系统的显示子系统发送,通常 60 次 / 秒,即 16ms 一次)。
  • 触发视图树的绘制流程:从根视图开始,依次调用 measure()(测量)→ layout()(布局)→ draw()(绘制)。
  • 对于 EnterAnimLayout(ViewGroup),最终会调用 dispatchDraw() 方法(负责子视图绘制的调度)。
3. 循环核心:在 dispatchDraw() 中再次调用 invalidate()

EnterAnimLayout 重写了 dispatchDraw(),在动画运行时(mIsAnimaionRun = true),会在绘制完成后再次调用 invalidate(),形成循环:

// EnterAnimLayout.dispatchDraw() 中
if (mIsAnimaionRun && anim != null) {// 1. 绘制当前帧动画(波纹圆)anim.handleCanvas(canvas, rate); super.dispatchDraw(canvas); // 绘制子视图// 2. 若动画仍在运行,调用invalidate()触发下一次重绘if (mIsAnimaionRun) {invalidate(); // 关键:启动下一次循环}
}
  • 为什么能形成循环:每次 dispatchDraw() 执行完当前帧绘制后,若动画未停止(mIsAnimaionRun = true),就通过 invalidate() 触发下一次 dispatchDraw(),如此反复,实现 “绘制→触发下一次绘制” 的循环。
4. 动画帧更新:每一次循环都是一帧

在循环中,AnimLadder.handleCanvas() 会根据当前时间计算动画进度(如波纹圆的半径、透明度),并绘制新的帧:

// AnimLadder.handleCanvas() 中
long currentTime = System.currentTimeMillis();
// 计算每个波纹圆的当前进度(半径、透明度)
for (RippleCircle circle : rippleCircles) {drawExpandingCircle(canvas, circle, currentTime); // 绘制当前帧的波纹圆
}

每一次循环(dispatchDraw() 调用)对应动画的一帧,通过不断更新绘制内容(如波纹圆扩大),视觉上就形成了连续的动画。

三、循环的停止条件

绘制循环不会无限执行,当满足以下条件时会终止:

  1. 动画停止标记:设置 mIsAnimaionRun = false(如用户交互时),此时 dispatchDraw() 中不再调用 invalidate(),循环终止:

    // EnterAnimLayout.handleUserInteraction() 中
    mIsAnimaionRun = false; // 停止循环
    
  2. 视图销毁:当视图从窗口移除(onDetachedFromWindow()),系统会停止处理该视图的 invalidate() 请求,循环自然终止。

四、与代码结合的关键细节

  1. 帧率控制:你的代码中 AnimLadder 通过 TARGET_FRAME_TIME = 33ms 限制循环频率(约 30 帧 / 秒),避免过度绘制:

    // AnimLadder.handleCanvas() 中
    if (currentTime - lastFrameTime < TARGET_FRAME_TIME) {return; // 未到目标帧间隔,不更新绘制
    }
    
  2. 动画连贯性:通过 startTime 和当前时间计算进度,确保每次循环的动画状态连续(如波纹圆的扩散不会因帧率波动而跳跃)。

总结

invalidate() 绘制循环的核心是:通过反复调用 invalidate() 触发 dispatchDraw() 重绘,每一次重绘更新动画帧内容,最终形成视觉上的连续动画。在你的代码中,这一机制由 EnterAnimLayout 主导调度,AnimLadder 负责具体帧绘制,通过 mIsAnimaionRun 控制循环启停,既实现了波纹扩散的动画效果,又保证了性能可控。

http://www.dtcms.com/a/341549.html

相关文章:

  • 【Obsidian插件】HiNote
  • 爬虫项目实践之淘宝商品详情数据采集​||电商API接口
  • 结构化 OCR 技术:破解各类检测报告信息提取难题
  • 5.Kotlin作用于函数let、run、with、apply、also
  • SpringCloud微服务架构入门指南
  • Day12--滑动窗口与双指针--2762. 不间断子数组,LCP 68. 美观的花束,2743. 计算没有重复字符的子字符串数量
  • day075-MySQL数据库服务安装部署与基础服务管理命令
  • Unity 开源分享一个轻量路点编辑器插件 常用于对象寻路
  • 在IDEA中DEBUG调试时查看MyBatis-Plus动态生成的SQL语句
  • 数据结构:AVL 树
  • RHCA05-文件系统调优
  • Spark学习
  • 游戏本不插电源适配器不卡设置教程
  • 技术半衰期悖论:AI时代“不可替代领域“的深耕地图
  • 30.Linux cobbler自动化部署
  • 生物信息学深度学习模型比较与学习框架
  • chrome插件开发(一)
  • 23TaskExecutor初始化
  • Windows 命令行:dir 命令
  • MyBatis 动态查询语句详解:让 SQL 更灵活可控
  • 前端调用阿里云接口语音合成演示
  • 20人团队文件共享选哪款?群晖DS925+ 和 DS1525+深度对比
  • 反射基础知识初入(up晚上回家再补完剩下的)
  • Anomalib:在Linux服务器上安装使用Anomalib 2.1.0
  • 生意参谋-市场竞争分析-提升商品成长效率
  • PostgreSQL 中的金钱计算处理
  • C语言第十章内存函数
  • 《SQLAlchemy 2 In Practice》读后感
  • win与ubuntu双系统安装笔记
  • 小波函数多尺度变换的 Curvelet 变换