? ? ? ? 這一節主要了解一下Compose的AnimationVisibility,AnimatedVisibility 是 Jetpack Compose 里用于實現組件可見性動畫效果的組件,借助它能讓組件在顯示和隱藏時帶有平滑的過渡動畫,從而提升用戶體驗。現總結如下:
API
1. visible
含義:這是一個布爾類型的參數,用于控制組件的可見性。當 visible為true時,組件會顯示;當visible為false時,組件會隱藏。
作用:它是控制組件顯示與隱藏的核心參數,配合動畫效果可以實現平滑的過渡。
2. enter
含義:指定組件進入(顯示)時的動畫效果。enter參數接收一個EnterTransition類型的值,Compose提供了多種內置的進入動畫,如fadeIn()、slideInHorizontally()等,也可以自定義進入動畫。
作用:讓組件在顯示時帶有動畫效果,增強界面的動態感和視覺效果。
3. exit
含義:指定組件退出(隱藏)時的動畫效果。exit參數接收一個ExitTransition 類型的值,Compose提供了多種內置的退出動畫,如fadeOut()、slideOutHorizontally()等,也可以自定義退出動畫。
作用:讓組件在隱藏時帶有動畫效果,使界面的變化更加平滑自然。
4. initiallyVisible
含義:這是一個布爾類型的可選參數,用于設置組件的初始可見性。默認值為true,即組件在初始狀態下是可見的。
作用:方便在組件首次顯示時就設置其可見性,結合動畫效果可以實現更豐富的界面展示。
5. content
含義:它是一個@Composable函數,用于定義要顯示或隱藏的組件內容。
作用:將需要進行可見性動畫的組件包裹在content函數中,以便應用動畫效果。
常見動畫類型:
1. 淡入動畫(fadeIn)
?讓組件在顯示時從透明逐漸變為不透明,實現淡入效果。
2 淡出動畫(fadeOut)
?讓組件在隱藏時從不透明逐漸變為透明,實現淡出效果。
3 滑入動畫(slideIn)
?slideInHorizontally:使組件從水平方向滑入屏幕。
4 滑出動畫(slideOut)
slideOutHorizontally:使組件從水平方向滑出屏幕。
5. 縮放進入動畫(scaleIn)
?讓組件在顯示時從較小的尺寸逐漸放大到正常尺寸。
6. 縮放退出動畫(scaleOut)
讓組件在隱藏時從正常尺寸逐漸縮小。
栗子:
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp@Composable
fun TestAnimatedVisibilityOne() {var isCardVisible by remember { mutableStateOf(false) }var isTextVisible by remember { mutableStateOf(false) }Column(modifier = Modifier.fillMaxSize(),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally) {Row(modifier = Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceEvenly) {Button(onClick = { isCardVisible = !isCardVisible }) {Text(text = if (isCardVisible) "隱藏卡片" else "顯示卡片")}Button(onClick = { isTextVisible = !isTextVisible }) {Text(text = if (isTextVisible) "隱藏文本" else "顯示文本")}}Spacer(modifier = Modifier.height(20.dp))AnimatedVisibility(visible = isCardVisible,enter = slideInHorizontally(initialOffsetX = { -it },animationSpec = tween(durationMillis = 500)) + fadeIn(animationSpec = tween(durationMillis = 500)),exit = slideOutHorizontally(targetOffsetX = { -it },animationSpec = tween(durationMillis = 500)) + fadeOut(animationSpec = tween(durationMillis = 500))) {Card(modifier = Modifier.fillMaxWidth().padding(16.dp)) {Text(text = "這是一個卡片組件",modifier = Modifier.padding(16.dp))}}Spacer(modifier = Modifier.height(20.dp))AnimatedVisibility(visible = isTextVisible,enter = slideInHorizontally(initialOffsetX = { it },animationSpec = tween(durationMillis = 500)) + fadeIn(animationSpec = tween(durationMillis = 500)),exit = slideOutHorizontally(targetOffsetX = { it },animationSpec = tween(durationMillis = 500)) + fadeOut(animationSpec = tween(durationMillis = 500))) {Box(modifier = Modifier.fillMaxWidth().padding(16.dp),contentAlignment = Alignment.Center) {Text(text = "這是一個文本組件")}}}
}
分析:主要演示當點擊按鈕時,卡片和文本會以不同的動畫效果顯示或隱藏。
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp@Composable
fun TestAnimatedVisibility() {val items = remember {mutableStateListOf("Item 1", "Item 2", "Item 3", "Item 4", "Item 5")}val expandedIndices = remember { mutableStateListOf<Int>() }LazyColumn(modifier = Modifier.fillMaxSize(),verticalArrangement = Arrangement.spacedBy(16.dp),contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp)) {itemsIndexed(items) { index, item ->Card(modifier = Modifier.fillMaxWidth()) {Column(modifier = Modifier.padding(16.dp)) {Button(onClick = {if (expandedIndices.contains(index)) {expandedIndices.remove(index)} else {expandedIndices.add(index)}},modifier = Modifier.fillMaxWidth()) {Text(text = item)}AnimatedVisibility(visible = expandedIndices.contains(index),enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(initialAlpha = 0.3f),exit = shrinkVertically() + fadeOut()) {Text(text = "這是 $item 的詳細信息",modifier = Modifier.padding(top = 8.dp))}}}}}
}
分析: AnimatedVisibility 與列表結合使用。當點擊列表項時,該項的詳細信息會以動畫形式展開或收縮。
注意:
1 狀態管理不當引發的問題,如果沒有正確管理 AnimatedVisibility 的可見性狀態,可能會出現動畫不按預期執行,或者在重組時狀態丟失的情況。
2 動畫配置沖突 當組合多個動畫時,如果動畫的配置相互沖突,可能會導致動畫效果不符合預期,甚至出現卡頓現象。
3 動畫完成回調缺失,AnimatedVisibility 本身沒有直接提供動畫完成的回調,若需要在動畫完成后執行特定操作,會比較麻煩,可以借助 LaunchedEffect 和 AnimatedVisibilityState 來監聽動畫的完成狀態。
源碼:
@Composable
fun AnimatedVisibility(visible: Boolean,modifier: Modifier = Modifier,enter: EnterTransition = fadeIn() + expandVertically(),exit: ExitTransition = shrinkVertically() + fadeOut(),initiallyVisible: Boolean = true,content: @Composable () -> Unit
) {// 管理可見性狀態val visibilityState = rememberAnimatedVisibilityState(initiallyVisible = initiallyVisible,visible = visible)// 應用動畫AnimatedVisibilityImpl(visibilityState = visibilityState,modifier = modifier,enter = enter,exit = exit,content = content)
}
分析:
visible:控制組件的可見性。
enter和exit:分別指定進入和退出動畫。
initiallyVisible:設置組件的初始可見性。
content:要顯示或隱藏的組件內容。
調用rememberAnimatedVisibilityState函數創建并管理可見性狀態。
調用AnimatedVisibilityImpl 函數應用動畫。
@Composable
fun rememberAnimatedVisibilityState(initiallyVisible: Boolean,visible: Boolean
): AnimatedVisibilityState {val state = remember { AnimatedVisibilityState(initiallyVisible) }LaunchedEffect(visible) {if (visible) {state.show()} else {state.hide()}}return state
}
分析:
使用 remember 函數創建并存儲 AnimatedVisibilityState 實例。
通過 LaunchedEffect監聽visible參數的變化,當visible改變時,調用state.show()或state.hide()方法觸發動畫。
class AnimatedVisibilityState(initiallyVisible: Boolean) {var targetVisibility by mutableStateOf(initiallyVisible)var isVisible: Booleanget() = targetVisibility || isAnimationRunningprivate set(_) {}var isAnimationRunning by mutableStateOf(false)suspend fun show() {if (targetVisibility) returntargetVisibility = trueisAnimationRunning = truetry {// 執行進入動畫// 這里會根據具體的 EnterTransition 執行動畫邏輯} finally {isAnimationRunning = false}}suspend fun hide() {if (!targetVisibility) returntargetVisibility = falseisAnimationRunning = truetry {// 執行退出動畫// 這里會根據具體的 ExitTransition 執行動畫邏輯} finally {isAnimationRunning = false}}
}
分析:
targetVisibility:存儲目標可見性狀態。
isVisible:表示組件當前是否可見,考慮了動畫執行狀態。
isAnimationRunning:記錄動畫是否正在執行。
show()和hide()方法:分別觸發進入和退出動畫,在動畫執行期間將isAnimationRunning設置為true,動畫結束后設置為false。
AnimatedVisibilityImpl 函數
@Composable
private fun AnimatedVisibilityImpl(visibilityState: AnimatedVisibilityState,modifier: Modifier,enter: EnterTransition,exit: ExitTransition,content: @Composable () -> Unit
) {if (visibilityState.isVisible) {// 根據動畫狀態應用過渡效果val transition = updateTransition(visibilityState.targetVisibility, label = "AnimatedVisibility")val enterFraction = transition.animateFloat(transitionSpec = { enter.animationSpec },label = "EnterFraction") { targetVisible ->if (targetVisible) 1f else 0f}.valueval exitFraction = transition.animateFloat(transitionSpec = { exit.animationSpec },label = "ExitFraction") { targetVisible ->if (targetVisible) 0f else 1f}.value// 根據動畫進度應用修飾符val animatedModifier = modifier.then(enter.createModifier(enterFraction)).then(exit.createModifier(exitFraction))// 顯示組件內容Box(animatedModifier) {content()}}
}
分析:
若visibilityState.isVisible為true,則創建 updateTransition來管理動畫過渡。
分別計算進入和退出動畫的進度enterFraction和exitFraction。
根據動畫進度應用enter和 exit 動畫的修飾符。
使用Box組件包裹content,并應用動畫修飾符。
? ? ? ? 簡而言之,AnimatedVisibility 的源碼核心在于狀態管理和動畫調度。通過 AnimatedVisibilityState管理可見性狀態和動畫執行狀態,利用updateTransition實現動畫過渡,最終根據動畫進度動態調整組件的屬性,實現平滑的可見性動畫效果。