Jetpack Compose 动画

5,015 阅读13分钟

这篇文章,我们会讲解Compose中动画的使用。 先来看一张官网关于如何选择使用Compose动画的图: Screenshot_drawrectandroundrect.jpg 我们会根据这张图的流程来一个个讲解Compose动画的使用。

  • 询问是否是为布局的内容变化添加动画效果(Animating content change in layout)
    • 是布局内容变化的话,其次询问是否是进入/退出过度的动画 (Animating enter/exit transition)\
      • 是的话使用AnimatedVisibility(第一个讲AnimatedVisibility)
      • 否的话询问是否是内容大小的改变的动画(Animating changes to content size)
        • 是的话,使用animateContentSize (第二个讲)
        • 否的话,使用Crossfade (第三个讲)
    • 不是布局内容变化的话,判断是否是基于状态的,并且在合成期间发生。(State-based and happens during composition)
      • 是基于状态的动画的话,在判断动画是否是无限的动画。(My animation is infinite)
        • 是无限的动画的话,使用rememberInfiniteTransition (第6个讲)
        • 不是无限动画的话,判断是否是要多个动画一起生效
          • 多个动画一起生效的话用 updateTransition 第5个讲
          • 不是多个动画一起生效的话用 animate*AsState (比如animateFloatAsState,animateColorAsState) 第4个讲
      • 不是基于状态的话
        • 如果您要对动画播放过程进行定制的话 用Animatable

一:AnimatedVisibility的使用

根据上面的流程图,我们知道,在布局内容改变,并且是进入/退出这种过度动画的时候(比如像控件的显示隐藏)我们使用AnimatedVisibility。注意:AnimatedVisibility为实验性的代码,实验性 API 将来可能会发生变化,也可能会被完全移除。我们先来看看AnimatedVisibility的代码

@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)
}
  • visible 显示还是隐藏
  • modifier 修饰符 之前文章讲过Modifier用法详解
  • enter 进入的动画 EnterTransition有几种获取方式
    • fadeIn()
    • slideIn()
    • expandIn()
    • expandHorizontally()
    • expandVertically()
    • slideInHorizontally()
    • slideInVertically()
  • exit 退出的动画 ExitTransition有几种获取方式
    • fadeOut()
    • slideOut()
    • shrinkOut()
    • shrinkHorizontally()
    • shrinkVertically()
    • slideOutHorizontally()
    • slideOutVertically()
  • initiallyVisible 控制是否应设置入场的动画,默认值是visible。
  • content 包含的内容 举例:比如一个按钮去控制按钮下面内容的显示隐藏,(动画效果可以自行替换)代码如下
@ExperimentalAnimationApi
@Preview
@Composable
fun animatedVisibilityTest(){
    val visible = remember {
        mutableStateOf(true)
    }
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        
        Button(onClick = {
            visible.value = !visible.value
        },modifier = Modifier.padding(vertical = 10.dp)) {
           Text(text =  stringResource(id =if(visible.value) R.string.hide else R.string.show))
        }

        AnimatedVisibility(
            visible = visible.value,
            enter = fadeIn(),
            exit = fadeOut()) {
            Row(
                verticalAlignment = Alignment.CenterVertically
            ) {
                Icon(imageVector = Icons.Filled.Login, contentDescription = stringResource(id = R.string.login))
                Text(modifier = Modifier.padding(start = 5.dp),text = stringResource(id = R.string.login))
            }
        }
    }
}

效果如下:点击隐藏,下面的内容会按我们指定的动画隐藏,点击显示按指定动画显示。

compose_animatedvisibility.png

二:animateContentSize

animateContentSize是Modifier的一个扩展方法,主要使用的时机是,当布局内容的大小发生改变的时候。 我们先来看看它的代码

fun Modifier.animateContentSize(
    animationSpec: FiniteAnimationSpec<IntSize> = spring(),
    finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null
)
  • animationSpec 动画规范(默认是spring弹簧效果)可以有如下几种实现
    • spring(),tween(),keyframes(),repeatable(),infiniteRepeatable(),snap() 后面内容讲到AnimationSpec我们再细讲这几个方法。
  • finishedListener 完成动画的回调

举例如下:比如我们经常碰到的显示的文本多行的情况下,有展开收起的功能

@Preview
@Composable
fun animateContentSizeTest(){

    val expand = remember {
        mutableStateOf(true)
    }
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {

        Button(onClick = {
            expand.value = !expand.value
        },modifier = Modifier.padding(vertical = 10.dp)) {
           Text(text =  if(expand.value) "收起" else "展开")
        }

        Box(
            modifier = Modifier.background(Color.Blue).padding(horizontal = 5.dp).animateContentSize(
            finishedListener = {
                    initialValue, targetValue ->  
                    
                })
        ) {
            Text(text = "探清水河,桃叶儿尖上尖,柳叶儿遮满了天,在其位这位名阿公,细听我来言呐。此事啊,发生在京西南电厂啊,南电厂火器营有一位宋老三啊,提起那宋老三,两口子落平川,一生啊无有儿,所生女婵娟啊,小女啊年长啊一十六啊,取了个乳名,名字叫大连啊,姑娘叫大连,俊俏好容颜",color = Color.White,
            maxLines = if(expand.value) 100 else 2,lineHeight = 20.sp,fontSize = 15.sp,overflow = TextOverflow.Ellipsis)
        }

    }
}

效果如下:点击收起,则显示2行,点击展开则显示多行。 animatecontentsize_.png

三:Crossfade

Crossfade动画的使用时机,是在内容切换的时候,去使用。先来看看代码:

@Composable
fun <T> Crossfade(
    targetState: T,
    modifier: Modifier = Modifier,
    animationSpec: FiniteAnimationSpec<Float> = tween(),
    content: @Composable (T) -> Unit
){...}
  • targetState 是表示目标布局的状态。每次更改时,都会触发动画。
  • modifier 修饰符
  • animationSpec 动画规范。默认是tween。跟上面一致
  • content 内容 举例:内容的切换,比如点击一个按钮,把一个文本变成一个图片。代码如下:
enum class ContentStyle{
    IMAGE,
    TEXT
}

@Preview
@Composable
fun crossfadeTest(){
    val contentStyle = remember {
        mutableStateOf(ContentStyle.TEXT)
    }
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {

        Button(onClick = {
            contentStyle.value = if(contentStyle.value==ContentStyle.TEXT) ContentStyle.IMAGE else ContentStyle.TEXT
        },modifier = Modifier.padding(vertical = 10.dp)) {
           Text(text =  if(contentStyle.value==ContentStyle.TEXT) "变成图片" else "变成文本")
        }

        Crossfade(
            targetState = contentStyle,
            animationSpec = tween(durationMillis = 3000,easing = LinearEasing)
        ) {
            when(it.value){
                // 文本
                ContentStyle.TEXT->{
                    Text(text =  "我是海绵宝宝的文本")
                }
                // 图片
                ContentStyle.IMAGE->{
                    Image(bitmap = ImageBitmap.imageResource(id = R.drawable.icon_head), contentDescription = "海绵宝宝的图片")
                }
            }
        }

    }
}

效果如下:点击变成文本,会显示文本,点击变成图片会显示图片 crossfade.jpg

四:animate*AsState

animateXXXAsState的调用时机是基于状态的改变,从一个状态切换到另一个状态,并且不是无限的动画就去使用它们。总共有如下几种类型的animateXXXAsState。

  • animateColorAsState() color变化
  • animateDpAsState() dp变化
  • animateFloatAsState() float变化
  • animateIntAsState() int变化
  • animateIntOffsetAsState() IntOffset变化
  • animateIntSizeAsState() IntSize变化
  • animateOffsetAsState() Offset变化
  • animateRectAsState() Rect变化
  • animateSizeAsState() Size变化
  • animateValueAsState 自定义类型的变化 animateXXXAsState 的内部实现是会开一个协程去逐渐的改变一个百分比的值,从而达到从一个状态动画过度到另一个状态的效果。如下举例讲解animateColorAsState

4.1 animateColorAsState

先来看看animateColorAsState的具体代码:

@Composable
fun animateColorAsState(
    targetValue: Color,
    animationSpec: AnimationSpec<Color> = colorDefaultSpring,
    finishedListener: ((Color) -> Unit)? = null
): State<Color> {
    val converter = remember(targetValue.colorSpace) {
        (Color.VectorConverter)(targetValue.colorSpace)
    }
    return animateValueAsState(
        targetValue, converter, animationSpec, finishedListener = finishedListener
    )
}
  • targetValue 目标的颜色值
  • animationSpec 动画的类型。默认是colorDefaultSpring
  • finishedListener 动画完成的监听 举例:还是点击一个按钮,按钮下面的内容从红色通过动画过度到黄色。代码如下:
@Preview
@Composable
fun animateXXXStateTest(){

    val redChange = remember {
        mutableStateOf(true)
    }

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {

        Button(onClick = {
            redChange.value = !redChange.value
        },modifier = Modifier.padding(vertical = 10.dp)) {
           Text(text =  if(redChange.value) "变成黄色" else "变成红色")
        }


        val colorState by animateColorAsState(
            targetValue = if(redChange.value) Color.Red else Color.Yellow,
            animationSpec = spring(),
            finishedListener={
                // 动画完成的监听
            })

        Box(modifier = Modifier.background(colorState).size(100.dp))
    }
}

效果如下:点击按钮变红,变黄 animateColorAsState.jpg 其他的animateDpAsState,animateFloatAsState,animateIntAsState,animateIntOffsetAsState,animateOffsetAsState,animateRectAsState,animateSizeAsState类似的使用。animateValueAsState是可以自定义任何类型的。

4.2 animationValueAsState

如果我们需要对其他的类型做动画,我们需要自己去实现类型的转换TwoWayConverter。例子如下: 比如我们定义一个对象叫CustomSize,CustomSize有宽高属性,然后我们对CustomSize去做改变。从而实现动画。代码如下:点击红色的Box,宽高会从200dp动画过度到80dp,再点击会从80dp过度到200dp

class CustomSize(val width:Dp,val height:Dp)

@Preview
@Composable
fun customTwoWayConvert(){
    val big = remember() {
        mutableStateOf(true)
    }
    val sizeState:CustomSize by animateValueAsState<CustomSize,AnimationVector2D>(
        targetValue = if(big.value) CustomSize(200.dp,200.dp) else CustomSize(80.dp,80.dp),
        TwoWayConverter(
            convertFromVector = {
                CustomSize(it.v1.dp,it.v2.dp)
            },
            convertToVector = {
                AnimationVector2D(it.width.value,it.height.value)
            }
        )
    )
    Column() {
        Box(modifier = Modifier.size(sizeState.width,sizeState.height).clickable {
            big.value = !big.value
        }.background(Color.Red))
    }
}

五:updateTransition

当我们要同时去做多个动画的时候,就可以使用updateTransition。updateTransition的代码如下:

@Composable
fun <T> updateTransition(
    targetState: T,
    label: String? = null
): Transition<T> {
    val transition = remember { Transition(targetState, label = label) }
    transition.updateTarget(targetState)
    return transition
}
  • targetState 目标状态
  • label 标签用于区分androidstudio中的不同转换。什么意思呢,在Compose中,Android Studio 允许您通过互动式预览检查动画效果。可查看官方文档地址-拉到最后预览动画 yulan1.jpg 我们可以使用updateTransition创建Transition的实例,而通过Transition去调用如下几种做动画的方法。这几种方法分别一一对应animateXXXState
  • animateColor,animateDp,animateFloat,animateInt,animateIntOffset,animateIntSize,animateOffset,animateRect,animateSize,animateValue。

我们举例一个Box。点击Box同时改变颜色跟大小的例子 代码如下:

// 首先定义Box的两种状态,Small小,Big是大
enum class BoxState{
    Small,
    Big
}

// 定义一个对象TransitionData。里面存放着State<Color>,跟State<Dp>
class TransitionData(color:State<Color>,size:State<Dp>){
   val colorValue by color
   val sizeValue by size
}

// 封装做动画的box,点击的时候会改变boxState的状态
@Composable
fun AnimatingBox(boxState: MutableState<BoxState>){
    val transitionData = updateTransitionData(boxState.value)
    Box(modifier = Modifier
        .background(transitionData.colorValue)
        .size(transitionData.sizeValue).clickable {
            when(boxState.value){
                BoxState.Big->boxState.value=BoxState.Small
                else->boxState.value=BoxState.Big
            }
        })
}

// 根据boxState。Box的状态去封装动画
@Composable
fun updateTransitionData(boxState: BoxState):TransitionData{
    val transition = updateTransition(boxState)
    val color = transition.animateColor{
        when(it){
            BoxState.Big->Color.Red
            BoxState.Small->Color.Yellow
        }
    }
    val size = transition.animateDp{
        when(it){
            BoxState.Big->120.dp
            BoxState.Small->40.dp
        }
    }
    return remember {
        TransitionData(color,size)
    }
}

// 例子使用
@Preview
@Composable
fun updateTransitionTest(){
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        val boxState = remember {
            mutableStateOf(BoxState.Expand)
        }
        AnimatingBox(boxState)
    }
}

效果如下:点击Box,会变小,并且颜色从红色变成黄色,再次点击,颜色从黄变红,并且大小变大 boxbig.jpg 变小的图 boxsmall.jpg

六:rememberInfiniteTransition

InfiniteTransition 可以像 Transition 一样保存一个或多个子动画,但是,这些动画一进入组合阶段就开始运行,除非被移除,否则不会停止,当我们的动画是无限动画的时候,可以使用rememberInfiniteTransition。

@Composable
fun rememberInfiniteTransition(): InfiniteTransition {
    val infiniteTransition = remember { InfiniteTransition() }
    infiniteTransition.run()
    return infiniteTransition
}

我们可以使用 rememberInfiniteTransition 创建 InfiniteTransition 实例。InfiniteTransition通过使用 animateColor,animateFloat,animateValue 添加子动画。 animateColor,animateFloat,animateValue代码如下:

@Composable
fun InfiniteTransition.animateColor(
    initialValue: Color,
    targetValue: Color,
    animationSpec: InfiniteRepeatableSpec<Color>
): State<Color> {
    val converter = remember {
        (Color.VectorConverter)(targetValue.colorSpace)
    }
    return animateValue(initialValue, targetValue, converter, animationSpec)
}

@Composable
fun InfiniteTransition.animateFloat(
    initialValue: Float,
    targetValue: Float,
    animationSpec: InfiniteRepeatableSpec<Float>
): State<Float> =
    animateValue(initialValue, targetValue, Float.VectorConverter, animationSpec)

可以看到定义

  • initialValue 初始值
  • targetValue 目标值
  • animationSpec 动画的规范。后面再细讲AnimationSpec 使用animateColor跟animateFloat举例无限动画的例子如下:
@Preview
@Composable
fun rememberInfiniteTransitionTest(){
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {

        val infiniteTransition = rememberInfiniteTransition()
        val color by infiniteTransition.animateColor(
            initialValue = Color.Red,
            targetValue = Color.Green,
            animationSpec = infiniteRepeatable(
                animation = tween(1000, easing = LinearEasing),
                repeatMode = RepeatMode.Reverse
            )
        )
        val alpha by infiniteTransition.animateFloat(
            initialValue = 1f,
            targetValue = 0.5f,
            animationSpec = infiniteRepeatable(
                animation = tween(1000, easing = LinearEasing),
                repeatMode = RepeatMode.Reverse
            )
        )

        Box(
            Modifier
                .fillMaxSize()
                .background(color)
                .alpha(alpha))
    }
}

效果自行运行代码查看。

七:Animatable

Animatable的使用,是先创建一个Animatable对象,然后调用它的animateTo方法。还可以条用snapTo方法去设置初始值,注意snapTo,animateTo需要放到协程里。具体来看看它的代码:

// 下面是任何类型的
@Suppress("NotCloseable")
class Animatable<T, V : AnimationVector>(
    initialValue: T,
    val typeConverter: TwoWayConverter<T, V>,
    private val visibilityThreshold: T? = null
){...}

// 下面是Float的
fun Animatable(
    initialValue: Float,
    visibilityThreshold: Float = Spring.DefaultDisplacementThreshold
) = Animatable(
    initialValue,
    Float.VectorConverter,
    visibilityThreshold
)

// 下面是Color的。
fun Animatable(initialValue: Color): Animatable<Color, AnimationVector4D> =
    Animatable(initialValue, (Color.VectorConverter)(initialValue.colorSpace))
  • initialValue 是初始值

  • typeConverter 是类型转换器 类型转换器compose有帮我们默认实现了如下几种。

    • Float.VectorConverter
    • Int.VectorConverter
    • Dp.VectorConverter
    • Rect.VectorConverter
    • DpOffset.VectorConverter
    • Size.VectorConverter
    • IntOffset.VectorConverter
    • IntSize.VectorConverter
  • 由于Animatable对Float有做了封装,所以当我们创建一个Float改变的Animatable的时候,我们可以直接 val anim = Animatable(0f)

  • 由于Animatable对color有做了封装,所以当我们创建一个Color改变的Animatable的时候,我们可以直接 val anim = Animatable(Color.Red)

  • 而当我们创建一个Dp的时候,如下 val anim = Animatable(48.dp,Dp.VectorConverter) 我们需要额外去写一个VectorConverter。这个Dp.VectorConverter就是类型转换器。

举例:一个红色的Box。点击会从200dp过度到100dp,点击又会从100dp过度回200dp。代码如下:

@SuppressLint("UnrememberedAnimatable")
@Preview
@Composable
fun animatableTest(){
    val big = remember {
        mutableStateOf(true)
    }
    Column(){
        val anim = remember() {
            Animatable(100.dp,Dp.VectorConverter)
        }
        LaunchedEffect(big.value, block = {
            anim.animateTo(if(big.value) 200.dp else 100.dp)
        })
        Box(modifier = Modifier
            .size(anim.value)
            .clickable {
                big.value = !big.value
            }
            .background(Color.Red))
    }
}

而如果我们想要点击的时候,先把初始化变化到250dp,再去从250dp过度到100dp。那么这时候就得使用snapTo去设置一个初始值,snapTo方法是会直接变化到某个值,而不会有执行动画。而animateTo会有动画。修改之后代码如下:

@SuppressLint("UnrememberedAnimatable")
@Preview
@Composable
fun animatableTest(){
    val big = remember {
        mutableStateOf(true)
    }
    Column(){
        val anim = remember() {
            Animatable(100.dp,Dp.VectorConverter)
        }
        LaunchedEffect(big.value, block = {
            anim.snapTo(if(big.value) 250.dp else 0.dp)
            anim.animateTo(if(big.value) 200.dp else 100.dp)
        })
        Box(modifier = Modifier
            .size(anim.value)
            .clickable {
                big.value = !big.value
            }
            .background(Color.Red))
    }
}

我们在举个自定义类型的例子:同样是CustomSize的例子,用Animatable来写


class CustomSize(val width:Dp,val height:Dp)

@Preview
@Composable
fun customAnimationTest(){
    val big = remember() {
        mutableStateOf(true)
    }

    val customAnimation = remember{
        Animatable<CustomSize,AnimationVector2D>(initialValue = if(big.value) CustomSize(200.dp,200.dp) else CustomSize(80.dp,80.dp),TwoWayConverter(
            convertFromVector = {
                CustomSize(it.v1.dp,it.v2.dp)
            },
            convertToVector = {
                AnimationVector2D(it.width.value,it.height.value)
            }
        ))
    }
    LaunchedEffect(big.value, block = {
        customAnimation.animateTo(if(big.value) CustomSize(80.dp,80.dp) else CustomSize(200.dp,200.dp))
    })

    Column() {
        Box(modifier = Modifier
            .size(customAnimation.value.width, customAnimation.value.height)
            .clickable {
                big.value = !big.value
            }
            .background(Color.Red))
    }
}

八 AnimationSpec

我们很多的api中都有个animationSpec的属性,现在我们开始来讲解。AnimationSpec 它是用于定义动画规范。上面有讲到有如下几种动画类型

  • spring() 弹簧的动画 它的代码如下:
    @Stable
    fun <T> spring(
      dampingRatio: Float = Spring.DampingRatioNoBouncy,
      stiffness: Float = Spring.StiffnessMedium,
      visibilityThreshold: T? = null
    )
    
    它接受 2 个参数:
    • dampingRatio dampingRatio 定义弹簧的弹性。默认值为 Spring.DampingRatioNoBouncy,还有如下几种值 Spring.DampingRatioHighBouncy,Spring.DampingRatioMediumBouncy,Spring.DampingRatioLowBouncy,Spring.DampingRatioNoBouncy,Spring.DefaultDisplacementThreshold。效果如下 boxsmall.jpg
    • stiffness 定义弹簧应向结束值移动的速度。默认值为 Spring.StiffnessMedium,还有Spring.StiffnessHigh,Spring.StiffnessMedium,Spring.StiffnessLow,Spring.StiffnessVeryLow 例子如下:
    val value by animateFloatAsState(
      targetValue = 1f,
      animationSpec = spring(
          dampingRatio = Spring.DampingRatioHighBouncy,
          stiffness = Spring.StiffnessMedium
      )
    )
    
  • tween() 在指定的 durationMillis 内使用缓和曲线在起始值和结束值之间添加动画效果。如果我们想要给动画设置动画时长,可以用它。它的代码如下:
    @Stable
    fun <T> tween(
      durationMillis: Int = DefaultDurationMillis,
      delayMillis: Int = 0,
      easing: Easing = FastOutSlowInEasing
     ): TweenSpec<T> = TweenSpec(durationMillis, delayMillis, easing)
    
    • durationMillis 动画时间
    • delayMillis 延迟多少毫秒执行动画
    • easing 类似View系统时候的Interpolator插值器。有如下几种取值 Easing.FastOutSlowInEasing,Easing.LinearOutSlowInEasing,Easing.FastOutLinearInEasing,Easing.LinearEasing,Easing.CubicBezierEasing。 Easing是基于时长的 AnimationSpec 操作(如 tween 或 keyframes)使用 Easing 来调整动画的小数值。这样可让动画值加速和减速,而不是以恒定的速率移动。小数是介于 0(起始值)和 1.0(结束值)之间的值,表示动画中的当前点。Easing 实际上是一个函数,它取一个介于 0 和 1.0 之间的小数值并返回一个浮点数。返回的值可能位于边界之外,表示过冲或下冲。您可以使用如下所示的代码创建一个自定义 Easing。 easing自定义如下:
      val CustomEasing = Easing { fraction -> fraction * fraction }
      @Composable
      fun EasingUsage() {
         val value by animateFloatAsState(
         targetValue = 1f,
         animationSpec = tween(
             durationMillis = 300,
             easing = CustomEasing
         )
        )
      }
      
    tween举例如下:
    val value by animateFloatAsState(
     targetValue = 1f,
     animationSpec = tween(
         durationMillis = 300,
         delayMillis = 50,
         easing = LinearOutSlowInEasing
     )
    )
    
  • keyframes() 可以设置关键帧,官网解释是会根据在动画时长内的不同时间戳中指定的快照值添加动画效果。在任何给定时间,动画值都将插值到两个关键帧值之间。对于其中每个关键帧,您都可以指定 Easing 来确定插值曲线。 举例:
    val value by animateFloatAsState(
      targetValue = 1f,
      animationSpec = keyframes {
          durationMillis = 375
          0.0f at 0 with LinearOutSlowInEasing // for 0-15 ms
          0.2f at 15 with FastOutLinearInEasing // for 15-75 ms
          0.4f at 75 // ms
          0.4f at 225 // ms
      }
    )
    
  • repeatable() 反复运行基于时长的动画(例如 tween 或 keyframes),直至达到指定的迭代计数 代码如下:
    @Stable
    fun <T> repeatable(
      iterations: Int,
      animation: DurationBasedAnimationSpec<T>,
      repeatMode: RepeatMode = RepeatMode.Restart
    ): RepeatableSpec<T> =
      RepeatableSpec(iterations, animation, repeatMode)
    
    • iterations 重复的次数
    • animation 将重复的动画
    • repeatMode 有两种。一种是RepeatMode.Restart 一种RepeatMode.Reverse 举例使用:
     val value by animateFloatAsState(
      targetValue = 1f,
      animationSpec = repeatable(
          iterations = 3,
          animation = tween(durationMillis = 300),
          repeatMode = RepeatMode.Reverse
      )
     )
    
  • infiniteRepeatable() infiniteRepeatable 与 repeatable 类似,但它会重复无限次的迭代。
  • snap() snap 是特殊的 AnimationSpec,它会立即将值切换到结束值。您可以指定 delayMillis 来延迟动画播放的开始时间。代码如下:
      @Stable
      fun <T> snap(delayMillis: Int = 0) = SnapSpec<T>(delayMillis)
    
    • delayMillis是延迟 代码举例:
    val value by animateFloatAsState(
        targetValue = 1f,
        animationSpec = snap(delayMillis = 50)
    )
    

九:总结一下动画的使用时机,以及它们的关系。

我们总过学了如下几种动画,总结一下他们的使用时机。有三种高级别动画,其他是低级别动画。高级别动画都是基于低级别动画的封装得来。这里的低级别应该是说更底层的意思。

  • AnimatedVisibility (属于高级别动画)控制布局显示隐藏时可用
  • animateContentSize (Modifier的方法,属于高级别动画)布局大小发生改变是使用
  • Crossfade (属于高级别动画) 布局切换时使用
  • animatedXxxState (属于低级别动画) 状态改变时使用 是通过Animatable封装得来
  • updateTransition (属于低级别动画)存在多个动画同时进行,可使用
  • rememberInfiniteTransition (属于低级别动画)存在多个动画或者一个动画时,并且是无限动画可使用
  • Animatable (属于低级别的动画) 如果想要控制动画的过程,比如设置初始值,就可以使用该动画,
  • TargetBasedAnimation 更低级别的动画API 上面updateTransition以及rememberInfiniteTransition以及Animatable动画都是由该api封装得来。 官网有个图可以反映出一些他们的关系如下: boxsmall.jpg

查看的相关文章:
官网
Jetpack Compose Animations 超简单教程