日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Compose?動畫藝術之屬性動畫探索_Android

作者:Zhujiang ? 更新時間: 2022-11-19 編程語言

前言

本篇文章是此專欄的第三篇文章,如果想閱讀前兩篇文章的話請點擊下方鏈接:

  • 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 支持將?FloatColorDp?以及其他基本數據類型作為 開箱即用的動畫值,但有時我們需要為其他數據類型(比如自定義類型)添加動畫效果。在動畫播放期間,任何動畫值都表示為?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

欄目分類
最近更新