一、簡介
Jetpack Compose是Google推出的用于構(gòu)建原生界面的新Android 工具包燕刻,它可簡化并加快 Android上的界面開發(fā)窥翩。Jetpack Compose是一個聲明式的UI框架,隨著該框架的推出宪哩,標(biāo)志著Android 開始全面擁抱聲明式UI開發(fā)常熙。Jetpack Compose存在很多優(yōu)點:代碼更加簡潔直觀纬乍、應(yīng)用開發(fā)效率顯著提升、Kotlin API功能直觀裸卫、預(yù)覽工具強大等仿贬。
二、開發(fā)環(huán)境
為了獲得更好的開發(fā)體驗墓贿,筆者這里使用的是Android Studio Canary版本茧泪,這樣可以無需配置一些設(shè)置和依賴。(下載地址)
打開工程聋袋,新建Empty Compose activity 模版队伟,需要注意的是根目錄下的build.gradle,相關(guān)的依賴com.android.tools.build和org.jetbrains.kotlin版本需要對應(yīng)舱馅,否則可能出現(xiàn)出錯的情形,這里使用的是:
dependencies {
classpath "com.android.tools.build:gradle:7.0.0-alpha15"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30"
}
這樣就完成了項目的新建刀荒。
三代嗤、Jetpack Compose動畫
Jetpack Compose提供了一些功能強大且可擴展的 API,可用于在應(yīng)用界面中輕松實現(xiàn)各種動畫效果缠借。下文將會對Jetpack Compose Animations的常用方法進行介紹干毅。
3.1 狀態(tài)驅(qū)動動畫:State
Jetpack Compose動畫是通過對狀態(tài)的監(jiān)聽,即監(jiān)聽狀態(tài)值的變化泼返,使UI能實現(xiàn)自動更新硝逢。可組合函數(shù)可以使用 remember或者 mutableStateOf監(jiān)聽狀態(tài)值的變化。如果狀態(tài)值是不變的渠鸽,remember函數(shù)會在每次重新組合中保持該值叫乌;如果狀態(tài)是可變的,它會在值發(fā)生變化的時候觸發(fā)重組徽缚,mutableStateOf將得到一個MutableState對象憨奸,它是一個可觀察類型。
這種重組是創(chuàng)建狀態(tài)驅(qū)動動畫的關(guān)鍵凿试。利用重組排宰,它們會在可組合組件的狀態(tài)發(fā)生任何變化時被觸發(fā)。Compose動畫是由State驅(qū)動的那婉,動畫相關(guān)的API也較容易上手板甘,能比較容易創(chuàng)造出漂亮的聲明式動畫。
3.2 可見性動畫: AnimatedVisibility
首先看下函數(shù)定義:
@ExperimentalAnimationApi
@Composable
fun AnimatedVisibility(
visible: Boolean,
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandIn(),
exit: ExitTransition = shrinkOut() + fadeOut(),
initiallyVisible: Boolean = visible,
content: @Composable () -> Unit
) {
AnimatedVisibilityImpl(visible, modifier, enter, exit, initiallyVisible, content)
}
可以看出默認(rèn)的動畫是淡入放大详炬、淡出收縮盐类,實際中通過傳入不同函數(shù)實現(xiàn)各種動效。
隨著可見值的變化痕寓,AnimatedVisibility可為其內(nèi)容的出現(xiàn)和消失設(shè)置動畫傲醉。如下代碼,可以通過點擊Button呻率,控制圖片的出現(xiàn)和消失硬毕。
@Composable
fun AinmationDemo() {
//AnimatedVisibility 可見動畫
var visible by remember { mutableStateOf(true) }
Column(
Modifier
.fillMaxWidth()
.fillMaxHeight(),
Arrangement.Top,
Alignment.CenterHorizontally
) {
Button(
onClick = { visible = !visible }
) {
Text(text = if (visible) "Hide" else "Show")
}
Spacer(Modifier.height(16.dp))
AnimatedVisibility(
visible = visible,
enter = slideInVertically() + fadeIn(),
exit = slideOutVertically() + fadeOut()
) {
Image(
painter = painterResource(id = R.drawable.pikaqiu),
contentDescription = null,
Modifier.fillMaxSize()
)
}
}
}
通過監(jiān)聽visible的變化,可實現(xiàn)圖片的可見性動畫礼仗,效果如小圖所示吐咳;
3.3 布局大小動畫:AnimateContentSize
先看下函數(shù)的定義:
fun Modifier.animateContentSize(
animationSpec: FiniteAnimationSpec<IntSize> = spring(),
finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null
)
可以為布局大小動畫設(shè)置動畫速度和監(jiān)聽值。
由函數(shù)的定義可以看出這個函數(shù)本質(zhì)上就Modefier的一個擴展函數(shù)元践【录梗可以通過變量size監(jiān)聽狀態(tài)變化實現(xiàn)布局大小的動畫效果,代碼如下:
//放大縮小動畫 animateContentSize
var size by remember { mutableStateOf(Size(300F, 300F)) }
Column(
Modifier
.fillMaxWidth()
.fillMaxHeight(),
Arrangement.Top,
Alignment.CenterHorizontally
) {
Spacer(Modifier.height(16.dp))
Button(
onClick = {
size = if (size.height == 300F) {
Size(500F, 500F)
} else {
Size(300F, 300F)
}
}
) {
Text(if (size.height == 300F) "Shrink" else "Expand")
}
Spacer(Modifier.height(16.dp))
Box(
Modifier
.animateContentSize()
) {
Image(
painter = painterResource(id = R.drawable.pikaqiu),
contentDescription = null,
Modifier
.animateContentSize()
.size(size = size.height.dp)
)
}
} //放大縮小動畫 animateContentSize var size by remember { mutableStateOf(Size(300F, 300F)) } Column( Modifier .fillMaxWidth() .fillMaxHeight(), Arrangement.Top, Alignment.CenterHorizontally ) { Spacer(Modifier.height(16.dp)) Button( onClick = { size = if (size.height == 300F) { Size(500F, 500F) } else { Size(300F, 300F) } } ) { Text(if (size.height == 300F) "Shrink" else "Expand") } Spacer(Modifier.height(16.dp)) Box( Modifier .animateContentSize() ) { Image( painter = painterResource(id = R.drawable.pikaqiu), contentDescription = null, Modifier .animateContentSize() .size(size = size.height.dp) ) }}
通過Button的點擊单旁,監(jiān)聽size值的變化沪羔,利用animateContentSize()實現(xiàn)動畫效果,具體動效如下圖所示:
3.4布局切換動畫: Crossfade
Crossfade可以通過監(jiān)聽狀態(tài)值的變化象浑,使用淡入淡出的動畫在兩個布局之間添加動畫效果蔫饰,函數(shù)自身就是一個Composable,代碼如下:
//Crossfade 淡入淡出動畫
var fadeStatus by remember { mutableStateOf(true) }
Column(
Modifier
.fillMaxWidth()
.fillMaxHeight(),
Arrangement.Top,
Alignment.CenterHorizontally
) {
Button(
onClick = { fadeStatus = !fadeStatus }
) {
Text(text = if (fadeStatus) "Fade In" else "Fade Out")
}
Spacer(Modifier.height(16.dp))
Crossfade(targetState = fadeStatus, animationSpec = tween(3000)) { screen ->
when (screen) {
true -> Image(
painter = painterResource(id = R.drawable.pikaqiu),
contentDescription = null,
Modifier
.animateContentSize()
.size(300.dp)
)
false -> Image(
painter = painterResource(id = R.drawable.pikaqiu2),
contentDescription = null,
Modifier
.animateContentSize()
.size(300.dp)
)
}
}
}
同樣通過監(jiān)聽fadeStatus的值愉豺,實現(xiàn)布局切換的動畫篓吁,具體的動效如圖所示:
3.5單個值動畫:animate*AsState
為單個值添加動畫效果。只需提供結(jié)束值(或目標(biāo)值)蚪拦,該 API 就會從當(dāng)前值開始向指定值播放動畫杖剪。
Jetpack Compose 提供了很多內(nèi)置函數(shù)冻押,可以為不同類型的數(shù)據(jù)制作動畫,例如:animateColorAsState盛嘿、animateDpAsState洛巢、animateOffsetAsState等,這里將介紹下animateFooAsState的使用孩擂,代碼如下:
//animate*AsState 單個值添加動畫
var transparent by remember { mutableStateOf(true) }
val alpha: Float by animateFloatAsState(if (transparent) 1f else 0.5f)
Column(
Modifier
.fillMaxWidth()
.fillMaxHeight(),
Arrangement.Top,
Alignment.CenterHorizontally
) {
Button(
onClick = { transparent = !transparent }
) {
Text(if (transparent) "Light" else "Dark")
}
Spacer(Modifier.height(16.dp))
Box {
Image(
painter = painterResource(id = R.drawable.pikaqiu),
contentDescription = null,
Modifier
.animateContentSize()
.graphicsLayer(alpha = alpha)
.size(300.dp)
)
}
}
動畫效果如下圖所示:
3.6 組合動畫:updateTransition
Transition 可同時追蹤一個或多個動畫狼渊,并在多個狀態(tài)之間同步這些動畫。具體的代碼如下:
var imagePosition by remember { mutableStateOf(ImagePosition.TopLeft) }
Column(
Modifier
.fillMaxWidth()
.fillMaxHeight(),
Arrangement.Top,
Alignment.CenterHorizontally
) {
Spacer(Modifier.height(16.dp))
val transition = updateTransition(targetState = imagePosition, label = "")
val boxOffset by transition.animateOffset(label = "") { position ->
when (position) {
ImagePosition.TopLeft -> Offset(-60F, 0F)
ImagePosition.BottomRight -> Offset(60F, 120F)
ImagePosition.TopRight -> Offset(60F, 0F)
ImagePosition.BottomLeft -> Offset(-60F, 120F)
}
}
Button(onClick = {
imagePosition = ChangePosition(imagePosition)
}) {
Text("Change position")
}
Box {
Image(
painter = painterResource(id = R.drawable.pikaqiu),
contentDescription = null,
Modifier
.offset(boxOffset.x.dp, boxOffset.y.dp)
.animateContentSize()
.size(300.dp)
)
}
}
其中类垦,ImagePosition狈邑、ChangePosition分別為定義的枚舉類、自定義函數(shù)蚤认。
enum class ImagePosition {
TopRight,
TopLeft,
BottomRight,
BottomLeft
}
fun ChangePosition(position: ImagePosition) =
when (position) {
ImagePosition.TopLeft -> ImagePosition.BottomRight
ImagePosition.BottomRight -> ImagePosition.TopRight
ImagePosition.TopRight -> ImagePosition.BottomLeft
ImagePosition.BottomLeft -> ImagePosition.TopLeft
}
動畫的如下圖所示:
四米苹、結(jié)語
Jetpack Compose 已將動畫簡化到只需在我們的可組合函數(shù)中創(chuàng)建聲明性代碼的程度,只需編寫希望 UI 動畫的方式砰琢,其余部分由 Compose 管理蘸嘶。最后,這也是是 Jetpack Compose 的主要目標(biāo):創(chuàng)建一個聲明式 UI 工具包來加速應(yīng)用程序開發(fā)并提高代碼可讀性和邏輯性陪汽。
Jetpack Compose提供的聲明式UI工具包训唱,能做到使用更少的代碼實現(xiàn)更多的功能,且代碼的可讀性和邏輯性也大大提高了挚冤。
作者:vivo互聯(lián)網(wǎng)游戲客戶端團隊-Ke Jie