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

分析Jetpack Compose动画内部是如何实现的

前言

Compose的动画Api用起来很简单,效果看起来很神奇,那么它内部到底是如何运转的呢?

使用动画的代码示例:

var isOffset by remember { mutableStateOf(false) }
val offsetAnimation by animateDpAsState(targetValue = if (isOffset) 100.dp else 0.dp)
Button(
    onClick = { isOffset = !isOffset },
    modifier = Modifier.offset(0.dp, offsetAnimation)
) {
    Text(text = "点我进行位移")
}

看到有一个Boolean类型的isOffset状态,控制着offsetAnimation动画,然后动画又控制着Button的offset,最终实现了动画效果

正文

我们主要就看一下animateDpAsState(animate*AsState)做了什么

跟一下animateDpAsState最后会走进animateValueAsState方法中:

 方法内部创建了一个Animatable动画类

然后我们跟着上图的箭头看,在targetValue发生变化后,会通过channel来发送targetValue值的变化,然后launch启动一个协程,并在其中调用了animatable的animateTo方法

接着我们就看看Animatable类是如何做动画的:

主要就是内部持有一个AnimationState类型(State)的internalState 变量,然后我们接着上面的代码跟一下animateTo方法:

 

 没什么好说的,包了一下targetValue,接着就调用了runAnimation方法,接着往下跟(截不下分成两张图):

 主要逻辑就是endState.animate()
endState就是copy的Animatable中的AnimationState对象internalState,也就是动画的初始值

然后animation就是在animateTo方法中包装了动画目标值的对象

接着跟animate方法:

这个方法主要分为两部分,第一部分就是构建了一个AnimationScope,一个数据结构,用来存放动画需要的一些信息

 第二个部分就是真正动画计算和生效的地方,doAnimationFrame

首先会在第一次创建AnimationScope的时候执行一次(或再一下帧执行一次,通过callWithFrameNanos方法)

然后会判断如果动画还未执行完毕,就一直循环(while),一帧一帧执行doAnimationFrame计算动画的值

 doAnimationFrame方法代码:

 其中通过时间等参数计算当前动画应该设置的值,包括lastFrameTimeNanos和value等属性,然后再最后调用updateState方法去将AnimationScope的值更新到AnimationState中

updatState方法代码:

 这个state对象其实也就是animateDpAsState中的Animatable动画类中的AnimationState对象internalState

所以上面其实就是Compose中动画的简化流程

总结

 由于AnimationState是一个State,在Compose中使用会自动监听其变化,只要其value变化了,就会导致相应位置重组,然后Composable就会使用新的值来展示不同的效果,比如最开始的示例代码:

var isOffset by remember { mutableStateOf(false) }
val offsetAnimation by animateDpAsState(targetValue = if (isOffset) 100.dp else 0.dp)
Button(
    onClick = { isOffset = !isOffset },
    modifier = Modifier.offset(0.dp, offsetAnimation)
) {
    Text(text = "点我进行位移")
}

 在修改了isOffset后,animateDpAsState中的值就会因为动画的计算和修改内部state的value,导致Button的offset函数一直被重新调用,使Button不停的向下移动

其实Compose中的动画如果不考虑那么多东西的话,可以简化为如下代码:

    /**
     * creator: lt
     * effect : 自定义的动画播放器,逻辑更简单
     * warning:
     * [initialValueWithState]动画要改变的状态,起始动画值为其value值
     * [targetValue]要通过动画转化到的目标值
     * [duration]动画的持续时间
     */
    @OptIn(ExperimentalComposeApi::class)
    suspend fun animateWithFloat(
        initialValueWithState: MutableState<Float>,
        targetValue: Float,
        duration: Int = AnimationConstants.DefaultDurationMillis,
    ) {
        //动画起始值,目标差值
        val startValue = initialValueWithState.value
        val valueToBeTransformed = targetValue - startValue
        //动画起始时间,持续时间
        val startTime = System.nanoTime()
        val duration = duration * 1000000L
        //通过循环在下一帧计算动画的值
        val frameClock = coroutineContext.monotonicFrameClock
        while (System.nanoTime() <= startTime + duration) {
            frameClock.withFrameNanos {
                //计算动画的值,并设置值给状态
                val progress = minOf(it - startTime, duration).toFloat() / duration
                val increase = progress * valueToBeTransformed
                initialValueWithState.value = startValue + increase
            }
        }
    }

使用方式如下,效果跟示例差不多:

 ps:不建议线上项目用这个api,还是用系统的比较好,如果想使用也可以参考(欢迎star): ComposeViews/MAnimator.kt at main · ltttttttttttt/ComposeViews (github.com)

如果有分析的不对的地方请大佬们指出

end

相关文章:

  • ZTG-事务码使用日志报表
  • 不服不行!一篇文章带你透彻了解华为认证,考它真的值吗?
  • AXI死锁
  • python笔记III--流程控制语句
  • Java如何提高代码效率
  • docker修改挂载目录
  • JVM类加载器
  • 路由引入基本概念
  • centos安装Nginx
  • 学习笔记18--自动驾驶智能化指标评测体系(上)
  • 《Mycat分布式数据库架构》之配置详解
  • springboot bean找不到问题
  • [河北银行 2022 CTF]
  • 通过数据库建表实战来理解数据库知识
  • 200A FS3L200R10W3S7FB11 EasyPACK 950V IGBT模块
  • 《微软的软件测试之道》成书始末、出版宣告、补充致谢名单及相关信息
  • Android开发 - 掌握ConstraintLayout(四)创建基本约束
  • CSS盒模型深入
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • Flex布局到底解决了什么问题
  • JS变量作用域
  • PAT A1017 优先队列
  • python大佬养成计划----difflib模块
  • spring security oauth2 password授权模式
  • Vue实战(四)登录/注册页的实现
  • 技术攻略】php设计模式(一):简介及创建型模式
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 人脸识别最新开发经验demo
  • 深入浏览器事件循环的本质
  • 以太坊客户端Geth命令参数详解
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • !!Dom4j 学习笔记
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • $emit传递多个参数_PPC和MIPS指令集下二进制代码中函数参数个数的识别方法
  • (1)虚拟机的安装与使用,linux系统安装
  • (10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】
  • (pojstep1.3.1)1017(构造法模拟)
  • (含react-draggable库以及相关BUG如何解决)固定在左上方某盒子内(如按钮)添加可拖动功能,使用react hook语法实现
  • (三) diretfbrc详解
  • (实战篇)如何缓存数据
  • (转载)CentOS查看系统信息|CentOS查看命令
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • .NET C# 使用 SetWindowsHookEx 监听鼠标或键盘消息以及此方法的坑
  • .NET CORE使用Redis分布式锁续命(续期)问题
  • .net framework 4.0中如何 输出 form 的name属性。
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • .NET与 java通用的3DES加密解密方法
  • .sys文件乱码_python vscode输出乱码
  • @property python知乎_Python3基础之:property
  • [22]. 括号生成
  • [30期] 我的学习方法
  • [C++从入门到精通] 14.虚函数、纯虚函数和虚析构(virtual)
  • [HTML]Web前端开发技术30(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页
  • [LeetCode刷题笔记]1 - 两数之和(哈希表)