Compose笔记(二十一)--AnimationVisibility
这一节主要了解一下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实现动画过渡,最终根据动画进度动态调整组件的属性,实现平滑的可见性动画效果。