網站首頁 編程語言 正文
前言
本篇文章是此專欄的第三篇文章,如果想閱讀前兩篇文章的話請點擊下方鏈接:
- Compose?動畫藝術探索之可見性動畫示例詳解
- Compose開發之動畫藝術探索及實現示例
Compose的屬性動畫
屬性動畫是通過不斷地修改值來實現的,而初始值和結束值之間的過渡動畫就需要來計算了。在?Compose
?中為我們提供了一整套 api 來實現屬性動畫,具體有哪些呢?讓我們一起來看下吧!
官方為我們提供了上圖這十種方法,我們可以根據實際項目中的需求進行挑選使用。
在第一篇文章中也提到了?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 ? ) }
可以看到一共接收四個參數,來分別看下代表什么吧:
- targetValue:顧名思義,目標值,這里對應的就是想要轉換成的顏色
- animationSpec:動畫規格,動畫隨著時間改變值的一種規格吧,上一篇文章中也提到了,但由于上一篇文章主要內容并不是這個,也就沒有講,本篇文章會詳細說明
- label:標簽,以區別于其他動畫
- finishedListener:在動畫完成時會進行回調
參數并不算多,而且有三個是可選參數,也就只有?targetValue
?必須要進行設置。方法體內只通過?Color.colorSpace
?強轉構建了一個?TwoWayConverter
?。
前面說過,大多數?Compose
?動畫 API 支持將?Float
、Color
、Dp
?以及其他基本數據類型作為 開箱即用的動畫值,但有時我們需要為其他數據類型(比如自定義類型)添加動畫效果。在動畫播放期間,任何動畫值都表示為?AnimationVector
。使用相應的?TwoWayConverter
?即可將值轉換為?AnimationVector
,反之亦然,這樣一來,核心動畫系統就可以統一對其進行處理了。由于顏色有 argb,所以構建的時候使用的是?AnimationVector4D
?,來看下吧:
val Color.Companion.VectorConverter: ? (colorSpace: ColorSpace) -> TwoWayConverter<Color, AnimationVector4D> ? ? ? ?get() = ColorToVector
如果按照我之前的習慣肯定要接著看?animateValueAsState
?方法內部的代碼了,但今天等會再看!再來看看?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 ? ) }
發現了點什么沒有,參數基本一摸一樣,別著急,咱們再看看別的!
@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
?來看吧!
上面的說法其實是不對的,并不是有十種,而是九種,因為九種都調用了?animateValueAsState
?,其實也可以說有無數種,因為可以自定義。。。。
參數
下面先來看下?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>
來看看接收的參數吧,可以發現有兩個參數沒有見過:
-
typeConverter:類型轉換器,將需要的類型轉換為?
AnimationVector
- visibilityThreshold:一個可選的閾值,用于定義何時動畫值可以被認為足夠接近targetValue以結束動畫
OK,剩下的參數在上面都介紹過,就不重復進行介紹了。
方法體
由于?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) ? ? ? ? ? } ? ? ? } ? } }
可以看到首先構建了一個?Animatable
?,然后記錄了完成回調,又記錄了?AnimationSpec
?,之后有個判斷,如果?visibilityThreshold
?不為空并且?AnimationSpec
?為?SpringSpec
?的時候為新構建的一個?AnimationSpec
?,反之則還是傳進來的?AnimationSpec
?。
那?Animatable
?是個啥呢?它是一個值容器,它可以在通過?animateTo
?更改值時為值添加動畫效果,它可確保一致的連續性和互斥性,這意味著值變化始終是連續的,并且會取消任何正在播放的動畫。Animatable
?的許多功能(包括?animateTo
)以掛起函數的形式提供,所以需要封裝在適當的協程作用域內,所以下面使用了?LaunchedEffect
?來包裹執行?animateTo
?方法,最后調用了動畫完成的回調。
由于?Animatable
?類中代碼比較多,先來看下類的初始化及構造方法吧!
class Animatable<T, V : AnimationVector>( ? ?initialValue: T, ? ?val typeConverter: TwoWayConverter<T, V>, ? ?private val visibilityThreshold: T? = null, ? ?val label: String = "Animatable" )
可以看到這里使用到的參數在?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
?使用傳進來的參數構建了一個?TargetBasedAnimation
?,這是一個方便的動畫包裝類,適用于所有基于目標的動畫,即具有預定義結束值的動畫。然后返回調用了?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
?在動畫結尾包含關于動畫的信息,endState
?捕獲動畫在最后一幀的值?evelocityframe time
?等。它可以用于啟動另一個動畫以從先前中斷的動畫繼續速度。endReason
?描述動畫結束的原因。
下面看下?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
?,這個是關鍵,來看下!
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() ? ? ? ? ? } ? ? ? } ? ? ? ?// 后續幀 ? ? ? ?while (lateInitScope!!.isRunning) { ? ? ? ? ? ?val durationScale = coroutineContext.durationScale ? ? ? ? ? ?animation.callWithFrameNanos { ? ? ? ? ? ? ? ?lateInitScope!!.doAnimationFrameWithScale(it, durationScale, animation, this, block) ? ? ? ? ? } ? ? ? } ? ? ? ?// 動畫結束 ? } catch (e: CancellationException) { ? ? ? ?lateInitScope?.isRunning = false ? ? ? ?if (lateInitScope?.lastFrameTimeNanos == lastFrameTimeNanos) { ? ? ? ? ? ?isRunning = false ? ? ? } ? ? ? ?throw e ? } }
嗯,柳暗花明!這個動畫函數從頭到尾運行給定?animation
?中定義的動畫。在動畫過程中,AnimationState
?將被更新為最新的值,速度,幀時間等。
到這里?animateColorAsState
?大概過了一遍,但也只是簡單走了一遍流程,并沒有深究里面的細節,比如?Animatable
?類中都沒看,runAnimation
?方法也只是看了主要的代碼等等。
結尾
原文鏈接:https://juejin.cn/post/7148631147705008142
相關推薦
- 2023-01-29 Python操作lxml庫實戰之Xpath篇_python
- 2022-11-14 Spring中存取Bean對象的相關注解
- 2023-07-26 node中的內置模塊path和fs
- 2023-04-24 Android布局控件View?ViewRootImpl?WindowManagerService關
- 2021-12-05 密碼學之apache部署https介紹_Linux
- 2022-06-12 Android開發之保存圖片到相冊的三種方法詳解_Android
- 2022-02-27 Spring Boot -- 創建工程時 Spring Initializr 報錯 Error:co
- 2022-08-27 python?中defaultdict()對字典進行初始化的用法介紹_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支