Android Compose 屬性動(dòng)畫(huà)使用探索詳解
前言
Jetpack Compose(簡(jiǎn)稱(chēng) Compose )是 Google 官方推出的基于 Kotlin 語(yǔ)言的 Android 新一代 UI 開(kāi)發(fā)框架,其采用聲明式的 UI 編程特性使得 Android 應(yīng)用界面的編寫(xiě)和維護(hù)變得更加簡(jiǎn)單。
本專(zhuān)欄將詳細(xì)介紹在使用 Compose 進(jìn)行 UI 開(kāi)發(fā)中如何實(shí)現(xiàn)炫酷的動(dòng)畫(huà)效果。動(dòng)畫(huà)效果在 App 使用中至關(guān)重要,它使得 App 的交互更加自然流暢,用戶使用體驗(yàn)更加良好。
在傳統(tǒng)的 Android 開(kāi)發(fā)中有古老的 View 動(dòng)畫(huà)和目前流行的屬性動(dòng)畫(huà),如今 View 動(dòng)畫(huà)幾乎已被廣大開(kāi)發(fā)者所拋棄,屬性動(dòng)畫(huà)因其可以作用于任何對(duì)象的靈活和強(qiáng)大特性而被開(kāi)發(fā)者所擁抱。既然屬性動(dòng)畫(huà)這么強(qiáng)大,那么它是否能用在 Compose 開(kāi)發(fā)中呢?如果能那跟傳統(tǒng) UI 開(kāi)發(fā)中使用又有什么區(qū)別呢?本篇就帶領(lǐng)你來(lái)探索一下在 Compose 中屬性動(dòng)畫(huà)的使用。
使用探索
在傳統(tǒng) Android 開(kāi)發(fā)中,屬性動(dòng)畫(huà)使用得最多的是 ObjectAnimator 和 ValueAnimator,接下來(lái)就探索一下在 Compose 中如何使用它們來(lái)實(shí)現(xiàn)動(dòng)畫(huà)效果。
ObjectAnimator 使用探索
首先看一下在傳統(tǒng) Android 開(kāi)發(fā)中如何使用屬性動(dòng)畫(huà),比如使用屬性動(dòng)畫(huà)實(shí)現(xiàn)豎直方向向下移動(dòng)的動(dòng)畫(huà):
val animator = ObjectAnimator.ofFloat(view, "translationY", 10f, 100f) animator.start()
通過(guò) ObjectAnimator作用于 View 的 translationY屬性,不斷改變 translationY 的值從而實(shí)現(xiàn)動(dòng)畫(huà)效果,一個(gè)很簡(jiǎn)單的屬性動(dòng)畫(huà),這里就不貼運(yùn)行效果了。
那在 Compose 中能否使用 ObjectAnimator 呢?
下面使用 Compose 在界面上顯示一個(gè) 100dp*100dp 的藍(lán)色正方形方塊,代碼如下:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Box(Modifier.padding(start = 10.dp, top = 10.dp)
.size(100.dp)
.background(Color.Blue)
)
}
}
}
運(yùn)行效果如下:

現(xiàn)在要同樣實(shí)現(xiàn)一個(gè)豎直方向移動(dòng)的動(dòng)畫(huà)效果,讓方塊從上往下移動(dòng)。在上面的屬性動(dòng)畫(huà)實(shí)現(xiàn)中 ObjectAnimator是作用于 View 組件上的,按照這個(gè)思路在這里 ObjectAnimator 就應(yīng)該作用于 Box 上,但實(shí)際上我們這里壓根拿不到 Box 的實(shí)例,因?yàn)檫@里的 Box 實(shí)際是一個(gè)函數(shù)且沒(méi)有返回值,看一下 Box 的源碼:
@Composable
fun Box(modifier: Modifier) {
Layout({}, measurePolicy = EmptyBoxMeasurePolicy, modifier = modifier)
}
既然作用于 Box 上不行,那能不能作用于 State 上呢,Compose 是數(shù)據(jù)驅(qū)動(dòng) UI 刷新,通過(guò)數(shù)據(jù)狀態(tài)改變重組 UI 實(shí)現(xiàn)界面的刷新,把上面的 top 提取為一個(gè) State 再通過(guò) ObjectAnimator 去改變是否可行呢?改造代碼實(shí)驗(yàn)一下:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val topPadding:MutableState<Int> = mutableStateOf(10)
val animator = ObjectAnimator.ofInt(topPadding, "value", 10, 100)
animator.duration = 1000
setContent {
Box(Modifier.padding(start = 10.dp, top = topPadding.value.dp)
.size(100.dp)
.background(Color.Blue)
// 添加點(diǎn)擊事件
.clickable {
// 啟動(dòng)動(dòng)畫(huà)
animator.start()
}
)
}
}
}
改造如下:
- 將之前 top 的固定值提取成了一個(gè) State 變量 topPadding,當(dāng) topPadding 的值發(fā)生改變時(shí)會(huì)重組界面從而讓界面刷新
- 聲明了 ObjectAnimator 的 animator 變量,作用于 topPadding 的 value 屬性上,并設(shè)置動(dòng)畫(huà)值從 10 到 100,動(dòng)畫(huà)時(shí)長(zhǎng) 1000ms
- 給 Box 添加點(diǎn)擊監(jiān)聽(tīng)事件啟動(dòng)動(dòng)畫(huà)
實(shí)際上寫(xiě)完這段代碼,編輯器就已經(jīng)有報(bào)錯(cuò)提示了,提示如下:

說(shuō)沒(méi)有找到帶 Int 參數(shù)的 setValue方法,那來(lái)看看 MutableState是否有 setValue 方法:
interface MutableState<T> : State<T> {
override var value: T
operator fun component1(): T
operator fun component2(): (T) -> Unit
}
可以發(fā)現(xiàn) MutableState 中是有一個(gè) var 修飾的 value 變量的,說(shuō)明是有 setValue 方法的,但是錯(cuò)誤提示是找不到帶 Int 參數(shù)的 setValue 方法,實(shí)際上 MutableState 的 setValue 的定義應(yīng)該是這樣的:
fun setValue(value:T){
this.value = value
}
這里參數(shù)類(lèi)型是泛型 T,而 ObjectAnimator 找的是明確的 Int 類(lèi)型參數(shù)的方法,所以找不到。那怎么辦呢?是不是就意味著在 Compose 中無(wú)法使用 ObjectAnimator 了呢?
直接使用確實(shí)是不行,那我們能不能對(duì)其進(jìn)行封裝,不是找不到對(duì)應(yīng)的 setValue 方法嘛,那我封裝一下提供一個(gè) setValue 方法不就行了。定義一個(gè) IntState類(lèi),再提供一個(gè) mutableIntStateOf方法:
class IntState(private val state: MutableState<Int>){
var value : Int = state.value
get() = state.value
set(value) {
field = value
state.value = value
}
}
fun mutableIntStateOf(value: Int, policy: SnapshotMutationPolicy<Int> = structuralEqualityPolicy()) : IntState{
val state = mutableStateOf(value, policy)
return IntState(state)
}
IntState構(gòu)造方法傳入一個(gè) MutableState 類(lèi)型的 state 參數(shù),然后提供一個(gè) value 變量,get 方法返回 state.value ,set 方法將傳入值設(shè)置給 state.value,這樣 IntState 就有了一個(gè)明確的 setValue(value:Int) 的方法。
為了便于使用,封裝一個(gè) mutableIntStateOf方法,實(shí)現(xiàn)里先采用 Compose 提供的 mutableStateOf 方法獲取一個(gè) MutableState ,然后用其構(gòu)建一個(gè) IntState 進(jìn)行返回。
再改造一下上面動(dòng)畫(huà)實(shí)現(xiàn)代碼將 mutableStateOf替換成 mutableIntStateOf:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 替換為 mutableIntStateOf
val topPadding = mutableIntStateOf(10)
// 創(chuàng)建 ObjectAnimator 目標(biāo)為 topPadding,作用屬性為 value,值從 10 變化到 100
val animator = ObjectAnimator.ofInt(topPadding, "value", 10, 100)
// 設(shè)置動(dòng)畫(huà)時(shí)長(zhǎng) 1s
animator.duration = 1000
setContent {
Box(Modifier.padding(start = 10.dp, top = topPadding.value.dp)
.size(100.dp)
.background(Color.Blue)
// 添加點(diǎn)擊事件
.clickable {
// 啟動(dòng)動(dòng)畫(huà)
animator.start()
}
)
}
}
}
現(xiàn)在不報(bào)錯(cuò)了,運(yùn)行一下看看是否有動(dòng)畫(huà)效果:

效果符合預(yù)期,說(shuō)明這種辦法是可行,也說(shuō)明 ObjectAnimator 在 Compose 中也是可以使用的,只是不能像傳統(tǒng) Android 開(kāi)發(fā)那樣直接作用于 View 組件上,而是需要進(jìn)行二次封裝后使用。
ValueAnimator 使用探索
ObjectAnimator 使用探索完了,那么 ValueAnimator能否使用呢?Compose 以聲明式的方式通過(guò)數(shù)據(jù)驅(qū)動(dòng)界面刷新,而ValueAnimator主要用于數(shù)據(jù)的改變,好像很契合的樣子,使用 ValueAnimator 不斷改變 State 的值理論上就可以實(shí)現(xiàn)動(dòng)畫(huà)效果。還是上面的例子,改造成使用 ValueAnimator來(lái)實(shí)現(xiàn):
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 使用 mutableStateOf 創(chuàng)建 topPadding 的 State
var topPadding by mutableStateOf(10)
// 創(chuàng)建 ValueAnimator 從 10 變化到 100
val animator = ValueAnimator.ofInt(10, 100)
// 動(dòng)畫(huà)時(shí)長(zhǎng) 1s
animator.duration = 1000
// 設(shè)置監(jiān)聽(tīng),當(dāng)動(dòng)畫(huà)改變時(shí)動(dòng)態(tài)修改 topPadding 的值
animator.addUpdateListener {
topPadding = it.animatedValue as Int
}
setContent {
Box(Modifier.padding(start = 10.dp, top = topPadding.dp)
.size(100.dp)
.background(Color.Blue)
.clickable {
animator.start()
}
)
}
}
}
是否有效果呢?運(yùn)行一下看看效果:

跟上面使用 ObjectAnimator 實(shí)現(xiàn)的效果一致,說(shuō)明 ValueAnimator 在 Compose 中實(shí)現(xiàn)動(dòng)畫(huà)是可行的,只是需要手動(dòng)去監(jiān)聽(tīng) ValueAnimator 值的變化然后去動(dòng)態(tài)更新 State 的值,稍微麻煩了一點(diǎn),實(shí)際上我們也可以對(duì)其進(jìn)行封裝簡(jiǎn)化其使用。
通過(guò)上面的代碼發(fā)現(xiàn),如果要在 Compose 中使用 ValueAnimator 來(lái)實(shí)現(xiàn)動(dòng)畫(huà),對(duì)動(dòng)畫(huà)數(shù)值的改變進(jìn)行監(jiān)聽(tīng)并動(dòng)態(tài)更新 State 的值是必不可少的一步,那么我們就可以將其提取進(jìn)行封裝。
/**
* @param state 動(dòng)畫(huà)作用的目標(biāo) State
* @param values 動(dòng)畫(huà)的變化值,可變參數(shù)
*/
fun animatorOfInt(state:MutableState<Int>, vararg values: Int) : ValueAnimator{
// 創(chuàng)建 ValueAnimator ,參數(shù)為傳入的 values
val animator = ValueAnimator.ofInt(*values)
// 添加監(jiān)聽(tīng)
animator.addUpdateListener {
// 更新 state 的 value 值
state.value = it.animatedValue as Int
}
return animator
}
然后將上面的創(chuàng)建動(dòng)畫(huà)替換成使用 animatorOfInt 創(chuàng)建:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val topPadding = mutableStateOf(10)
// 使用封裝的 animatorOfInt 方法創(chuàng)建動(dòng)畫(huà)
val animator = animatorOfInt(topPadding, 10, 100)
animator.duration = 1000
setContent {
Box(Modifier.padding(start = 10.dp, top = topPadding.value.dp)
.size(100.dp)
.background(Color.Blue)
.clickable {
animator.start()
}
)
}
}
}
使用是不是要簡(jiǎn)單很多,不需要手動(dòng)去處理動(dòng)畫(huà)值變化的監(jiān)聽(tīng)了,有點(diǎn)使用 ObjectAnimator 的感覺(jué),只是不需要指定目標(biāo)屬性。運(yùn)行效果跟上面一致就不貼圖了。
Compose 函數(shù)中使用屬性動(dòng)畫(huà)
前面在 Compose 中使用的動(dòng)畫(huà)都是創(chuàng)建在 Compose 函數(shù)外面的,如果我們想把這個(gè)組件封裝成一個(gè)獨(dú)立的 Compose 組件就需要將動(dòng)畫(huà)的創(chuàng)建放到 Compose 函數(shù)里面,比如將上面的效果封裝成一個(gè) AnimationBox組件:
@Composable
fun AnimationBox(){
val topPadding = mutableStateOf(10)
val animator = animatorOfInt(topPadding, 10, 100)
animator.duration = 1000
Box(modifier = Modifier.padding(start = 10.dp, top = topPadding.value.dp)
.size(100.dp)
.background(Color.Blue)
.clickable {
animator.start()
})
}
首先 mutableStateOf 會(huì)報(bào)錯(cuò):

意思是在組合過(guò)程中創(chuàng)建 state 需要使用 remember,原因是當(dāng) state 里的值發(fā)生變化時(shí) Compose 會(huì)進(jìn)行重組導(dǎo)致函數(shù)重新執(zhí)行,如果 mutableStateOf 不加 remember則會(huì)每次重組都重新創(chuàng)建 state,導(dǎo)致 UI 上使用的值每次都是初始值而得不到刷新。
既然報(bào)錯(cuò)那就給他加上 remember:
@Composable
fun AnimationBox(){
val topPadding = remember { mutableStateOf(10) }
...
}
然后在使用的地方直接使用 AnimationBox() 即可:
setContent {
AnimationBox()
}
運(yùn)行后發(fā)現(xiàn)效果跟之前一樣,那是不是就可以了呢?
實(shí)際上上面的代碼是還存在問(wèn)題的,前面說(shuō)在 Compose 重組時(shí)會(huì)重新執(zhí)行 Compose 組件的代碼,也就是在界面刷新時(shí)會(huì)多次重復(fù)創(chuàng)建動(dòng)畫(huà)對(duì)象,我們?cè)?animatorOfInt 函數(shù)里添加一個(gè)日志再看看運(yùn)行時(shí)的日志輸出:
fun animatorOfInt(state:MutableState<Int>, vararg values: Int) : ValueAnimator{
println("-------call animatorOfInt--------")
...
}
輸出結(jié)果:
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
I/System.out: -------call animatorOfInt--------
日志確實(shí)輸出了多次,意味著動(dòng)畫(huà)確實(shí)創(chuàng)建了多次,那怎么解決呢?
前面說(shuō)了 remember可以解決重組時(shí)重復(fù)創(chuàng)建的問(wèn)題,所以只需在創(chuàng)建動(dòng)畫(huà)上套上 remember即可,如下:
val animator = remember { animatorOfInt(topPadding, 10, 100) }
修改后再看日志,發(fā)現(xiàn)就只在第一次進(jìn)行了創(chuàng)建,動(dòng)畫(huà)執(zhí)行過(guò)程中并沒(méi)有再次創(chuàng)建。
為了方便使用,可以再封裝一個(gè) rememberAnimatorOfInt方法:
@Composable
fun rememberAnimatorOfInt(state:MutableState<Int>, vararg values: Int) : ValueAnimator{
return remember { animatorOfInt(state, *values) }
}
在 animatorOfInt 上套了一個(gè) remember,這樣使用時(shí)就可以直接使用 rememberAnimatorOfInt 方法:
val animator = rememberAnimatorOfInt(topPadding, 10, 100)
remember 是 Compose 提供的在 Compose 函數(shù)中緩存狀態(tài)的方法,解決在 Compose 重組時(shí)重復(fù)創(chuàng)建的問(wèn)題,關(guān)于 remember 更多使用大家可以自行查詢相關(guān)資料,本專(zhuān)欄主要講解動(dòng)畫(huà)的使用就不過(guò)多贅述。
實(shí)戰(zhàn)
前面介紹了屬性動(dòng)畫(huà)在 Compose 中的運(yùn)用,那在實(shí)際開(kāi)發(fā)中到底好不好用呢?接下來(lái)我們通過(guò)一個(gè)實(shí)例來(lái)看看。
先看一下最終實(shí)現(xiàn)的效果:

一個(gè)上傳按鈕的動(dòng)畫(huà)效果,動(dòng)畫(huà)主要分為三階段:
- 上傳開(kāi)始
- 按鈕從圓角矩形變成圓形
- 按鈕顏色從藍(lán)色變成中間白色,邊框灰色
- 文字逐漸消失
- 上傳進(jìn)度
- 邊框根據(jù)進(jìn)度變?yōu)樗{(lán)色
- 上傳完成
- 按鈕從圓形變成圓角矩形
- 按鈕顏色變成紅色
- 文字逐漸顯示,且文字變?yōu)?“Success”
上傳開(kāi)始動(dòng)畫(huà)
先把按鈕的初始狀態(tài)使用 Compose 實(shí)現(xiàn):
@Composable
fun UploadButton() {
Box(
modifier = Modifier
.padding(start = 10.dp, top = 10.dp)
.width(180.dp),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(24.dp))
.background(Color.Blue)
.size(180.dp, 48.dp),
contentAlignment = Alignment.Center,
) {
Text("Upload", color = Color.White)
}
}
}
運(yùn)行效果如下:

下面就為這按鈕添加動(dòng)畫(huà),前面講了動(dòng)畫(huà)主要作用于 State 上,所以需要先將使用到的數(shù)據(jù)提取成對(duì)應(yīng)的狀態(tài):
@Composable
fun UploadButton() {
val originWidth = 180.dp
val circleSize = 48.dp
var text by remember { mutableStateOf("Upload") }
val textAlpha = remember { mutableStateOf(1.0f) }
val backgroundColor = remember { mutableStateOf(Color.Blue) }
val boxWidth = remember { mutableStateOf(originWidth) }
Box(
modifier = Modifier
.padding(start = 10.dp, top = 10.dp)
.width(originWidth),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(height/2))
.background(backgroundColor.value)
.size(boxWidth.value, height),
contentAlignment = Alignment.Center,
) {
Text(text, color = Color.White, modifier = Modifier.alpha(textAlpha.value))
}
}
}
創(chuàng)建開(kāi)始上傳的動(dòng)畫(huà):
@Composable
fun UploadButton() {
...
val uploadStartAnimator = remember {
// 創(chuàng)建 AnimatorSet
val animatorSet = AnimatorSet()
// 按鈕寬度變化動(dòng)畫(huà)
val widthAnimator = animatorOfDp(boxWidth, arrayOf(originWidth, circleSize))
// 文字消失動(dòng)畫(huà)
val textAnimator = animatorOfFloat(textAlpha, 1f, 0.0f)
// 按鈕顏色動(dòng)畫(huà)
val colorAnimator = animatorOfColor(backgroundColor, arrayOf(Color.Blue, Color.Gray))
// 動(dòng)畫(huà)添加到 AnimatorSet
animatorSet.playTogether(widthAnimator, textAnimator, colorAnimator)
animatorSet
}
Box(...) {
Box(
modifier = Modifier
...
.clickable {
// 點(diǎn)擊執(zhí)行動(dòng)畫(huà)
uploadStartAnimator.start()
},
...
)
}
}
分別創(chuàng)建按鈕寬度、按鈕顏色和文字 alpha 值變化的動(dòng)畫(huà),因需同時(shí)執(zhí)行多個(gè)動(dòng)畫(huà),這里使用 AnimatorSet 進(jìn)行同時(shí)執(zhí)行,然后在按鈕上添加點(diǎn)擊事件進(jìn)行動(dòng)畫(huà)執(zhí)行。
上面的 animatorOfDp、animatorOfFloat、animatorOfColor都是自定義封裝的函數(shù),封裝方法與上面介紹的 animatorOfInt基本相同,源碼可通過(guò)文章最后附的源碼地址進(jìn)行查看。
運(yùn)行效果如下:

好像還差點(diǎn),中間應(yīng)該是白色的,在 Box 下再添加一個(gè)白色圓形的 Box,默認(rèn) alpha 是 0,上傳開(kāi)始時(shí) alpha 從 0 變成 1 :
@Composable
fun UploadButton() {
...
val progressAlpha = remember { mutableStateOf(0.0f) }
val uploadStartAnimator = remember {
...
// 中間白色透明度變化動(dòng)畫(huà)
val centerAlphaAnimator = animatorOfFloat(progressAlpha, 0.0f, 1f)
animatorSet.playTogether(widthAnimator, textAnimator, colorAnimator, centerAlphaAnimator)
animatorSet
}
Box(...) {
Box(...) {
// 白色圓形
Box(
modifier = Modifier.size(40.dp).clip(RoundedCornerShape(20.dp))
.alpha(progressAlpha.value).background(Color.White)
)
Text(text, color = Color.White, modifier = Modifier.alpha(textAlpha.value))
}
}
}
運(yùn)行效果如下:

上傳進(jìn)度動(dòng)畫(huà)
這里通過(guò)自定義 clip 的一個(gè)弧形的 shape 來(lái)實(shí)現(xiàn)進(jìn)度,自定義代碼如下:
class ArcShape(private val progress: Int) : Shape {
override fun createOutline(
size: Size,
layoutDirection: LayoutDirection,
density: Density
): Outline {
val path = Path().apply {
moveTo(size.width / 2f, size.height / 2f)
arcTo(Rect(0f, 0f, size.width, size.height), -90f, progress / 100f * 360f, false)
close()
}
return Outline.Generic(path)
}
}
傳入一個(gè)進(jìn)度值(0-100),然后根據(jù)進(jìn)度值算出一個(gè)繪制的弧度,使用這個(gè)自定義的 ArcShape 代碼如下:
Box(Modifier.size(48.dp).clip(ArcShape(30)).background(Color.Blue))
效果:

所以只需動(dòng)態(tài)改變 ArcShape 的 progress 參數(shù)的值就能實(shí)現(xiàn)上傳進(jìn)度效果,修改代碼如下:
@Composable
fun PreviewUploadButton() {
...
val progress = remember { mutableStateOf(0) }
//上傳進(jìn)度動(dòng)畫(huà)
val progressAnimator = remember {
val animator = animatorOfInt(progress, 0, 100)
animator.duration = 1000
animator
}
val uploadStartAnimator = remember {
...
// 添加動(dòng)畫(huà)監(jiān)聽(tīng),完成后執(zhí)行進(jìn)度動(dòng)畫(huà)
animatorSet.addListener(onEnd = {
progressAnimator.start()
})
animatorSet
}
Box(...) {
Box(...) {
// 進(jìn)度 Box
Box(
modifier = Modifier.size(height).clip(ArcShape(progress.value))
.alpha(progressAlpha.value).background(Color.Blue)
)
...
}
}
}
運(yùn)行效果:

上傳完成動(dòng)畫(huà)
最后是上傳完成動(dòng)畫(huà)就很簡(jiǎn)單了,基本就是開(kāi)始動(dòng)畫(huà)的反向,只是按鈕顏色從藍(lán)色變成了紅色,動(dòng)畫(huà)在上傳進(jìn)度動(dòng)畫(huà)完成時(shí)執(zhí)行:
@Composable
fun PreviewUploadButton() {
...
val endAnimatorSet = remember {
val animatorSet = AnimatorSet()
val widthAnimator = animatorOfDp(boxWidth, arrayOf(circleSize, originWidth))
val centerAnimator = animatorOfFloat(progressAlpha, 1f, 0f)
val textAnimator = animatorOfFloat(textAlpha, 0f, 1f)
val colorAnimator = animatorOfColor(backgroundColor, arrayOf(Color.Blue, Color.Red))
animatorSet.playTogether(widthAnimator, centerAnimator, textAnimator, colorAnimator)
animatorSet.addListener(onStart = {
text = "Success"
})
animatorSet
}
val progressAnimator = remember {
val animator = animatorOfInt(progress, 0, 100)
animator.duration = 1000
animator.addListener(onEnd = {
endAnimatorSet.start()
})
animator
}
...
}
最終效果:

最后
通過(guò)本篇文章的探索可以發(fā)現(xiàn)屬性動(dòng)畫(huà)在 Compose 中確實(shí)是可以使用的,雖然跟傳統(tǒng) UI 開(kāi)發(fā)中使用屬性動(dòng)畫(huà)有所區(qū)別,但確實(shí)能用,而且通過(guò)一個(gè)簡(jiǎn)單的實(shí)戰(zhàn)示例發(fā)現(xiàn)好像還挺好用的。好了,我已經(jīng)學(xué)會(huì) Compose 的動(dòng)畫(huà)開(kāi)發(fā)了,什么?Compose 還單獨(dú)提供了一套動(dòng)畫(huà) API ?
屬性動(dòng)畫(huà)這不是挺好使的么,這不是多此一舉么,難道 Compose 的動(dòng)畫(huà) API 比屬性動(dòng)畫(huà)還好用、還強(qiáng)大?如果感興趣請(qǐng)關(guān)注本專(zhuān)欄,從下一篇開(kāi)始帶你真正走進(jìn) Compose 的動(dòng)畫(huà)世界。
源碼地址:ComposeAnimationDemo
以上就是Android Compose 屬性動(dòng)畫(huà)使用探索詳解的詳細(xì)內(nèi)容,更多關(guān)于Android Compose 屬性動(dòng)畫(huà)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android自定義控件ViewGroup實(shí)現(xiàn)標(biāo)簽云
這篇文章主要為大家詳細(xì)介紹了Android自定義控件ViewGroup實(shí)現(xiàn)標(biāo)簽云,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
Flutter框架解決盒約束widget和assets里加載資產(chǎn)技術(shù)
這篇文章主要為大家介紹了Flutter框架解決盒約束widget和assets里加載資產(chǎn)技術(shù)運(yùn)用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
Android開(kāi)發(fā)返回鍵明暗點(diǎn)擊效果的實(shí)例代碼
這篇文章主要介紹了Android開(kāi)發(fā)返回鍵明暗點(diǎn)擊效果的實(shí)例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
android中Bitmap用法(顯示,保存,縮放,旋轉(zhuǎn))實(shí)例分析
這篇文章主要介紹了android中Bitmap用法,以實(shí)例形式較為詳細(xì)的分析了android中Bitmap操作圖片的顯示、保存、縮放、旋轉(zhuǎn)等相關(guān)技巧,需要的朋友可以參考下2015-09-09
Android訪問(wèn)assets本地json文件的方法
這篇文章主要介紹了Android訪問(wèn)assets本地json文件的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
解決Android 6.0獲取wifi Mac地址為02:00:00:00:00:00問(wèn)題
這篇文章主要介紹了Android 6.0獲取wifi Mac地址為02:00:00:00:00:00的解決方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-11-11
Android仿滴滴出行驗(yàn)證碼輸入框功能實(shí)例代碼
最近項(xiàng)目經(jīng)理交給我們組一個(gè)類(lèi)似滴滴出行填寫(xiě)驗(yàn)證碼的彈框功能,拿到這個(gè)項(xiàng)目需求真是把我忙暈了,下面通過(guò)本文給大家分享Android仿滴滴出行驗(yàn)證碼輸入框功能實(shí)例代碼,需要的朋友參考下吧2017-12-12

