8、Jetpack Compose 入门 --- 开箱即用的动画

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、开箱即用的高级别动画Api

动画 描述 部分参数解释
AnimatedVisibility 为其内容的出现和消失添加动画效果 visible:显示/隐藏状态,改变时触发动画。
enter:指定进入动画效果。
exit:指定退出动画效果。
AnimatedContent 在内容根据目标状态发生变化时,为内容添加动画效果 targetState:目标状态,改变时触发动画。
transitionSpec:用于指定内容切换时的动画。
animateContentSize 为大小变化添加动画效果 animationSpec:动画规范,默认为sping()。可选项包含TweenSpecSpringSpecKeyframesSpecRepeatableSpecSnapSpec
finishedListener:动画完成后的监听。
Crossfade 在两个布局之间切换时添加淡入淡出动画 targetState:目标状态,改变时触发动画。
animationSpec:动画。默认为tween(Int, Int, Easing),参数分别为持续时长、延迟时长、从开始到结束的差值器。

1、AnimatedVisibility:为内容的出现和消失添加动画

系统提供了开箱即用的八种进入动画EnterTransition和八种退出动画ExitTransition。要同时使用多种动画效果,直接通过加号(+)进行组合即可,如:fadeIn() + slideInVertically()

八种进入动画 预览 描述及部分参数解释
fadeIn 淡入
initialAlpha:动画开始时的透明度
slideIn 滑入
initialOffset:动画开始时位置的偏移量
slideInHorizontally 水平滑入
initialOffsetX:动画开始时位置的横向偏移量
slideInVertically 垂直滑入
initialOffsetY:动画开始时位置的垂直偏移量
scaleIn 缩放进入
initialScale:动画开始时的缩放比例
transformOrigin:动画执行的基准点
expandIn 展开进入
expandFrom:从原始样式的哪个方向开始执行动画
clip:动画执行时,是否剪切掉原始组件空间外的内容
initialSize:动画开始时的初始尺寸
expandHorizontally 水平展开进入
expandFrom:从原始样式的哪个方向开始执行动画
clip:动画执行时,是否剪切掉原始组件空间外的内容
initialWidth:动画开始时的初始宽度
expandVertically 垂直展开进入
expandFrom:从原始样式的哪个方向开始执行动画
clip:动画执行时,是否剪切掉原始组件空间外的内容
initialHeight:动画开始时的初始高度
八种退出动画 预览 描述及部分参数解释
fadeOut 淡出
targetAlpha:动画目标的透明度
slideOut 滑出
targetOffset:动画目标位置的偏移量
slideOutHorizontally 水平滑出
targetOffsetX:动画目标位置的横向偏移量
slideOutVertically 垂直滑出
targetOffsetY:动画目标位置的垂直偏移量
scaleOut 缩放进入
targetScale:动画目标的缩放比例
transformOrigin:动画执行的基准点
shrinkOut 收缩退出
shrinkTowards:缩小范围的终点
clip:动画执行时,是否剪切掉原始组件空间外的内容
targetSize:动画结束的终止尺寸
shrinkHorizontally 水平收缩退出
shrinkTowards:缩小范围的终点
clip:动画执行时,是否剪切掉原始组件空间外的内容
targetWidth:动画结束的终止宽度
shrinkVertically 垂直收缩退出
shrinkTowards:缩小范围的终点
clip:动画执行时,是否剪切掉原始组件空间外的内容
targetHeight:动画结束的终止高度

2、AnimatedContent:在内容发生变化时添加动画

参数 说明 解释
targetState 目标状态 该状态变化时,将触发动画
transitionSpec 动画行为 可通过with infix来组合进入动画与退出动画
content 内容 使用目标状态的值,接收动画的触发条件

3、animateContentSize:为大小变化添加动画

只需要给modifier多配置一项animateContentSize(),即可开启该动画。举例:

GIF 2022-4-30 22-29-56.gif

var large by remember { mutableStateOf(true) }
Button(onClick = { large = !large }) {
    Text(text = if (large) "变短" else "变长")
}
Box(
    modifier = Modifier
        .padding(top = 4.dp)
        .background(Color.Blue)
        .animateContentSize()
) {
    Text(
        text = "Hello",
        modifier = Modifier.width(if (large) 100.dp else 50.dp)
    )
}
复制代码

4、Crossfade:在布局间切换时添加淡入淡出动画

参数 说明 解释
targetState 目标状态 该状态变化时,将触发动画
animationSpec 动画配置 默认为tween()
content 内容 使用目标状态的值,接收动画的触发条件

举例:

GIF 2022-4-30 22-48-58.gif

var currentPage by remember { mutableStateOf("A") }
Button(onClick = {
    currentPage = if (currentPage == "A") {
        "B"
    } else {
        "A"
    }
}) {
    Text(text = if (currentPage == "A") "切换到B" else "切换到A")
}
Crossfade(
    targetState = currentPage,
    modifier = Modifier.padding(top = 4.dp),
    animationSpec = tween(durationMillis = 1500)
) { screen ->
    when (screen) {
        "A" -> Box(
            modifier = Modifier
                .size(100.dp)
                .background(color = Color.Cyan)
        ) { Text("Page A") }
        "B" -> Box(
            modifier = Modifier.size(100.dp)
        ) { Text("Page B") }
    }
}
复制代码

二、开箱即用的低级别动画

1、animate*AsState:为单个值添加动画效果

只需提供一个目标值,该 API 就会从当前值开始向目标值播放动画。

GIF 2022-5-6 0-35-19.gif

Column {
    val enabled = remember { mutableStateOf(true) }
    val alpha: Float by animateFloatAsState(if (enabled.value) 1f else 0.5f)
    Box(
        modifier = Modifier
            .padding(vertical = 10.dp)
            .width(width = 100.dp)
            .height(height = 50.dp)
            .graphicsLayer(alpha = alpha)
            .background(color = Color.Red)
    )
    Button(onClick = { enabled.value = !enabled.value }) {
        Text(text = if (enabled.value) "切换为不可用" else "切换为可用")
    }
}
复制代码

上面的列子使用了animateFloatAsState来为透明度alpha添加了动画效果。包含Float在内,Compose 为 FloatColorDpSizeOffsetRectIntIntOffset 和 IntSize 提供开箱即用的 animate*AsState 函数。另外,Compose还提供了一个animateValueAsState函数,支持对任何其他数据类型的支持。

2、Animatable

这种动画除了支持实现上面的animate*AsState的效果之外,还可以对内容做更加精细的控制。比如:

  1. 初始值可以与目标值不同
  2. animateTo可以在值的变化过程中使用动画,snapTo则可以让当前值立即变为目标值

Animatable的使用步骤如下:

  1. 创建一个Animatable对象。
  2. 开启动画作用域。
  3. 在组件中使用Animatable的值。

GIF 2022-5-14 23-53-12.gif

Column {
    //控制尺寸大小
    var big by remember { mutableStateOf(false) }
    val sizeAnimatable = remember { Animatable(32.dp, Dp.VectorConverter) }
    //控制颜色
    var colorTag by remember { mutableStateOf(1) }
    val colorAnimatable = remember {
        //初始值设置为黄色
        Animatable(initialValue = Color.Yellow)
    }
    //是否开启动画作用域
    val animateOpened = remember { mutableStateOf(false) }
    if (animateOpened.value) {
        LaunchedEffect(key1 = big) {
            // 以动画的过程对 size 的值进行切换
            sizeAnimatable.animateTo(targetValue = if (big) 144.dp else 32.dp)
        }
        LaunchedEffect(key1 = colorTag) {
            // 以动画的过程对 color 的值进行切换
            colorAnimatable.animateTo(
                targetValue = when (colorTag) {
                    1 -> Color.Green
                    2 -> Color.Red
                    else -> Color.Blue
                }
            )
        }
    }
    Box(modifier = Modifier
        .size(size = sizeAnimatable.value) // size 在 sizeAnimate 中取值
        .background(color = colorAnimatable.value) // color 在 colorAnimate 中取值
        .clickable { animateOpened.value = !animateOpened.value }
    )
    Text(text = "点击上图开关动画功能,当前已${if (animateOpened.value) "开启" else "关闭"}")
    Row {
        Button(onClick = { big = true }) {
            Text(text = "变大")
        }
        Button(onClick = { big = false }) {
            Text(text = "变小")
        }
    }
    Row(modifier = Modifier.padding(top = 4.dp)) {
        Button(onClick = { colorTag = 1 }) {
            Text(text = "变成绿色")
        }
        Button(onClick = { colorTag = 2 }) {
            Text(text = "变成红色")
        }
        Button(onClick = { colorTag = 3 }) {
            Text(text = "变成蓝色")
        }
    }
}
复制代码

3、updateTransition

Transition 可管理一个或多个动画作为其子项,并在多个状态切换的过程中同时运行这些动画。

Transition的使用步骤如下:

  1. 创建一组状态值。为了保证状态值的范围性,一般可选用枚举来定义。
  2. 定义一组此过渡效果中的子动画。
  3. 在组件中使用这些属性的值。

下面的列子还是实现的上面讲的Animatable部分的效果。

GIF 2022-5-15 1-28-07.gif

enum class BoxState {
    BIG_GREEN,      // 大-绿色
    BIG_RED,        // 大-红色
    BIG_BLUE,       // 大-蓝色
    SMALL_GREEN,    // 小-绿色
    SMALL_RED,      // 小-红色
    SMALL_BLUE      // 小-蓝色
}

@Composable
fun UpdateTransitionCode() {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        //定义初始状态为 小-绿色
        var currentState by remember { mutableStateOf(BoxState.SMALL_GREEN) }
        //创建 Transition
        val transition = updateTransition(targetState = currentState, label = null)
        //定义 尺寸 子动画
        val sizeTransition by transition.animateDp(label = "") { state ->
            when (state) {
                BoxState.BIG_GREEN,
                BoxState.BIG_RED,
                BoxState.BIG_BLUE -> 144.dp
                BoxState.SMALL_GREEN,
                BoxState.SMALL_RED,
                BoxState.SMALL_BLUE -> 32.dp
            }
        }
        //定义 颜色 子动画
        val colorTransition by transition.animateColor(label = "") { state ->
            when (state) {
                BoxState.BIG_GREEN,
                BoxState.SMALL_GREEN -> Color.Green
                BoxState.BIG_RED,
                BoxState.SMALL_RED -> Color.Red
                BoxState.BIG_BLUE,
                BoxState.SMALL_BLUE -> Color.Blue
            }
        }
        Column(horizontalAlignment = Alignment.CenterHorizontally) {
            Box(
                modifier = Modifier
                    .size(size = sizeTransition) // size 取 sizeTransition 的值
                    .background(color = colorTransition) // color 取 colorTransition 的值
            )
            Row(modifier = Modifier.padding(top = 4.dp)) {
                Button(onClick = { currentState = BoxState.BIG_GREEN }) {
                    Text(text = "大-绿色")
                }
                Button(onClick = { currentState = BoxState.BIG_RED }) {
                    Text(text = "大-红色")
                }
                Button(onClick = { currentState = BoxState.BIG_BLUE }) {
                    Text(text = "大-蓝色")
                }
            }
            Row(modifier = Modifier.padding(top = 4.dp)) {
                Button(onClick = { currentState = BoxState.SMALL_GREEN }) {
                    Text(text = "小-绿色")
                }
                Button(onClick = { currentState = BoxState.SMALL_RED }) {
                    Text(text = "小-红色")
                }
                Button(onClick = { currentState = BoxState.SMALL_BLUE }) {
                    Text(text = "小-蓝色")
                }
            }
        }
    }
}
复制代码

4、rememberInfiniteTransition

InfiniteTransition 可用来构建一个无限循环运行的动画,并可以像 Transition 一样保存一个或多个子动画。但是这些动画将会在界面的组合阶段就开始运行,只要不被移除,此动画将永远地运行下去。

使用步骤如下:

  1. 创建 InfiniteTransition
  2. 利用 InfiniteTransition.animateXxx API 创建一组子动画。
  3. 在组件中使用这些属性的值。

GIF 2022-5-15 1-59-10.gif

// 创建 InfiniteTransition
val infiniteTransition = rememberInfiniteTransition()
// 定义 尺寸 子动画
val sizeState by infiniteTransition.animateFloat(
    initialValue = 144f, targetValue = 32f,
    animationSpec = infiniteRepeatable(
        animation = tween(2000, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    )
)
// 定义 颜色 子动画
val colorState by infiniteTransition.animateColor(
    initialValue = Color.Red,
    targetValue = Color.Green,
    animationSpec = infiniteRepeatable(
        animation = tween(2000, easing = LinearEasing),
        repeatMode = RepeatMode.Reverse
    )
)
Box(
    modifier = Modifier
        .size(size = Dp(sizeState))     // 使用 sizeState
        .background(color = colorState) // 使用 colorState
)
复制代码

猜你喜欢

转载自juejin.im/post/7097896923251605518