Compose 动画艺术探索之可见性动画
本篇文章是此专栏的第二篇文章,上一篇文章简单写了下Compose的动画,让大家先看了下Compose开箱即用的动画效果,效果还是挺好的,感兴趣的可以去看下:Compose 动画艺术探索之瞅下 Compose 的动画
本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
从可见性动画看起可见性动画在上一篇文章中介绍过,不过只是简单使用,没看过上一篇文章的也不用担心,给大家看下可见性动画的实际效果。
Visible.gif实现代码也很简单,来回顾下:
valvisible=remember{mutableStateOf(true)}
AnimatedVisibility(visible=visible.value,){
Text(text="天青色等烟雨,而我在等你,炊烟袅袅升起,隔江千万里")
}
上一篇文章主要介绍了Compose动画的便携之处,例如上面代码,确实非常简单就能实现之前原生安卓中比较难实现的动画效果,今天咱们就来稍微深入一点看看,从小节标题也能知道,就从可见性动画来看!
怎么看呢?直接点进去源码来看!先来看看AnimatedVisibility的函数定义吧!
@Composable
funAnimatedVisibility(
visible:Boolean,
modifier:Modifier=Modifier,
enter:EnterTransition=fadeIn()+expandIn(),
exit:ExitTransition=shrinkOut()+fadeOut(),
label:String="AnimatedVisibility",
content:@Composable()AnimatedVisibilityScope.()-Unit
){
valtransition=updateTransition(visible,label)
AnimatedEnterExitImpl(transition,{it},modifier,enter,exit,content)
}
其实可组合项AnimatedVisibility不是只有这一个,目前Compose 1.3.0-beta02版本中有六个,这个咱们待会再说,先看这一个,也是第一个,可以看到函数中一共可以接收六个参数,下面先来看看这六个参数分别有什么作用吧:
visible:定义内容是否应该可见,true 为可见,false 为不可见modifier:修饰符,这个就不多说了,在Compose中Modifier简直随处可见enter:内容出现时的动画,可以看到这个参数有默认值,默认值为fadeIn() + expandIn(),大致意思为淡入并扩展开exit:内容由可见变为不可见时的动画,同样的,这个参数也有默认值,默认值为shrinkOut() + fadeOut(),大致意思为缩小并淡出消失label:字面意思理解为标签,默认值为AnimatedVisibility,可以自定义做标记,用于区分不同动画content:需要添加可见性动画的可组合项。上面这些参数除了enter和exit外都比较好理解,这里就不做过多解释,重点来看下enter和exit,可以看到enter的类型为EnterTransition,exit的类型为ExitTransition,那么接下来咱们来分别看看EnterTransition和ExitTransition吧!
这里其实有一个小问题,可以看到上面 Gif 图中并不是淡入并扩展和缩小并消失,这是为什么呢?继续往下看就能找到答案!
进入过渡——EnterTransition顾名思义,这个类主要是为了做进入过渡的,来简单看下它的源码吧:
@Immutable
sealedclassEnterTransition{
internalabstractvaldata:TransitionData
//组合不同的进入转换。组合的顺序并不重要,因为这些将同时启动
@Stable
operatorfunplus(enter:EnterTransition):EnterTransition{
returnEnterTransitionImpl(
TransitionData(
fade=data.fade?:enter.data.fade,
slide=data.slide?:enter.data.slide,
changeSize=data.changeSize?:enter.data.changeSize,
scale=data.scale?:enter.data.scale
)
)
}
companionobject{
//当不需要输入转换时,可以使用此函数。
valNone:EnterTransition=EnterTransitionImpl(TransitionData())
}
}
可以看到EnterTransition是一个密封类, 类中有一个抽象的不可变值data,类型为TransitionData,这个放到下面来说;类中还有一个函数,而且该函数有operator前缀, 这表示运算符重载,重载了“+”号,所以就可以使用“+”来组合不同的输入动画了,函数接收的参数也是EnterTransition,然后直接返回EnterTransitionImpl,又没见过,怎么办?继续看看EnterTransitionImpl是个啥!
@Immutable
privateclassEnterTransitionImpl(overridevaldata:TransitionData):EnterTransition()
可以看到EnterTransitionImpl类很简单,是一个私有类,继承自EnterTransition,注意类上有Immutable注解,Immutable注解可用于将类标记为生成不可变实例,但类的不变性没有得到验证,它是类型的一种承诺,即在构造实例之后,所有公开可访问的属性和字段都不会更改。EnterTransitionImpl还需要实现父类的抽象值,所有有TransitionData类型的参数data,上面咱们简单提到了TransitionData,这里来看下吧!
@Immutable
internaldataclassTransitionData(
valfade:Fade?=null,
valslide:Slide?=null,
valchangeSize:ChangeSize?=null,
valscale:Scale?=null
)
可以看到TransitionData类也有Immutable注解,这里就不做过多介绍,这是一个包内可见的数据类,里面有四个不可变值,分别是Fade、Slide、ChangeSize、Scale,其实从名称就能看出这几个参数分别代表的意思,不过还是来看下它们的源码吧!
@Immutable
internaldataclassFade(valalpha:Float,valanimationSpec:FiniteAnimationSpecFloat)
@Immutable
internaldataclassSlide(
valslideOffset:(fullSize:IntSize)-IntOffset,
valanimationSpec:FiniteAnimationSpecIntOffset
)
@Immutable
internaldataclassChangeSize(
valalignment:Alignment,
valsize:(fullSize:IntSize)-IntSize={IntSize(0,0)},
valanimationSpec:FiniteAnimationSpecIntSize,
valclip:Boolean=true
)
@Immutable
internaldataclassScale(
valscale:Float,
valtransformOrigin:TransformOrigin,
valanimationSpec:FiniteAnimationSpecFloat
)
可以看到这四个类都是不可变的数据类,分别表示颜色转变、滑动、大小变化及缩放。这几个类有一个共同点,都有一个共同的参数animationSpec,参数类型为FiniteAnimationSpec,来看看FiniteAnimationSpec是个啥?
interfaceFiniteAnimationSpec:AnimationSpec{
overridefunV:AnimationVectorvectorize(
converter:TwoWayConverterT,V
):VectorizedFiniteAnimationSpec
}
可以看到FiniteAnimationSpec是一个接口,继承自AnimationSpec,简单理解就是有限动画规格,定义了动画的时长及动画效果等,类似于原生安卓中的什么呢?嗯。。。差值器吧!FiniteAnimationSpec是所有非无限动画实现的接口,包括:TweenSpec,SpringSpec,KeyframesSpec,RepeatableSpec,SnapSpec等等,上一篇文章中说到的无限循环动画InfiniteRepeatableSpec没有继承这个接口,其实从名字看就知道了,InfiniteRepeatableSpec也继承自AnimationSpec。。。。
不行不行,扯太远了,其实看源码就是这样,看着看着一直往下看就会迷失了最初的目标,越看越多,越看越多,这里就先不接着往下看了,再看就没完没了了,这里咱们知道这四个类大概存储了什么数据就行了。动画规格咱们放到之后的文章中慢慢看,今天主要来过一遍可见性动画!
简单总结下,EnterTransition类中有一个函数,进行了运算符重载,可以有多个动画同时进行。
关闭过渡——ExitTransition其实看完刚才的EnterTransition类再来看ExitTransition就会觉得很简单了,不信的话咱们来看下ExitTransition的源码:
@Immutable
sealedclassExitTransition{
internalabstractvaldata:TransitionData
//结合不同的退出转换,组合顺序并不重要
@Stable
operatorfunplus(exit:ExitTransition):ExitTransition{
returnExitTransitionImpl(
TransitionData(
fade=data.fade?:exit.data.fade,
slide=data.slide?:exit.data.slide,
changeSize=data.changeSize?:exit.data.changeSize,
scale=data.scale?:exit.data.scale
)
)
}
companionobject{
//当不需要内置动画时使用
valNone:ExitTransition=ExitTransitionImpl(TransitionData())
}
}
是不是基本一致,连抽象不可变值都一摸一样,甚至值的名称都没变!唯一不同的是EnterTransition的实现类为EnterTransitionImpl,而ExitTransition的子类为ExitTransitionImpl,那就再看看ExitTransitionImpl!
@Immutable
privateclassExitTransitionImpl(overridevaldata:TransitionData):ExitTransition()
和EnterTransitionImpl不能说相似,只能说一摸一样。。。所以就不做过多介绍。。。
过渡——Transition文章开头的时候看AnimatedVisibility函数中只有下面的两行代码,
valtransition=updateTransition(visible,label)
AnimatedEnterExitImpl(transition,{it},modifier,enter,exit,content)
这一小节咱们先来看第一行,可以看到调用了updateTransition函数, 传入了当前显示状态和标签,来具体看下这个函数吧!
@Composable
funTupdateTransition(
targetState:T,
label:String?=null
):Transition{
valtransition=remember{Transition(targetState,label=label)}
transition.animateTo(targetState)
DisposableEffect(transition){
onDispose{
//出去的时候清理干净,确保观察者不会被困在中间状态
transition.onTransitionEnd()
}
}
returntransition
}
可以看到这个函数也是一个可组合项,返回值为Transition,函数中先记住并构建了一个Transition,然后调用了Transition的animateTo函数来执行过渡动画,然后调用了需要清理的效应DisposableEffect,之后在onDispose中调用了onTransitionEnd函数,以防卡在这里,最后返回刚才构建的Transition。
函数中的内容不难理解,现在唯一困惑的是Transition是个什么东西!那就来看看!
@Stable
classTransition@PublishedApiinternalconstructor(
privatevaltransitionState:MutableTransitionState,
vallabel:String?=null
)
这就是Transition的类声明,Transition在状态级别上管理所有子动画。子动画可以使用Transition以声明的方式创建。animateFloat、animateValue、animateColoranimateColor等。当targetState改变时,Transition将自动启动或调整其所有子动画的路线,使其动画到为每个动画定义的新目标值。
可以看到Transition构造函数中接收一个MutableTransitionState类型的参数和一个lable,但咱们看到上面传入的并不是MutableTransitionState类型的参数,肯定还有别的构造函数!
internalconstructor(
initialState:S,
label:String?
):this(MutableTransitionState(initialState),label)
没错,确实还有一个构造函数,接下来看下MutableTransitionState吧!
classMutableTransitionState(initialState:S){
//当前的状态
varcurrentState:SbymutableStateOf(initialState)
internalset
//过渡的目标状态
vartargetState:SbymutableStateOf(initialState)
//是否空闲
valisIdle:Boolean
get()=(currentState==targetState)!isRunning
//是否运行
internalvarisRunning:BooleanbymutableStateOf(false)
}
可以看到MutableTransitionState中构建了几个需要的状态,具体表示什么在上面代码中添加了注释。
过渡动画实现——AnimatedEnterExitImpl刚才看了AnimatedVisibility函数中的第一行代码,下面咱们来看下第二行代码:
AnimatedEnterExitImpl(transition,{it},modifier,enter,exit,content)
这块调用了一个实现函数,将刚构建好的Transition和之前的EnterTransition、ExitTransition统统传了进去,下面就来看下AnimatedEnterExitImpl!
@Composable
privatefunTAnimatedEnterExitImpl(
transition:Transition,visible:(T)-Boolean,
modifier:Modifier,enter:EnterTransition,
exit:ExitTransition,content:@Composable()AnimatedVisibilityScope.()-Unit
){
valisAnimationVisible=remember(transition){
mutableStateOf(visible(transition.currentState))
}
if(visible(transition.targetState)||isAnimationVisible.value||transition.isSeeking){
valchildTransition=transition.createChildTransition(label="EnterExitTransition"){
transition.targetEnterExit(visible,it)
}
LaunchedEffect(childTransition){
snapshotFlow{
childTransition.currentState==EnterExitState.Visible||
childTransition.targetState==EnterExitState.Visible
}.collect{
isAnimationVisible.value=it
}
}
AnimatedEnterExitImpl(childTransition,modifier,
enter=enter,exit=exit,content=content
)
}
}
这块代码有点多啊,但不要害怕,可以看到基本上这里使用到的类在刚才咱们都看过了,接下来需要的就是一行一行往下看,看看哪个没见过咱们再看!
函数中先记录了当前的状态,然后判断当前的状态值是否为 true,如果为 true 的话则创建子过渡,老规矩,来看看createChildTransition!
@Composable
inlinefunS,TTransition.createChildTransition(
label:String="ChildTransition",
transformToChildState:@Composable(parentState:S)-T,
):Transition{
valinitialParentState=remember(this){this.currentState}
valinitialState=transformToChildState(if(isSeeking)currentStateelseinitialParentState)
valtargetState=transformToChildState(this.targetState)
returncreateChildTransitionInternal(initialState,targetState,label)
}
可以看到createChildTransition是Transition的一个扩展函数,还是一个高阶函数,函数内获取了高阶函数中的返回值,然后直接返回调用了createChildTransitionInternal,并传入获取的值,这个值目前还不知道是什么,只知道是targetEnterExit返回的数据,这个一会再看,先接着看createChildTransitionInternal函数:
@Composable
internalfunS,TTransition.createChildTransitionInternal(
initialState:T,
targetState:T,
childLabel:String,
):Transition{
valtransition=remember(this){
Transition(MutableTransitionState(initialState),"${this.label}$childLabel")
}
DisposableEffect(transition){
addTransition(transition)
onDispose{
removeTransition(transition)
}
}
if(isSeeking){
transition.setPlaytimeAfterInitialAndTargetStateEstablished(
initialState,
targetState,
this.lastSeekedTimeNanos
)
}else{
transition.updateTarget(targetState)
transition.isSeeking=false
}
returntransition
}
可以看到createChildTransitionInternal也是Transition的一个扩展函数,然后函数中也使用了需要清理的效应,之后判断isSeeking的值是,isSeeking的值在setPlaytimeAfterInitialAndTargetStateEstablished函数中会被置为 true,如果isSeeking值为 true 则调用setPlaytimeAfterInitialAndTargetStateEstablished函数,用来设置初始状态和目标状态建立后的时间,如果为 false,则更新状态值。
到这里createChildTransition函数咱们大概看了下,但刚才还有一个扣,刚才说了不知道目标值是什么,因为那是targetEnterExit的返回值,现在咱们来看看!
@Composable
privatefunTTransition.targetEnterExit(
visible:(T)-Boolean,
targetState:T
):EnterExitState=key(this){
if(this.isSeeking){
if(visible(targetState)){
Visible
}else{
if(visible(this.currentState)){
PostExit
}else{
PreEnter
}
}
}else{
valhasBeenVisible=remember{mutableStateOf(false)}
if(visible(currentState)){
hasBeenVisible.value=true
}
if(visible(targetState)){
EnterExitState.Visible
}else{
//Ifneverbeenvisible,visible=falsemeansPreEnter,otherwisePostExit
if(hasBeenVisible.value){
EnterExitState.PostExit
}else{
EnterExitState.PreEnter
}
}
}
}
同样的,targetEnterExit也是一个扩展函数,返回值为EnterExitState。这里也使用了isSeeking来进行判断,然后根据当前的值来设置不同的EnterExitState。EnterExitState又是个啥呢???接着来看!
enumclassEnterExitState{
//自定义进入动画的初始状态。
PreEnter,
//自定义进入动画的目标状态,也是动画过程中自定义退出动画的初始状态。
Visible,
//自定义退出动画的目标状态。
PostExit
}
奥,EnterExitState只是一个枚举类,定义了三种状态:初始状态、进入动画的状态和退出动画的状态。
下面接着来看AnimatedEnterExitImpl:
LaunchedEffect(childTransition){
snapshotFlow{
childTransition.currentState==EnterExitState.Visible||
childTransition.targetState==EnterExitState.Visible
}.collect{
isAnimationVisible.value=it
}
}
AnimatedEnterExitImpl(childTransition,modifier,
enter=enter,exit=exit,content=content
)
这里使用了LaunchedEffect效应,并使用snapshotFlow将State转为了Flow,然后将值设置到isAnimationVisible。
后面又调用了相同名字的一个函数AnimatedEnterExitImpl:
@Composable
privateinlinefunAnimatedEnterExitImpl(
transition:TransitionEnterExitState,
modifier:Modifier,
enter:EnterTransition,
exit:ExitTransition,
content:@ComposableAnimatedVisibilityScope.()-Unit
){
if(transition.currentState==EnterExitState.Visible||
transition.targetState==EnterExitState.Visible
){
valscope=remember(transition){AnimatedVisibilityScopeImpl(transition)}
Layout(
content={scope.content()},
modifier=modifier.then(transition.createModifier(enter,exit,"Built-in")),
measurePolicy=remember{AnimatedEnterExitMeasurePolicy(scope)}
)
}
}
函数名字一样,但参数不同,这里将刚才构建好的TransitionEnterExitState传了进来,然后函数内先对状态进行了过滤,然后构建了Layout。
Layout中设置了modifier,modifier调用了then函数,then函数用于将此修饰符与另一个修饰符连接,然后连接了transition.createModifier(enter, exit, "Built-in"),大家发现点什么没有,这里使用到了上面咱们构建好了所有东西。。。那么。。。。哈哈哈哈!
柳暗花明下面咱们就来看看transition.createModifier这个函数,先来看下函数体:
@Composable
internalfunTransitionEnterExitState.createModifier(
enter:EnterTransition,
exit:ExitTransition,
label:String
):Modifier
嗯,没错,是TransitionEnterExitState的一个扩展函数,也是一个可组合项!接着往下看几行代码:
varmodifier:Modifier=Modifier
modifier=modifier.slideInOut(
this,
rememberUpdatedState(enter.data.slide),
rememberUpdatedState(exit.data.slide),
label
).shrinkExpand(
this,
rememberUpdatedState(enter.data.changeSize),
rememberUpdatedState(exit.data.changeSize),
label
)
构建了一个Modifier,然后调用了slideInOut和shrinkExpand,没错,这是滑动和缩小放大!接着来!
varshouldAnimateAlphabyremember(this){mutableStateOf(false)}
varshouldAnimateScalebyremember(this){mutableStateOf(false)}
if(currentState==targetState!isSeeking){
shouldAnimateAlpha=false
shouldAnimateScale=false
}else{
if(enter.data.fade!=null||exit.data.fade!=null){
shouldAnimateAlpha=true
}
if(enter.data.scale!=null||exit.data.scale!=null){
shouldAnimateScale=true
}
}
创建两个值来记录是否需要透明度的转换和缩放!下面来看下执行的代码:
if(shouldAnimateScale){
......
modifier=modifier.graphicsLayer{
this.alpha=alpha
this.scaleX=scale
this.scaleY=scale
this.transformOrigin=transformOrigin
}
}elseif(shouldAnimateAlpha){
modifier=modifier.graphicsLayer{
this.alpha=alpha
}
}
嗯呢,是不是豁然开朗!但是slideInOut和shrinkExpand函数也是可见性动画这里定义的Modifier的扩展函数,里面还有一些自定义的东西,但这不是本文的重点了。
别的可见性动画文章开头说了,可见性动画目前一共有六个,听着很吓人,其实大同小异,来简单看下不同吧!
@Composable
funRowScope.AnimatedVisibility(
visible:Boolean,
modifier:Modifier=Modifier,
enter:EnterTransition=fadeIn()+expandHorizontally(),
exit:ExitTransition=fadeOut()+shrinkHorizontally(),
label:String="AnimatedVisibility",
content:@Composable()AnimatedVisibilityScope.()-Unit
){
valtransition=updateTransition(visible,label)
AnimatedEnterExitImpl(transition,{it},modifier,enter,exit,content)
}
@Composable
funColumnScope.AnimatedVisibility(
visible:Boolean,
modifier:Modifier=Modifier,
enter:EnterTransition=fadeIn()+expandVertically(),
exit:ExitTransition=fadeOut()+shrinkVertically(),
label:String="AnimatedVisibility",
content:@ComposableAnimatedVisibilityScope.()-Unit
){
valtransition=updateTransition(visible,label)
AnimatedEnterExitImpl(transition,{it},modifier,enter,exit,content)
}
上面这两个和咱们上面说的基本一样,只不过一个是RowScope的扩展函数,另一个是ColumnScope的扩展函数,并且在默认动画上还有些区别,RowScope默认是横向的扩展和收缩,ColumnScope是纵向的扩展和收缩,更加方便咱们日常调用!这也就解释了文章开头提出的小问题!
接着再来看别的!
@Composable
funAnimatedVisibility(
visibleState:MutableTransitionStateBoolean,
modifier:Modifier=Modifier,
enter:EnterTransition=fadeIn()+expandIn(),
exit:ExitTransition=fadeOut()+shrinkOut(),
label:String="AnimatedVisibility",
content:@Composable()AnimatedVisibilityScope.()-Unit
){
valtransition=updateTransition(visibleState,label)
AnimatedEnterExitImpl(transition,{it},modifier,enter,exit,content)
}
这个和之前的就有点区别了,第一个参数就不同了,参数类型为MutableTransitionState,其实是一样的,咱们上面也都说到了,MutableTransitionState的使用方法在上一篇文章中也介绍过,感兴趣的可以去看一下。
剩下的两个还是RowScope和ColumnScope的扩展函数,也是参数类型改为了MutableTransitionState,这里也就不做过多介绍。
突兀的结尾本篇文章带大家看了可见性动画的一些源码,很多其实都是点到为止,并没有一致不断深入,一直深入就会陷入其中,忘了看源码的本意,本文所有源码基于Compose 1.3.0-beta02。
本文至此结束,有用的地方大家可以参考,当然如果能帮助到大家,哪怕是一点也足够了。就这样。
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线