Compose?動畫藝術(shù)之屬性動畫探索
前言
本篇文章是此專欄的第三篇文章,如果想閱讀前兩篇文章的話請點擊下方鏈接:
Compose的屬性動畫
屬性動畫是通過不斷地修改值來實現(xiàn)的,而初始值和結(jié)束值之間的過渡動畫就需要來計算了。在 Compose
中為我們提供了一整套 api 來實現(xiàn)屬性動畫,具體有哪些呢?讓我們一起來看下吧!
官方為我們提供了上圖這十種方法,我們可以根據(jù)實際項目中的需求進行挑選使用。
在第一篇文章中也提到了 Compose
的屬性動畫,但只是簡單使用了下,告訴大家 Compose
有這個東西,今天咱們來具體看下!
先來看下 animateColorAsState
的代碼吧:
@Composable fun animateColorAsState( ? ?targetValue: Color, ? ?animationSpec: AnimationSpec<Color> = colorDefaultSpring, ? ?label: String = "ColorAnimation", ? ?finishedListener: ((Color) -> Unit)? = null ): State<Color> { ? ?val converter = remember(targetValue.colorSpace) { ? ? ? (Color.VectorConverter)(targetValue.colorSpace) ? } ? ?return animateValueAsState( ? ? ? ?targetValue, converter, animationSpec, label = label, finishedListener = finishedListener ? ) }
可以看到一共接收四個參數(shù),來分別看下代表什么吧:
- targetValue:顧名思義,目標值,這里對應(yīng)的就是想要轉(zhuǎn)換成的顏色
- animationSpec:動畫規(guī)格,動畫隨著時間改變值的一種規(guī)格吧,上一篇文章中也提到了,但由于上一篇文章主要內(nèi)容并不是這個,也就沒有講,本篇文章會詳細說明
- label:標簽,以區(qū)別于其他動畫
- finishedListener:在動畫完成時會進行回調(diào)
參數(shù)并不算多,而且有三個是可選參數(shù),也就只有 targetValue
必須要進行設(shè)置。方法體內(nèi)只通過 Color.colorSpace
強轉(zhuǎn)構(gòu)建了一個 TwoWayConverter
。
前面說過,大多數(shù) Compose
動畫 API 支持將 Float
、Color
、Dp
以及其他基本數(shù)據(jù)類型作為 開箱即用的動畫值,但有時我們需要為其他數(shù)據(jù)類型(比如自定義類型)添加動畫效果。在動畫播放期間,任何動畫值都表示為 AnimationVector
。使用相應(yīng)的 TwoWayConverter
即可將值轉(zhuǎn)換為 AnimationVector
,反之亦然,這樣一來,核心動畫系統(tǒng)就可以統(tǒng)一對其進行處理了。由于顏色有 argb,所以構(gòu)建的時候使用的是 AnimationVector4D
,來看下吧:
val Color.Companion.VectorConverter: ? (colorSpace: ColorSpace) -> TwoWayConverter<Color, AnimationVector4D> ? ? ? ?get() = ColorToVector
如果按照我之前的習慣肯定要接著看 animateValueAsState
方法內(nèi)部的代碼了,但今天等會再看!再來看看 animateDpAsState
的代碼吧!
@Composable fun animateDpAsState( ? ?targetValue: Dp, ? ?animationSpec: AnimationSpec<Dp> = dpDefaultSpring, ? ?label: String = "DpAnimation", ? ?finishedListener: ((Dp) -> Unit)? = null ): State<Dp> { ? ?return animateValueAsState( ? ? ? ?targetValue, ? ? ? ?Dp.VectorConverter, ? ? ? ?animationSpec, ? ? ? ?label = label, ? ? ? ?finishedListener = finishedListener ? ) }
發(fā)現(xiàn)了點什么沒有,參數(shù)基本一摸一樣,別著急,咱們再看看別的!
@Composable fun animateIntAsState( ? ?targetValue: Int, ? ?animationSpec: AnimationSpec<Int> = intDefaultSpring, ? ?label: String = "IntAnimation", ? ?finishedListener: ((Int) -> Unit)? = null ) ? @Composable fun animateSizeAsState( ? ?targetValue: Size, ? ?animationSpec: AnimationSpec<Size> = sizeDefaultSpring, ? ?label: String = "SizeAnimation", ? ?finishedListener: ((Size) -> Unit)? = null ) ? @Composable fun animateRectAsState( ? ?targetValue: Rect, ? ?animationSpec: AnimationSpec<Rect> = rectDefaultSpring, ? ?label: String = "RectAnimation", ? ?finishedListener: ((Rect) -> Unit)? = null )
不能說是大同小異,只能說是一摸一樣!既然一摸一樣的話咱們就以文章開頭的 animateColorAsState
來看吧!
上面的說法其實是不對的,并不是有十種,而是九種,因為九種都調(diào)用了 animateValueAsState
,其實也可以說有無數(shù)種,因為可以自定義。。。。
參數(shù)
下面先來看下 animateValueAsState
的方法體吧:
@Composable fun <T, V : AnimationVector> animateValueAsState( ? ?targetValue: T, ? ?typeConverter: TwoWayConverter<T, V>, ? ?animationSpec: AnimationSpec<T> = remember { spring() }, ? ?visibilityThreshold: T? = null, ? ?label: String = "ValueAnimation", ? ?finishedListener: ((T) -> Unit)? = null ): State<T>
來看看接收的參數(shù)吧,可以發(fā)現(xiàn)有兩個參數(shù)沒有見過:
- typeConverter:類型轉(zhuǎn)換器,將需要的類型轉(zhuǎn)換為
AnimationVector
- visibilityThreshold:一個可選的閾值,用于定義何時動畫值可以被認為足夠接近targetValue以結(jié)束動畫
OK,剩下的參數(shù)在上面都介紹過,就不重復(fù)進行介紹了。
方法體
由于 animateValueAsState
方法有點長,所以分開來看吧,接下來看下 animateValueAsState
方法中的前半部分:
val animatable = remember { Animatable(targetValue, typeConverter, visibilityThreshold, label) } val listener by rememberUpdatedState(finishedListener) val animSpec: AnimationSpec<T> by rememberUpdatedState( ? ?animationSpec.run { ? ? ? ?if (visibilityThreshold != null && this is SpringSpec && ? ? ? ? ? ?this.visibilityThreshold != visibilityThreshold ? ? ? ) { ? ? ? ? ? ?spring(dampingRatio, stiffness, visibilityThreshold) ? ? ? } else { ? ? ? ? ? ?this ? ? ? } ? } ) val channel = remember { Channel<T>(Channel.CONFLATED) } SideEffect { ? ?channel.trySend(targetValue) } LaunchedEffect(channel) { ? ?for (target in channel) { ? ? ? ?val newTarget = channel.tryReceive().getOrNull() ?: target ? ? ? ?launch { ? ? ? ? ? ?if (newTarget != animatable.targetValue) { ? ? ? ? ? ? ? ?animatable.animateTo(newTarget, animSpec) ? ? ? ? ? ? ? ?listener?.invoke(animatable.value) ? ? ? ? ? } ? ? ? } ? } }
可以看到首先構(gòu)建了一個 Animatable
,然后記錄了完成回調(diào),又記錄了 AnimationSpec
,之后有個判斷,如果 visibilityThreshold
不為空并且 AnimationSpec
為 SpringSpec
的時候為新構(gòu)建的一個 AnimationSpec
,反之則還是傳進來的 AnimationSpec
。
那 Animatable
是個啥呢?它是一個值容器,它可以在通過 animateTo
更改值時為值添加動畫效果,它可確保一致的連續(xù)性和互斥性,這意味著值變化始終是連續(xù)的,并且會取消任何正在播放的動畫。Animatable
的許多功能(包括 animateTo
)以掛起函數(shù)的形式提供,所以需要封裝在適當?shù)膮f(xié)程作用域內(nèi),所以下面使用了 LaunchedEffect
來包裹執(zhí)行 animateTo
方法,最后調(diào)用了動畫完成的回調(diào)。
由于 Animatable
類中代碼比較多,先來看下類的初始化及構(gòu)造方法吧!
class Animatable<T, V : AnimationVector>( ? ?initialValue: T, ? ?val typeConverter: TwoWayConverter<T, V>, ? ?private val visibilityThreshold: T? = null, ? ?val label: String = "Animatable" )
可以看到這里使用到的參數(shù)在 animateValueAsState
中都有,就不一一介紹了,挑著重點來,來看看上面使用到的 animateTo
吧:
suspend fun animateTo( ? ?targetValue: T, ? ?animationSpec: AnimationSpec<T> = defaultSpringSpec, ? ?initialVelocity: T = velocity, ? ?block: (Animatable<T, V>.() -> Unit)? = null ): AnimationResult<T, V> { ? ?val anim = TargetBasedAnimation( ? ? ? ?animationSpec = animationSpec, ? ? ? ?initialValue = value, ? ? ? ?targetValue = targetValue, ? ? ? ?typeConverter = typeConverter, ? ? ? ?initialVelocity = initialVelocity ? ) ? ?return runAnimation(anim, initialVelocity, block) }
可以看到 animateTo
使用傳進來的參數(shù)構(gòu)建了一個 TargetBasedAnimation
,這是一個方便的動畫包裝類,適用于所有基于目標的動畫,即具有預(yù)定義結(jié)束值的動畫。然后返回調(diào)用了 runAnimation
,返回值為 AnimationResult
,來看下吧:
class AnimationResult<T, V : AnimationVector>( ? ? ? ?val endState: AnimationState<T, V>, ? ? ? ?val endReason: AnimationEndReason ) { ? ?override fun toString(): String = "AnimationResult(endReason=$endReason, endState=$endState)" }
AnimationResult
在動畫結(jié)尾包含關(guān)于動畫的信息,endState
捕獲動畫在最后一幀的值 evelocityframe time
等。它可以用于啟動另一個動畫以從先前中斷的動畫繼續(xù)速度。endReason
描述動畫結(jié)束的原因。
下面看下 runAnimation
吧:
private suspend fun runAnimation( ? ?animation: Animation<T, V>, ? ?initialVelocity: T, ? ?block: (Animatable<T, V>.() -> Unit)? ): AnimationResult<T, V> { ? ? ?val startTime = internalState.lastFrameTimeNanos ? ?return mutatorMutex.mutate { ? ? ? ?try { ? ? ? ? ? ...... ? ? ? ? ? ?endState.animate( ? ? ? ? ? ? ? ?animation, ? ? ? ? ? ? ? ?startTime ? ? ? ? ? ) { ? ? ? ? ? ? ? ?updateState(internalState) ? ? ? ? ? ? ? ...... ? ? ? ? ? } ? ? ? ? ? ?val endReason = if (clampingNeeded) BoundReached else Finished ? ? ? ? ? ?endAnimation() ? ? ? ? ? ?AnimationResult(endState, endReason) ? ? ? } catch (e: CancellationException) { ? ? ? ? ? ?// Clean up internal states first, then throw. ? ? ? ? ? ?endAnimation() ? ? ? ? ? ?throw e ? ? ? } ? } }
這里需要注意:所有不同類型的動畫代碼路徑最終都會匯聚到這個方法中。
好了,基本快見到陽光了!
天亮了
上面方法中有一行:endState.animate
,這個是關(guān)鍵,來看下!
internal suspend fun <T, V : AnimationVector> AnimationState<T, V>.animate( ? ?animation: Animation<T, V>, ? ?startTimeNanos: Long = AnimationConstants.UnspecifiedTime, ? ?block: AnimationScope<T, V>.() -> Unit = {} ) { ? ?val initialValue = animation.getValueFromNanos(0) ? ?val initialVelocityVector = animation.getVelocityVectorFromNanos(0) ? ?var lateInitScope: AnimationScope<T, V>? = null ? ?try { ? ? ? ?if (startTimeNanos == AnimationConstants.UnspecifiedTime) { ? ? ? ? ? ?val durationScale = coroutineContext.durationScale ? ? ? ? ? ?animation.callWithFrameNanos { ? ? ? ? ? ? ? ?lateInitScope = AnimationScope(...).apply { ? ? ? ? ? ? ? ? ? ?// 第一幀 ? ? ? ? ? ? ? ? ? ?doAnimationFrameWithScale(it, durationScale, animation, this@animate, block) ? ? ? ? ? ? ? } ? ? ? ? ? } ? ? ? } else { ? ? ? ? ? ?lateInitScope = AnimationScope(...).apply { ? ? ? ? ? ? ? ?// 第一幀 ? ? ? ? ? ? ? ?doAnimationFrameWithScale() ? ? ? ? ? } ? ? ? } ? ? ? ?// 后續(xù)幀 ? ? ? ?while (lateInitScope!!.isRunning) { ? ? ? ? ? ?val durationScale = coroutineContext.durationScale ? ? ? ? ? ?animation.callWithFrameNanos { ? ? ? ? ? ? ? ?lateInitScope!!.doAnimationFrameWithScale(it, durationScale, animation, this, block) ? ? ? ? ? } ? ? ? } ? ? ? ?// 動畫結(jié)束 ? } catch (e: CancellationException) { ? ? ? ?lateInitScope?.isRunning = false ? ? ? ?if (lateInitScope?.lastFrameTimeNanos == lastFrameTimeNanos) { ? ? ? ? ? ?isRunning = false ? ? ? } ? ? ? ?throw e ? } }
嗯,柳暗花明!這個動畫函數(shù)從頭到尾運行給定 animation
中定義的動畫。在動畫過程中,AnimationState
將被更新為最新的值,速度,幀時間等。
到這里 animateColorAsState
大概過了一遍,但也只是簡單走了一遍流程,并沒有深究里面的細節(jié),比如 Animatable
類中都沒看,runAnimation
方法也只是看了主要的代碼等等。
結(jié)尾
到此這篇關(guān)于Compose 動畫藝術(shù)之屬性動畫探索的文章就介紹到這了,更多相關(guān)Compose屬性動畫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android 基于RecyclerView實現(xiàn)的歌詞滾動自定義控件
這篇文章主要介紹了Android 基于RecyclerView實現(xiàn)的歌詞滾動自定義控件,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03android開機自啟動原理與實現(xiàn)案例(附源碼)
完成一下步驟后,啟動一次程序,完成注冊。等下次手機開機時,該軟件即會自動啟動,具體實現(xiàn)如下,感興趣的朋友可以參考下哈2013-06-06Android ActionBarActivity設(shè)置全屏無標題的方法總結(jié)
這篇文章主要介紹了Android ActionBarActivity設(shè)置全屏無標題的相關(guān)資料,需要的朋友可以參考下2017-07-07Android 通過httppost上傳文本文件到服務(wù)器的實例代碼
這篇文章主要介紹了Android 通過httppost上傳文本文件到服務(wù)器的實例代碼,非常簡單易懂,非常實用,需要的朋友可以參考下2016-08-08Android中GridView和ArrayAdapter用法實例分析
這篇文章主要介紹了Android中GridView和ArrayAdapter用法,結(jié)合實例形式分析了Android中GridView結(jié)合ArrayAdapter實現(xiàn)表格化排版的相關(guān)技巧,需要的朋友可以參考下2016-02-02AndroidStudio 實現(xiàn)加載字體資源的方法
這篇文章主要介紹了AndroidStudio 實現(xiàn)加載字體資源的方法的相關(guān)資料,這里提供了詳細的實現(xiàn)方法,需要的朋友可以參考下2016-11-11