全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

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

与我们取得联系

13245491521     13245491521

2022-09-29_Compose 动画艺术探索之可见性动画

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

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。 本文至此结束,有用的地方大家可以参考,当然如果能帮助到大家,哪怕是一点也足够了。就这样。 阅读原文

上一篇:2017-03-24_【重磅】沟通渠道:以TA为价值核心的连通(内附完整报告下载) 下一篇:2023-12-17_2023年底 “最受欢迎“ 的4支圣诞广告

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
项目经理手机

微信
咨询

加微信获取报价