全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2022-09-30_Compose 动画艺术探索之属性动画

您的位置:首页 >> 新闻 >> 行业资讯

Compose 动画艺术探索之属性动画 本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! 本篇文章是此专栏的第三篇文章,如果想阅读前两篇文章的话请点击下方链接: Compose 动画艺术探索之瞅下 Compose 的动画Compose 动画艺术探索之可见性动画Compose的属性动画属性动画是通过不断地修改值来实现的,而初始值和结束值之间的过渡动画就需要来计算了。在Compose中为我们提供了一整套 api 来实现属性动画,具体有哪些呢?让我们一起来看下吧! image.png官方为我们提供了上图这十种方法,我们可以根据实际项目中的需求进行挑选使用。 在第一篇文章中也提到了Compose的属性动画,但只是简单使用了下,告诉大家Compose有这个东西,今天咱们来具体看下! 先来看下animateColorAsState的代码吧: @Composable funanimateColorAsState( targetValue:Color, animationSpec:AnimationSpecColor=colorDefaultSpring, label:String="ColorAnimation", finishedListener:((Color)-Unit)?=null ):StateColor{ valconverter=remember(targetValue.colorSpace){ (Color.VectorConverter)(targetValue.colorSpace) } returnanimateValueAsState( targetValue,converter,animationSpec,label=label,finishedListener=finishedListener ) } 可以看到一共接收四个参数,来分别看下代表什么吧: targetValue:顾名思义,目标值,这里对应的就是想要转换成的颜色animationSpec:动画规格,动画随着时间改变值的一种规格吧,上一篇文章中也提到了,但由于上一篇文章主要内容并不是这个,也就没有讲,本篇文章会详细说明label:标签,以区别于其他动画finishedListener:在动画完成时会进行回调参数并不算多,而且有三个是可选参数,也就只有targetValue必须要进行设置。方法体内只通过Color.colorSpace强转构建了一个TwoWayConverter。 前面说过,大多数Compose动画 API 支持将Float、Color、Dp以及其他基本数据类型作为 开箱即用的动画值,但有时我们需要为其他数据类型(比如自定义类型)添加动画效果。在动画播放期间,任何动画值都表示为AnimationVector。使用相应的TwoWayConverter即可将值转换为AnimationVector,反之亦然,这样一来,核心动画系统就可以统一对其进行处理了。由于颜色有 argb,所以构建的时候使用的是AnimationVector4D,来看下吧: valColor.Companion.VectorConverter: (colorSpace:ColorSpace)-TwoWayConverterColor,AnimationVector4D get()=ColorToVector 如果按照我之前的习惯肯定要接着看animateValueAsState方法内部的代码了,但今天等会再看!再来看看animateDpAsState的代码吧! @Composable funanimateDpAsState( targetValue:Dp, animationSpec:AnimationSpec=dpDefaultSpring, label:String="DpAnimation", finishedListener:((Dp)-Unit)?=null ):State{ returnanimateValueAsState( targetValue, Dp.VectorConverter, animationSpec, label=label, finishedListener=finishedListener ) } 发现了点什么没有,参数基本一摸一样,别着急,咱们再看看别的! @Composable funanimateIntAsState( targetValue:Int, animationSpec:AnimationSpecInt=intDefaultSpring, label:String="IntAnimation", finishedListener:((Int)-Unit)?=null ) @Composable funanimateSizeAsState( targetValue:Size, animationSpec:AnimationSpecSize=sizeDefaultSpring, label:String="SizeAnimation", finishedListener:((Size)-Unit)?=null ) @Composable funanimateRectAsState( targetValue:Rect, animationSpec:AnimationSpecRect=rectDefaultSpring, label:String="RectAnimation", finishedListener:((Rect)-Unit)?=null ) 不能说是大同小异,只能说是一摸一样!既然一摸一样的话咱们就以文章开头的animateColorAsState来看吧! 上面的说法其实是不对的,并不是有十种,而是九种,因为九种都调用了animateValueAsState,其实也可以说有无数种,因为可以自定义。。。。 参数下面先来看下animateValueAsState的方法体吧: @Composable funT,V:AnimationVectoranimateValueAsState( targetValue:T, typeConverter:TwoWayConverterT,V, animationSpec:AnimationSpec=remember{spring()}, visibilityThreshold:T?=null, label:String="ValueAnimation", finishedListener:((T)-Unit)?=null ):State 来看看接收的参数吧,可以发现有两个参数没有见过: typeConverter:类型转换器,将需要的类型转换为AnimationVectorvisibilityThreshold:一个可选的阈值,用于定义何时动画值可以被认为足够接近targetValue以结束动画OK,剩下的参数在上面都介绍过,就不重复进行介绍了。 方法体由于animateValueAsState方法有点长,所以分开来看吧,接下来看下animateValueAsState方法中的前半部分: valanimatable=remember{Animatable(targetValue,typeConverter,visibilityThreshold,label)} vallistenerbyrememberUpdatedState(finishedListener) valanimSpec:AnimationSpecbyrememberUpdatedState( animationSpec.run{ if(visibilityThreshold!=nullthisisSpringSpec&& this.visibilityThreshold!=visibilityThreshold ){ spring(dampingRatio,stiffness,visibilityThreshold) }else{ this } } ) valchannel=remember{Channel(Channel.CONFLATED)} SideEffect{ channel.trySend(targetValue) } LaunchedEffect(channel){ for(targetinchannel){ valnewTarget=channel.tryReceive().getOrNull()?:target launch{ if(newTarget!=animatable.targetValue){ animatable.animateTo(newTarget,animSpec) listener?.invoke(animatable.value) } } } } 可以看到首先构建了一个Animatable,然后记录了完成回调,又记录了AnimationSpec,之后有个判断,如果visibilityThreshold不为空并且AnimationSpec为SpringSpec的时候为新构建的一个AnimationSpec,反之则还是传进来的AnimationSpec。 那Animatable是个啥呢?它是一个值容器,它可以在通过animateTo更改值时为值添加动画效果,它可确保一致的连续性和互斥性,这意味着值变化始终是连续的,并且会取消任何正在播放的动画。Animatable的许多功能(包括animateTo)以挂起函数的形式提供,所以需要封装在适当的协程作用域内,所以下面使用了LaunchedEffect来包裹执行animateTo方法,最后调用了动画完成的回调。 由于Animatable类中代码比较多,先来看下类的初始化及构造方法吧! classAnimatableT,V:AnimationVector( initialValue:T, valtypeConverter:TwoWayConverterT,V, privatevalvisibilityThreshold:T?=null, vallabel:String="Animatable" ) 可以看到这里使用到的参数在animateValueAsState中都有,就不一一介绍了,挑着重点来,来看看上面使用到的animateTo吧: suspendfunanimateTo( targetValue:T, animationSpec:AnimationSpec=defaultSpringSpec, initialVelocity:T=velocity, block:(AnimatableT,V.()-Unit)?=null ):AnimationResultT,V{ valanim=TargetBasedAnimation( animationSpec=animationSpec, initialValue=value, targetValue=targetValue, typeConverter=typeConverter, initialVelocity=initialVelocity ) returnrunAnimation(anim,initialVelocity,block) } 可以看到animateTo使用传进来的参数构建了一个TargetBasedAnimation,这是一个方便的动画包装类,适用于所有基于目标的动画,即具有预定义结束值的动画。然后返回调用了runAnimation,返回值为AnimationResult,来看下吧: classAnimationResultT,V:AnimationVector( valendState:AnimationStateT,V, valendReason:AnimationEndReason ){ overridefuntoString():String="AnimationResult(endReason=$endReason,endState=$endState)" } AnimationResult在动画结尾包含关于动画的信息,endState捕获动画在最后一帧的值evelocityframe time等。它可以用于启动另一个动画以从先前中断的动画继续速度。endReason描述动画结束的原因。 下面看下runAnimation吧: privatesuspendfunrunAnimation( animation:AnimationT,V, initialVelocity:T, block:(AnimatableT,V.()-Unit)? ):AnimationResultT,V{ valstartTime=internalState.lastFrameTimeNanos returnmutatorMutex.mutate{ try{ ...... endState.animate( animation, startTime ){ updateState(internalState) ...... } valendReason=if(clampingNeeded)BoundReachedelseFinished endAnimation() AnimationResult(endState,endReason) }catch(e:CancellationException){ //Cleanupinternalstatesfirst,thenthrow. endAnimation() throwe } } } 这里需要注意:所有不同类型的动画代码路径最终都会汇聚到这个方法中。 好了,基本快见到阳光了! 天亮了上面方法中有一行:endState.animate,这个是关键,来看下! internalsuspendfunT,V:AnimationVectorAnimationStateT,V.animate( animation:AnimationT,V, startTimeNanos:Long=AnimationConstants.UnspecifiedTime, block:AnimationScopeT,V.()-Unit={} ){ valinitialValue=animation.getValueFromNanos(0) valinitialVelocityVector=animation.getVelocityVectorFromNanos(0) varlateInitScope:AnimationScopeT,V?=null try{ if(startTimeNanos==AnimationConstants.UnspecifiedTime){ valdurationScale=coroutineContext.durationScale animation.callWithFrameNanos{ lateInitScope=AnimationScope(...).apply{ //第一帧 doAnimationFrameWithScale(it,durationScale,animation,this@animate,block) } } }else{ lateInitScope=AnimationScope(...).apply{ //第一帧 doAnimationFrameWithScale() } } //后续帧 while(lateInitScope!!.isRunning){ valdurationScale=coroutineContext.durationScale animation.callWithFrameNanos{ lateInitScope!!.doAnimationFrameWithScale(it,durationScale,animation,this,block) } } //动画结束 }catch(e:CancellationException){ lateInitScope?.isRunning=false if(lateInitScope?.lastFrameTimeNanos==lastFrameTimeNanos){ isRunning=false } throwe } } 嗯,柳暗花明!这个动画函数从头到尾运行给定animation中定义的动画。在动画过程中,AnimationState将被更新为最新的值,速度,帧时间等。 到这里animateColorAsState大概过了一遍,但也只是简单走了一遍流程,并没有深究里面的细节,比如Animatable类中都没看,runAnimation方法也只是看了主要的代码等等。 结尾本篇文章先写到这里吧,属性动画其实都差不多,区别只是泛型不同以及一些特定实现,大家如果有需要可以一个一个去看看。 本文所有源码基于Compose 1.3.0-beta02。 本文至此结束,有用的地方大家可以参考,当然如果能帮助到大家,哪怕是一点也足够了。就这样。 阅读原文

上一篇:2021-09-07_AE 操作知多少? | NewTalk直播分享会 下一篇:2024-10-03_「转」消费洞察:低价时代的5条出路

TAG标签:

18
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价