<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
本篇文章是此專欄的第二篇文章,上一篇文章簡單寫了下 Compose
的動畫,讓大家先看了下 Compose
開箱即用的動畫效果,效果還是挺好的,感興趣的可以去看下:Compose 動畫藝術探索之瞅下 Compose 的動畫
可見性動畫在上一篇文章中介紹過,不過只是簡單使用,沒看過上一篇文章的也不用擔心,給大家看下可見性動畫的實際效果。
實現程式碼也很簡單,來回顧下:
val visible = remember { mutableStateOf(true) } AnimatedVisibility(visible = visible.value,) { Text(text = "天青色等煙雨,而我在等你,炊煙裊裊升起,隔江千萬裡") }
上一篇文章主要介紹了 Compose
動畫的便攜之處,例如上面程式碼,確實非常簡單就能實現之前原生安卓中比較難實現的動畫效果,今天咱們就來稍微深入一點看看,從小節標題也能知道,就從可見性動畫來看!
怎麼看呢?直接點進去原始碼來看!先來看看 AnimatedVisibility
的函數定義吧!
@Composable fun AnimatedVisibility( visible: Boolean, modifier: Modifier = Modifier, enter: EnterTransition = fadeIn() + expandIn(), exit: ExitTransition = shrinkOut() + fadeOut(), label: String = "AnimatedVisibility", content: @Composable() AnimatedVisibilityScope.() -> Unit ) { val transition = updateTransition(visible, label) AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content) }
其實可組合項 AnimatedVisibility
不是隻有這一個,目前 Compose 1.3.0-beta02
版本中有六個,這個咱們待會再說,先看這一個,也是第一個,可以看到函數中一共可以接收六個引數,下面先來看看這六個引數分別有什麼作用吧:
Compose
中 Modifier
簡直隨處可見fadeIn() + expandIn()
,大致意思為淡入並擴充套件開shrinkOut() + fadeOut()
,大致意思為縮小並淡出消失AnimatedVisibility
,可以自定義做標記,用於區分不同動畫上面這些引數除了 enter
和 exit
外都比較好理解,這裡就不做過多解釋,重點來看下 enter
和 exit
,可以看到 enter
的型別為 EnterTransition
,exit
的型別為 ExitTransition
,那麼接下來咱們來分別看看 EnterTransition
和 ExitTransition
吧!
這裡其實有一個小問題,可以看到上面 Gif 圖中並不是淡入並擴充套件和縮小並消失,這是為什麼呢?繼續往下看就能找到答案!
顧名思義,這個類主要是為了做進入過渡的,來簡單看下它的原始碼吧:
@Immutable sealed class EnterTransition { internal abstract val data: TransitionData // 組合不同的進入轉換。組合的順序並不重要,因為這些將同時啟動 @Stable operator fun plus(enter: EnterTransition): EnterTransition { return EnterTransitionImpl( 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 ) ) } companion object { // 當不需要輸入轉換時,可以使用此函數。 val None: EnterTransition = EnterTransitionImpl(TransitionData()) } }
可以看到 EnterTransition
是一個密封類, 類中有一個抽象的不可變值 data
,型別為 TransitionData
,這個放到下面來說;類中還有一個函數,而且該函數有 operator
字首, 這表示運運算元過載,過載了“+”號,所以就可以使用“+”來組合不同的輸入動畫了,函數接收的引數也是 EnterTransition
,然後直接返回 EnterTransitionImpl
,又沒見過,怎麼辦?繼續看看 EnterTransitionImpl
是個啥!
@Immutable private class EnterTransitionImpl(override val data: TransitionData) : EnterTransition()
可以看到 EnterTransitionImpl
類很簡單,是一個私有類,繼承自 EnterTransition
,注意類上有 Immutable
註解, Immutable
註解可用於將類標記為生成不可變範例,但類的不變性沒有得到驗證,它是型別的一種承諾,即在構造範例之後,所有公開可存取的屬性和欄位都不會更改。 EnterTransitionImpl
還需要實現父類別的抽象值,所有有 TransitionData
型別的引數 data
,上面咱們簡單提到了 TransitionData
,這裡來看下吧!
@Immutable internal data class TransitionData( val fade: Fade? = null, val slide: Slide? = null, val changeSize: ChangeSize? = null, val scale: Scale? = null )
可以看到 TransitionData
類也有 Immutable
註解,這裡就不做過多介紹,這是一個包內可見的資料類,裡面有四個不可變值,分別是 Fade
、Slide
、ChangeSize
、Scale
,其實從名稱就能看出這幾個引數分別代表的意思,不過還是來看下它們的原始碼吧!
@Immutable internal data class Fade(val alpha: Float, val animationSpec: FiniteAnimationSpec<Float>) @Immutable internal data class Slide( val slideOffset: (fullSize: IntSize) -> IntOffset, val animationSpec: FiniteAnimationSpec<IntOffset> ) @Immutable internal data class ChangeSize( val alignment: Alignment, val size: (fullSize: IntSize) -> IntSize = { IntSize(0, 0) }, val animationSpec: FiniteAnimationSpec<IntSize>, val clip: Boolean = true ) @Immutable internal data class Scale( val scale: Float, val transformOrigin: TransformOrigin, val animationSpec: FiniteAnimationSpec<Float> )
可以看到這四個類都是不可變的資料類,分別表示顏色轉變、滑動、大小變化及縮放。這幾個類有一個共同點,都有一個共同的引數 animationSpec
,引數型別為 FiniteAnimationSpec
,來看看 FiniteAnimationSpec
是個啥?
interface FiniteAnimationSpec<T> : AnimationSpec<T> { override fun <V : AnimationVector> vectorize( converter: TwoWayConverter<T, V> ): VectorizedFiniteAnimationSpec<V> }
可以看到 FiniteAnimationSpec
是一個介面,繼承自 AnimationSpec
,簡單理解就是有限動畫規格,定義了動畫的時長及動畫效果等,類似於原生安卓中的什麼呢?嗯。。。差值器吧!FiniteAnimationSpec
是所有非無限動畫實現的介面,包括: TweenSpec
, SpringSpec
, KeyframesSpec
, RepeatableSpec
, SnapSpec
等等,上一篇文章中說到的無限迴圈動畫 InfiniteRepeatableSpec
沒有繼承這個介面,其實從名字看就知道了,InfiniteRepeatableSpec
也繼承自 AnimationSpec
。。。。
不行不行,扯太遠了,其實看原始碼就是這樣,看著看著一直往下看就會迷失了最初的目標,越看越多,越看越多,這裡就先不接著往下看了,再看就沒完沒了了,這裡咱們知道這四個類大概儲存了什麼資料就行了。動畫規格咱們放到之後的文章中慢慢看,今天主要來過一遍可見性動畫!
簡單總結下, EnterTransition
類中有一個函數,進行了運運算元過載,可以有多個動畫同時進行。
其實看完剛才的 EnterTransition
類再來看 ExitTransition
就會覺得很簡單了,不信的話咱們來看下 ExitTransition
的原始碼:
@Immutable sealed class ExitTransition { internal abstract val data: TransitionData // 結合不同的退出轉換,組合順序並不重要 @Stable operator fun plus(exit: ExitTransition): ExitTransition { return ExitTransitionImpl( 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 ) ) } companion object { // 當不需要內建動畫時使用 val None: ExitTransition = ExitTransitionImpl(TransitionData()) } }
是不是基本一致,連抽象不可變值都一摸一樣,甚至值的名稱都沒變!唯一不同的是 EnterTransition
的實現類為 EnterTransitionImpl
,而 ExitTransition
的子類為 ExitTransitionImpl
,那就再看看 ExitTransitionImpl
!
@Immutable private class ExitTransitionImpl(override val data: TransitionData) : ExitTransition()
和 EnterTransitionImpl
不能說相似,只能說一摸一樣。。。所以就不做過多介紹。。。
文章開頭的時候看 AnimatedVisibility
函數中只有下面的兩行程式碼,
val transition = updateTransition(visible, label) AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
這一小節咱們先來看第一行,可以看到呼叫了 updateTransition
函數, 傳入了當前顯示狀態和標籤,來具體看下這個函數吧!
@Composable fun <T> updateTransition( targetState: T, label: String? = null ): Transition<T> { val transition = remember { Transition(targetState, label = label) } transition.animateTo(targetState) DisposableEffect(transition) { onDispose { // 出去的時候清理乾淨,確保觀察者不會被困在中間狀態 transition.onTransitionEnd() } } return transition }
可以看到這個函數也是一個可組合項,返回值為 Transition
,函數中先記住並構建了一個 Transition
,然後呼叫了 Transition
的 animateTo
函數來執行過渡動畫,然後呼叫了需要清理的效應 DisposableEffect
,之後在 onDispose
中呼叫了 onTransitionEnd
函數,以防卡在這裡,最後返回剛才構建的 Transition
。
函數中的內容不難理解,現在唯一困惑的是 Transition
是個什麼東西!那就來看看!
@Stable class Transition<S> @PublishedApi internal constructor( private val transitionState: MutableTransitionState<S>, val label: String? = null )
這就是 Transition
的類宣告,Transition
在狀態級別上管理所有子動畫。子動畫可以使用Transition
以宣告的方式建立。animateFloat
、animateValue
、animateColor
animateColor
等。當 targetState
改變時,Transition
將自動啟動或調整其所有子動畫的路線,使其動畫到為每個動畫定義的新目標值。
可以看到 Transition
建構函式中接收一個 MutableTransitionState
型別的引數和一個 lable
,但咱們看到上面傳入的並不是 MutableTransitionState
型別的引數,肯定還有別的建構函式!
internal constructor( initialState: S, label: String? ) : this(MutableTransitionState(initialState), label)
沒錯,確實還有一個建構函式,接下來看下 MutableTransitionState
吧!
class MutableTransitionState<S>(initialState: S) { // 當前的狀態 var currentState: S by mutableStateOf(initialState) internal set // 過渡的目標狀態 var targetState: S by mutableStateOf(initialState) // 是否空閒 val isIdle: Boolean get() = (currentState == targetState) && !isRunning // 是否執行 internal var isRunning: Boolean by mutableStateOf(false) }
可以看到 MutableTransitionState
中構建了幾個需要的狀態,具體表示什麼在上面程式碼中新增了註釋。
剛才看了 AnimatedVisibility
函數中的第一行程式碼,下面咱們來看下第二行程式碼:
AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
這塊呼叫了一個實現函數,將剛構建好的 Transition
和之前的 EnterTransition
、ExitTransition
統統傳了進去,下面就來看下 AnimatedEnterExitImpl
!
@Composable private fun <T> AnimatedEnterExitImpl( transition: Transition<T>,visible: (T) -> Boolean, modifier: Modifier,enter: EnterTransition, exit: ExitTransition,content: @Composable() AnimatedVisibilityScope.() -> Unit ) { val isAnimationVisible = remember(transition) { mutableStateOf(visible(transition.currentState)) } if (visible(transition.targetState) || isAnimationVisible.value || transition.isSeeking) { val childTransition = 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 inline fun <S, T> Transition<S>.createChildTransition( label: String = "ChildTransition", transformToChildState: @Composable (parentState: S) -> T, ): Transition<T> { val initialParentState = remember(this) { this.currentState } val initialState = transformToChildState(if (isSeeking) currentState else initialParentState) val targetState = transformToChildState(this.targetState) return createChildTransitionInternal(initialState, targetState, label) }
可以看到 createChildTransition
是 Transition
的一個擴充套件函數,還是一個高階函數,函數內獲取了高階函數中的返回值,然後直接返回撥用了 createChildTransitionInternal
,並傳入獲取的值,這個值目前還不知道是什麼,只知道是 targetEnterExit
返回的資料,這個一會再看,先接著看 createChildTransitionInternal
函數:
@Composable internal fun <S, T> Transition<S>.createChildTransitionInternal( initialState: T, targetState: T, childLabel: String, ): Transition<T> { val transition = 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 } return transition }
可以看到 createChildTransitionInternal
也是 Transition
的一個擴充套件函數,然後函數中也使用了需要清理的效應,之後判斷 isSeeking
的值是,isSeeking
的值在 setPlaytimeAfterInitialAndTargetStateEstablished
函數中會被置為 true,如果 isSeeking
值為 true 則呼叫 setPlaytimeAfterInitialAndTargetStateEstablished
函數,用來設定初始狀態和目標狀態建立後的時間,如果為 false,則更新狀態值。
到這裡 createChildTransition
函數咱們大概看了下,但剛才還有一個扣,剛才說了不知道目標值是什麼,因為那是 targetEnterExit
的返回值,現在咱們來看看!
@Composable private fun <T> Transition<T>.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 { val hasBeenVisible = remember { mutableStateOf(false) } if (visible(currentState)) { hasBeenVisible.value = true } if (visible(targetState)) { EnterExitState.Visible } else { // If never been visible, visible = false means PreEnter, otherwise PostExit if (hasBeenVisible.value) { EnterExitState.PostExit } else { EnterExitState.PreEnter } } } }
同樣的,targetEnterExit
也是一個擴充套件函數,返回值為 EnterExitState
。這裡也使用了 isSeeking
來進行判斷,然後根據當前的值來設定不同的 EnterExitState
。EnterExitState
又是個啥呢???接著來看!
enum class EnterExitState { // 自定義進入動畫的初始狀態。 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 private inline fun AnimatedEnterExitImpl( transition: Transition<EnterExitState>, modifier: Modifier, enter: EnterTransition, exit: ExitTransition, content: @Composable AnimatedVisibilityScope.() -> Unit ) { if (transition.currentState == EnterExitState.Visible || transition.targetState == EnterExitState.Visible ) { val scope = remember(transition) { AnimatedVisibilityScopeImpl(transition) } Layout( content = { scope.content() }, modifier = modifier.then(transition.createModifier(enter, exit, "Built-in")), measurePolicy = remember { AnimatedEnterExitMeasurePolicy(scope) } ) } }
函數名字一樣,但引數不同,這裡將剛才構建好的 Transition<EnterExitState>
傳了進來,然後函數內先對狀態進行了過濾,然後構建了 Layout
。
Layout
中設定了 modifier
,modifier
呼叫了 then
函數,then
函數用於將此修飾符與另一個修飾符連線,然後連線了 transition.createModifier(enter, exit, "Built-in")
,大家發現點什麼沒有,這裡使用到了上面咱們構建好了所有東西。。。那麼。。。。哈哈哈哈!
下面咱們就來看看 transition.createModifier
這個函數,先來看下函數體:
@Composable internal fun Transition<EnterExitState>.createModifier( enter: EnterTransition, exit: ExitTransition, label: String ): Modifier
嗯,沒錯,是 Transition<EnterExitState>
的一個擴充套件函數,也是一個可組合項!接著往下看幾行程式碼:
var modifier: 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
,沒錯,這是滑動和縮小放大!接著來!
var shouldAnimateAlpha by remember(this) { mutableStateOf(false) } var shouldAnimateScale by remember(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 } } else if (shouldAnimateAlpha) { modifier = modifier.graphicsLayer { this.alpha = alpha } }
嗯呢,是不是豁然開朗!但是 slideInOut
和 shrinkExpand
函數也是可見性動畫這裡定義的 Modifier
的擴充套件函數,裡面還有一些自定義的東西,但這不是本文的重點了。
文章開頭說了,可見性動畫目前一共有六個,聽著很嚇人,其實大同小異,來簡單看下不同吧!
@Composable fun RowScope.AnimatedVisibility( visible: Boolean, modifier: Modifier = Modifier, enter: EnterTransition = fadeIn() + expandHorizontally(), exit: ExitTransition = fadeOut() + shrinkHorizontally(), label: String = "AnimatedVisibility", content: @Composable() AnimatedVisibilityScope.() -> Unit ) { val transition = updateTransition(visible, label) AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content) } @Composable fun ColumnScope.AnimatedVisibility( visible: Boolean, modifier: Modifier = Modifier, enter: EnterTransition = fadeIn() + expandVertically(), exit: ExitTransition = fadeOut() + shrinkVertically(), label: String = "AnimatedVisibility", content: @Composable AnimatedVisibilityScope.() -> Unit ) { val transition = updateTransition(visible, label) AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content) }
上面這兩個和咱們上面說的基本一樣,只不過一個是 RowScope
的擴充套件函數,另一個是 ColumnScope
的擴充套件函數,並且在預設動畫上還有些區別,RowScope
預設是橫向的擴充套件和收縮,ColumnScope
是縱向的擴充套件和收縮,更加方便咱們日常呼叫!這也就解釋了文章開頭提出的小問題!
接著再來看別的!
@Composable fun AnimatedVisibility( visibleState: MutableTransitionState<Boolean>, modifier: Modifier = Modifier, enter: EnterTransition = fadeIn() + expandIn(), exit: ExitTransition = fadeOut() + shrinkOut(), label: String = "AnimatedVisibility", content: @Composable() AnimatedVisibilityScope.() -> Unit ) { val transition = updateTransition(visibleState, label) AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content) }
這個和之前的就有點區別了,第一個引數就不同了,引數型別為 MutableTransitionState
,其實是一樣的,咱們上面也都說到了,MutableTransitionState
的使用方法在上一篇文章中也介紹過,感興趣的可以去看一下。
剩下的兩個還是 RowScope
和 ColumnScope
的擴充套件函數,也是引數型別改為了 MutableTransitionState
,這裡也就不做過多介紹。
本篇文章帶大家看了可見性動畫的一些原始碼,很多其實都是點到為止,並沒有一致不斷深入,一直深入就會陷入其中,忘了看原始碼的本意,本文所有原始碼基於 Compose 1.3.0-beta02
。
本文至此結束,有用的地方大家可以參考,當然如果能幫助到大家,更多關於Compose 可見性動畫的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45