动画是界面交互重要的要素之一,为界面交互增添了活性,给人视觉上的享受。下面我们来使用Android Compose中的动画。在使用Compsose中的动画之前,我们需要先引入动画依赖:
// Animations 下面的version不唯一,本文中使用的是1.1.0
implementation 'androidx.compose.animation:animation:$version'
AnimatedVisibility可组合项可为内容的出现和消失添加动画效果,出现动画(EnterTransition)有fadeIn、slideIn、slideInHorizontally、slideInVertically、scaleIn、expandIn、expandHorizontally、expandVertically;与之对应的消失动画(ExitTransition)有fadeOut、slideOut、slideOutHorizontally、slideOutVertically、scaleOut、shrinkOut、shrinkHorizontally、shrinkVertically。
/**
* fadeIn和fadeOut
*/
@ExperimentalAnimationApi
@Composable
fun compose_21_fade(visible: Boolean) {
AnimatedVisibility(
visible = visible,
enter = fadeIn(),
exit = fadeOut()
) {
Image(painter = painterResource(id = R.drawable.flower), contentDescription = "")
}
}
@ExperimentalAnimationApi
@Composable
fun compose_21_slide(visible: Boolean) {
val density = LocalDensity.current
AnimatedVisibility(
visible = visible,
// enter = slideIn(
// // 起始位置
// initialOffset = { fullSize -> IntOffset(fullSize.width,fullSize.height) },
// ),
// exit = slideOut(
// // 目标位置
// targetOffset = { fullSize -> IntOffset(fullSize.width,fullSize.height) },
// )
// enter = slideInHorizontally(
// // 起始X坐标位置
// initialOffsetX = { fullWidth -> fullWidth }
// ),
// exit = slideOutHorizontally(
// // 目标X坐标位置
// targetOffsetX = { fullWidth -> fullWidth }
// )
enter = slideInVertically(
// 起始X坐标位置
initialOffsetY = { fullHeight -> fullHeight }
),
exit = slideOutVertically(
// 目标X坐标位置
targetOffsetY = { fullHeight -> fullHeight }
)
) {
Image(painter = painterResource(id = R.drawable.flower), contentDescription = "")
}
}
@ExperimentalAnimationApi
@Composable
fun compose_21_scale(visible: Boolean) {
AnimatedVisibility(
visible = visible,
enter = scaleIn(),
exit = scaleOut()
) {
Image(painter = painterResource(id = R.drawable.flower), contentDescription = "")
}
}
@ExperimentalAnimationApi
@Composable
fun compose_21_expandShrink(visible: Boolean) {
AnimatedVisibility(
visible = visible,
// enter = expandIn(expandFrom = Alignment.Center),
// exit = shrinkOut(shrinkTowards = Alignment.Center)
// enter = expandHorizontally (expandFrom = Alignment.Start),
// exit = shrinkHorizontally(shrinkTowards = Alignment.Start)
enter = expandVertically(expandFrom = Alignment.Top),
exit = shrinkVertically(shrinkTowards = Alignment.Top)
) {
Image(painter = painterResource(id = R.drawable.flower), contentDescription = "")
}
}
@ExperimentalAnimationApi
@Composable
fun animationControl() {
var visible by remember { mutableStateOf(false) }
Column {
Button(
onClick = { visible = !visible }, // 点击事
) {
Text(text = "开始动画", color = Color.Black)
}
compose_21_expandShrink(visible)
}
}
class Compose_21Activity : ComponentActivity() {
@ExperimentalAnimationApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
animationControl()
}
}
}
上面写的都是单个动画出现,但是如果我们要使用组合动画该怎么办呢?其实很简单,只要用+号把要组合的动画加在一起就行了,例如:
@ExperimentalAnimationApi
@Composable
fun compose_21_fadePlusScale(visible: Boolean) {
AnimatedVisibility(
visible = visible,
enter = fadeIn() + scaleIn(),
exit = fadeOut() + scaleOut()
) {
Image(painter = painterResource(id = R.drawable.flower), contentDescription = "")
}
}
看到这里,可能有一些小伙伴会在想如何设置动画的属性呢?同样很简单,只需要动画的animationSpec赋值就行,默认值是animationSpec=spring(...),除了spring之外还有tween、keyFrame、repeatable和infiniteRepeatable。本文在这里不做研究,只写可能用得比较多的例子:
@ExperimentalAnimationApi
@Composable
fun compose_21_fade(visible: Boolean) {
AnimatedVisibility(
visible = visible,
enter = fadeIn(animationSpec = tween( //帧动画
durationMillis = 3000, // 持续时间(单位/ms)
delayMillis = 1000, // 延时时间(单位/ms)
easing = FastOutSlowInEasing)),
exit = fadeOut(animationSpec = repeatable( // 重复动画
iterations = 3, // 重复次数必须大于0
// 动画
animation = tween(durationMillis = 3000, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Restart // 模式
))
) {
Image(painter = painterResource(id = R.drawable.flower), contentDescription = "")
}
}
既然是动画,那么就有动画状态。对动画状态的监听是开发过程中比不可少的环节,我们可用通过AnimatedVisibility的MutableTransitionState来监听动画的状态。MutableTransactionState有两个重要的属性currentState和isIdle,我们可用通过这两个组合属性来判断AnimatedVisibility是否可见、出现中还是消失中。
// state是MutableTransactionState对象
when {
state.isIdle && state.currentState -> "可见"
!state.isIdle && state.currentState -> "消失中"
state.isIdle && !state.currentState -> "不可见"
else -> "出现中"
}
同样,写一个简单的例子:
@Composable
fun compose_21_animationState(state: MutableTransitionState) {
// 这里改为参数获取
// val state = remember {
// MutableTransitionState(false).apply {
// // Start the animation immediately.
// targetState = true
// }
// }
Column {
AnimatedVisibility(
visibleState = state,
enter = slideInHorizontally(animationSpec = tween(durationMillis = 2000, delayMillis = 1000)),
exit = slideOutHorizontally(animationSpec = tween(durationMillis = 2000, delayMillis = 1000))
) {
Image(painter = painterResource(id = R.drawable.flower), contentDescription = "")
}
Text(
text = when {
state.isIdle && state.currentState -> "可见"
!state.isIdle && state.currentState -> "消失中"
state.isIdle && !state.currentState -> "不可见"
else -> "出现中"
}
)
}
}
@ExperimentalAnimationApi
@Composable
fun animationControl() {
// var visible by remember { mutableStateOf(true) }
val state = remember {
MutableTransitionState(false).apply {
// Start the animation immediately.
targetState = false
}
}
Column {
Button(
onClick = {
// visible = !visible
state.targetState = !state.targetState
}, // 点击事件
) {
Text(text = "开始动画", color = Color.Black)
}
compose_21_animationState(state)
}
}
如果想给AnimatedVisibility的子项(直接或间接)添加动画的话,可用通过animateEnterExit来实现,但是子项的视觉效果均由AnimatedVisibility可组合项中的动画与子项自己的动画构成,以下是个简单例子:
@ExperimentalAnimationApi
@Composable
fun compose_21_sonAnimation(visible: Boolean) {
AnimatedVisibility(
visible = visible,
enter = slideInVertically(),
exit = slideOutVertically(animationSpec = tween(
delayMillis = 1000 // 为了见效果,延时一秒
))
) {
Box(Modifier.fillMaxSize().background(Color.DarkGray)) {
Box(
Modifier
.align(Alignment.Center)
.animateEnterExit(
// Slide in/out the inner box.
enter = slideInHorizontally (animationSpec = tween(
delayMillis = 1000 // 为了见效果,延时一秒
)),
exit = slideOutHorizontally()
)
.sizeIn(minWidth = 100.dp, minHeight = 100.dp)
.background(Color.Blue)
) {
// Content of the notification…
}
}
}
}
注:如果你不应用AnimatedVisibility的动画的话,可以把AnimatedVisibility的enter设置为EnterAnimation.Noe;eixt设置为ExitAnimation.None。
可能对于某些需求来说,自带的动画不能满足,那么可以通过AnimatedVisibility的内容lambda内的transition属性访问底层的Transition实例(其所有动画状态都与AnimatedVisibility的进入和退出动画同时运行)。
例如我想通过transition来改变某组合项的背景颜色和大小,可以这样子实现:
@ExperimentalAnimationApi
@Composable
fun compose_21_customizeAnimation(visible: Boolean){
AnimatedVisibility(
visible = visible,
enter = fadeIn(animationSpec = tween(
durationMillis = 3000, // 为了见效果
delayMillis = 1000
)),
exit = fadeOut(animationSpec = tween(
durationMillis = 3000, // 为了见效果
delayMillis = 5000
))
) { // this: AnimatedVisibilityScope
// Use AnimatedVisibilityScope#transition to add a custom animation
// to the AnimatedVisibility.
val background by transition.animateColor { state ->
when(state) {
EnterExitState.PreEnter -> Color.Red // 进入前
EnterExitState.Visible -> Color.Blue // 可见
EnterExitState.PostExit -> Color.Green // 退出前
}
}
val size by transition.animateDp { state ->
when(state) {
EnterExitState.PreEnter -> 50.dp
EnterExitState.Visible -> 100.dp
EnterExitState.PostExit -> 70.dp
}
}
Box(modifier = Modifier
.size(size)
.background(background))
}
}