Android中分析Jetpack?Compose動(dòng)畫(huà)內(nèi)部的實(shí)現(xiàn)原理
前言
Compose的動(dòng)畫(huà)Api用起來(lái)很簡(jiǎn)單,效果看起來(lái)很神奇,那么它內(nèi)部到底是如何運(yùn)轉(zhuǎn)的呢?
使用動(dòng)畫(huà)的代碼示例:
var isOffset by remember { mutableStateOf(false) } val offsetAnimation by animateDpAsState(targetValue = if (isOffset) 100.dp else 0.dp) Button( onClick = { isOffset = !isOffset }, modifier = Modifier.offset(0.dp, offsetAnimation) ) { Text(text = "點(diǎn)我進(jìn)行位移") }
看到有一個(gè)Boolean類(lèi)型的isOffset狀態(tài),控制著offsetAnimation動(dòng)畫(huà),然后動(dòng)畫(huà)又控制著B(niǎo)utton的offset,最終實(shí)現(xiàn)了動(dòng)畫(huà)效果
正文
我們主要就看一下animateDpAsState(animate*AsState)做了什么
跟一下animateDpAsState最后會(huì)走進(jìn)animateValueAsState方法中:
方法內(nèi)部創(chuàng)建了一個(gè)Animatable動(dòng)畫(huà)類(lèi)
然后我們跟著上圖的箭頭看,在targetValue發(fā)生變化后,會(huì)通過(guò)channel來(lái)發(fā)送targetValue值的變化,然后launch啟動(dòng)一個(gè)協(xié)程,并在其中調(diào)用了animatable的animateTo方法
接著我們就看看Animatable類(lèi)是如何做動(dòng)畫(huà)的:
主要就是內(nèi)部持有一個(gè)AnimationState類(lèi)型(State)的internalState 變量,然后我們接著上面的代碼跟一下animateTo方法:
沒(méi)什么好說(shuō)的,包了一下targetValue,接著就調(diào)用了runAnimation方法,接著往下跟(截不下分成兩張圖):
主要邏輯就是endState.animate()
endState就是copy的Animatable中的AnimationState對(duì)象internalState,也就是動(dòng)畫(huà)的初始值
然后animation就是在animateTo方法中包裝了動(dòng)畫(huà)目標(biāo)值的對(duì)象
接著跟animate方法:
這個(gè)方法主要分為兩部分,第一部分就是構(gòu)建了一個(gè)AnimationScope,一個(gè)數(shù)據(jù)結(jié)構(gòu),用來(lái)存放動(dòng)畫(huà)需要的一些信息
第二個(gè)部分就是真正動(dòng)畫(huà)計(jì)算和生效的地方,doAnimationFrame
首先會(huì)在第一次創(chuàng)建AnimationScope的時(shí)候執(zhí)行一次(或再一下幀執(zhí)行一次,通過(guò)callWithFrameNanos方法)
然后會(huì)判斷如果動(dòng)畫(huà)還未執(zhí)行完畢,就一直循環(huán)(while),一幀一幀執(zhí)行doAnimationFrame計(jì)算動(dòng)畫(huà)的值
doAnimationFrame方法代碼:
其中通過(guò)時(shí)間等參數(shù)計(jì)算當(dāng)前動(dòng)畫(huà)應(yīng)該設(shè)置的值,包括lastFrameTimeNanos和value等屬性,然后再最后調(diào)用updateState方法去將AnimationScope的值更新到AnimationState中
updatState方法代碼:
這個(gè)state對(duì)象其實(shí)也就是animateDpAsState中的Animatable動(dòng)畫(huà)類(lèi)中的AnimationState對(duì)象internalState
所以上面其實(shí)就是Compose中動(dòng)畫(huà)的簡(jiǎn)化流程
總結(jié)
由于AnimationState是一個(gè)State,在Compose中使用會(huì)自動(dòng)監(jiān)聽(tīng)其變化,只要其value變化了,就會(huì)導(dǎo)致相應(yīng)位置重組,然后Composable就會(huì)使用新的值來(lái)展示不同的效果,
比如最開(kāi)始的示例代碼:
var isOffset by remember { mutableStateOf(false) } val offsetAnimation by animateDpAsState(targetValue = if (isOffset) 100.dp else 0.dp) Button( onClick = { isOffset = !isOffset }, modifier = Modifier.offset(0.dp, offsetAnimation) ) { Text(text = "點(diǎn)我進(jìn)行位移") }
在修改了isOffset后,animateDpAsState中的值就會(huì)因?yàn)閯?dòng)畫(huà)的計(jì)算和修改內(nèi)部state的value,導(dǎo)致Button的offset函數(shù)一直被重新調(diào)用,使Button不停的向下移動(dòng)
其實(shí)Compose中的動(dòng)畫(huà)如果不考慮那么多東西的話,可以簡(jiǎn)化為如下代碼:
/** * creator: lt * effect : 自定義的動(dòng)畫(huà)播放器,邏輯更簡(jiǎn)單 * warning: * [initialValueWithState]動(dòng)畫(huà)要改變的狀態(tài),起始動(dòng)畫(huà)值為其value值 * [targetValue]要通過(guò)動(dòng)畫(huà)轉(zhuǎn)化到的目標(biāo)值 * [duration]動(dòng)畫(huà)的持續(xù)時(shí)間 */ @OptIn(ExperimentalComposeApi::class) suspend fun animateWithFloat( initialValueWithState: MutableState<Float>, targetValue: Float, duration: Int = AnimationConstants.DefaultDurationMillis, ) { //動(dòng)畫(huà)起始值,目標(biāo)差值 val startValue = initialValueWithState.value val valueToBeTransformed = targetValue - startValue //動(dòng)畫(huà)起始時(shí)間,持續(xù)時(shí)間 val startTime = System.nanoTime() val duration = duration * 1000000L //通過(guò)循環(huán)在下一幀計(jì)算動(dòng)畫(huà)的值 val frameClock = coroutineContext.monotonicFrameClock while (System.nanoTime() <= startTime + duration) { frameClock.withFrameNanos { //計(jì)算動(dòng)畫(huà)的值,并設(shè)置值給狀態(tài) val progress = minOf(it - startTime, duration).toFloat() / duration val increase = progress * valueToBeTransformed initialValueWithState.value = startValue + increase } } }
使用方式如下,效果跟示例差不多:
ps:不建議線上項(xiàng)目用這個(gè)api,還是用系統(tǒng)的比較好,如果想使用也可以參考(歡迎star): ComposeViews/MAnimator.kt at main · ltttttttttttt/ComposeViews (github.com)
到此這篇關(guān)于Android中分析Jetpack Compose動(dòng)畫(huà)內(nèi)部的實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Android Jetpack Compose內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 融會(huì)貫通Android?Jetpack?Compose中的Snackbar
- Android?Jetpack?Compose開(kāi)發(fā)實(shí)用小技巧
- Android開(kāi)發(fā)Jetpack?Compose元素Modifier特性詳解
- Android JetpackCompose使用教程講解
- Android?Jetpack結(jié)構(gòu)運(yùn)用Compose實(shí)現(xiàn)微博長(zhǎng)按點(diǎn)贊彩虹效果
- Android Jetpack Compose實(shí)現(xiàn)列表吸頂效果
- Android Jetpack Compose無(wú)限加載列表
- Android使用Jetpack Compose開(kāi)發(fā)零基礎(chǔ)起步教程
相關(guān)文章
Android實(shí)現(xiàn)頂部底部雙導(dǎo)航界面功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)頂部\底部雙導(dǎo)航界面功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09android listview初步學(xué)習(xí)實(shí)例代碼
這篇文章主要介紹了android listview初步學(xué)習(xí)實(shí)例代碼,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01Android中Retrofit 2.0直接使用JSON進(jìn)行數(shù)據(jù)交互
本篇文章主要介紹了Android中Retrofit 2.0直接使用JSON進(jìn)行數(shù)據(jù)交互,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08Android實(shí)現(xiàn)圓角ListView效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)圓角ListView效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10Android listView 繪制表格實(shí)例詳解
這篇文章主要介紹了Android listView 繪制表格實(shí)例詳解的相關(guān)資料,這里附有實(shí)例代碼及實(shí)現(xiàn)效果圖,利用listView 繪制表格提供實(shí)現(xiàn)思路,需要的朋友可以參考下2017-01-01Android布局控件View?ViewRootImpl?WindowManagerService關(guān)系
這篇文章主要為大家介紹了Android布局控件View?ViewRootImpl?WindowManagerService關(guān)系示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02AndroidStudio3 支持 Java8 了請(qǐng)問(wèn)你敢用嗎
Google 發(fā)布了 AS 3.0,以及一系列的 Support 包,有意思的新東西挺多,AS3里面有一個(gè)亮眼的特性就是支持J8。接下來(lái)通過(guò)本文給大家分享AndroidStudio3 支持 Java8 的相關(guān)內(nèi)容,感興趣的朋友一起看看吧2017-11-11