前一节介绍的高档别动画API都是在初级别动画API的基础上构建的。本节来看看都有哪些初级别动画API,以及该如何运用它们。

一、初级别动画API

1、animate*AsState

animated*AsState是最常用的初级别API之一,它类似于传统视图中的特点动画,能够自动完结从当时值到目标值过渡的估值核算。

以animateColorAsState为例,它将Color转成一个能够在Composable中拜访的State。

@Composable
funanimateColorAsState(  
targetValue:Color,  
animationSpec:AnimationSpec<Color>=colorDefaultSpring,  
finishedListener:((Color)->Unit)?=null  
):State<Color>{  
returnanimateColorAsState(  
targetValue,animationSpec,finishedListener=finishedListener  
)  
}

Color的变化会触发Composable重组,从而完结动画作用。下面是一个比方:

@Preview
@Composable  
funTestAnimateColorAsState(){  
varisLikebyremember{mutableStateOf(false)}  
        //animateDpAsState巨细发生变化
valbuttonSizebyanimateDpAsState(  
targetValue=if(isLike)32.dpelse24.dp,  
animationSpec=tween(3000),   //为了看到作用特别加长了动画时长
label=""  
)  
        //animateColorAsState色彩发生变化
valbuttonColorbyanimateColorAsState(  
targetValue=if(isLike)Color.RedelseColor.Gray,  
animationSpec=tween(3000),  //为了看到作用特别加长了动画时长
            label=""  
)  
IconButton(onClick={  
isLike=!isLike  
}){  
Icon(  
imageVector=Icons.Rounded.Favorite,  
contentDescription=null,  
modifier=Modifier.size(buttonSize),   //运用buttonSize
tint=buttonColor      //运用buttonColor
)  
}  
}

UI作用

Compose为常用的数据类型都供给了animate*AsState办法,例如Float、Color、Dp、Size、Bounds、Offset、Rect、Int、IntOffset和InSize等,关于无法直接估值核算的数据类型,能够运用通用类型的animateValueAsState,并自行完结TwoWayConverter估值核算器。

2、Animatable

Animatble是一个数值包装器,它的animateTo办法能够依据数值的变化设置动画作用,animate*AsState背面便是依据Animatable完结的。在前面的比方中,经过animateColorAsState对IconButton色彩运用过渡动画,假如改为Animatable完结方案,那么代码如下:

@Preview
@Composable  
funTestAnimatable(){  
varisLikebyremember{mutableStateOf(false)}  
//animateDpAsState修正尺度  
valbuttonSizebyanimateDpAsState(  
targetValue=if(isLike)32.dpelse24.dp,  
animationSpec=tween(3000),  
label=""  
)  
//Animatable修正色彩  
valbuttonColor=remember{Animatable(Color.Gray)}  
IconButton(onClick={  
isLike=!isLike  
}){  
Icon(  
imageVector=Icons.Rounded.Favorite,  
contentDescription=null,  
modifier=Modifier.size(buttonSize),  
tint=buttonColor.value  
)  
}  
LaunchedEffect(isLike){  
//animateTo  
buttonColor.animateTo(  
targetValue=if(isLike)Color.RedelseColor.Gray,  
animationSpec=tween(3000)  
)  
}  
  
}

UI作用同上

Animatable中包括animateTo在内的许多API都是挂起函数,需求在CoroutineScope中履行,能够运用LaunchedEffect为其供给所需的环境。

在上面的比方中,咱们创立了初始值为Color. Gray的Animatable,运用remember防止重组中的重复创立。当状况isLike更新时,Animatable的色彩值将以动画方法在Color. Red和Color. Gray之间转化。假如在动画中途从头修正了状况isLike,那么播映中的动画会被当即中断并开端新的动画。

Animatable是animate*AsState的更底层api,因而相关于animate*AsState,它具有更多灵敏的才能,这体现在以下几个方面:

首要,Animatable答应设置一个不同的初始值,比方能够将IconButton的初始色彩设置为一个不同于Gray和Red的恣意色彩,然后经过animateTo将其改动为目标色彩。

其次,Animatable除了animateTo之外,还供给了不少其他办法,比方snapTo能够当即到达目标值,中心没有过渡值,能够在需求越过动画的场景中运用这个办法。animateDecay能够发动的一个衰减动画,这在fling等场景中非常有用。

需求留意的是多个animateTo是顺次按次序履行的,看下面的代码:

import androidx.compose.animation.Animatable
import androidx.compose.animation.core.Animatable
@Preview  
@Composable  
funTestAnimatable(){  
varisLikebyremember{mutableStateOf(false)}  
//Animatable修正尺度  
valbuttonSize=remember{Animatable(24.dp,Dp.VectorConverter)}  
//Animatable修正色彩  
valbuttonColor=remember{Animatable(Color.Gray)}  
LaunchedEffect(isLike){  
//animateTo  
buttonColor.animateTo(  
targetValue=if(isLike)Color.RedelseColor.Gray,  
animationSpec=tween(2000)  
)  
//animateTo  
buttonSize.animateTo(  
targetValue=if(isLike)48.dpelse24.dp,  
animationSpec=tween(2000)  
)  
}  
IconButton(onClick={  
isLike=!isLike  
}){  
Icon(  
imageVector=Icons.Rounded.Favorite,  
contentDescription=null,  
modifier=Modifier.size(buttonSize.value),  
tint=buttonColor.value  
)  
}  
}

UI作用

Animatable传入名为Dp.VectorConverter的参数,这是一个针对Dp类型的TwoWayConverter。Animatable能够直接传入Float或Color类型的值,当传入其他类型时,需求一起指定对应的TwoWayConverter。Compose为常用数据类型都供给了对应的TwoWayConverter完结,直接传入即可,例如代码中的Dp. VectorConverter。

二、Transition过渡动画

Transition也是初级别动画API中的一类。AnimateState以及Animatable都是针对单个目标值的动画,而Transition能够面向多个目标值运用动画并保持它们同步完毕。Transition的作用更像是传统视图系统动画中的AnimationSet。

留意:尽管这儿的Transition与前面介绍的EnterTransition和ExitTransition等在姓名上很简略混淆,但实践是不同的东西。

1、updateTransition

咱们运用updateTransition创立一个Transition动画,经过一个比方看一下详细运用方法。

首要,Transition也需求依靠状况履行,需求枚举出一切或许的状况。

/**
 *密封类,代表二种状况  
 */  
sealedclassSwitchState{  
    objectOPEN:SwitchState()  
    objectCLOSE:SwitchState()  
}

接下来需求创立一个MutableState表明当时开关的状况,并运用updateTransition依据这个状况创立Transition实例。

varselectedStatebyremember{mutableStateOf(SwitchState.CLOSE)}
valtransition=updateTransition(targetState=selectedState,label="switch_transition")

updateTransition接纳两个参数。targetState最重要,它是动画履行所依靠的状况。label是代表此动画的标签,能够在Android Studio动画预览东西中标识动画。

获取到Transition实例后,能够创立Transitioin动画中的一切特点状况。前面说过当开关被打开时,文案会逐步消失,与此一起,底部逐步上升选中的标签。所以这儿需求两个特点状况:selectBarPadding与textAlpha。

运用animate*来声明每个动画特点其在不同状况时的数值信息,当Transition所依靠的状况发生改动时,其中每个特点状况都会得到相应的更新。

valselectBarPaddingbytransition.animateDp(
transitionSpec={  
tween(1000)  
},label=""  
){  
when(it){  
SwitchState.CLOSE->40.dp  
SwitchState.OPEN->0.dp  
}  
}  
valtextAlphabytransition.animateFloat(  
transitionSpec={  
tween(1000)  
},label=""  
){  
when(it){  
SwitchState.CLOSE->1f  
SwitchState.OPEN->0f  
}  
}

能够为animate*设置transitionSpec参数,为特点状况制定不同的AnimationSpec,关于AnimationSpec咱们会在后边一节详细介绍。接下来,仅需将创立好的状况运用到组件的对应特点中,完好代码如下:

    @Preview
@Composable  
funSwitchBlock(){  
varselectedState:SwitchStatebyremember{mutableStateOf(SwitchState.CLOSE)}  
valtransition=updateTransition(selectedState,label="switchtransition")  
valselectBarPaddingbytransition.animateDp(  
transitionSpec={  
tween(1000)  
},label=""  
){  
when(it){  
SwitchState.CLOSE->40.dp  
SwitchState.OPEN->0.dp  
}  
}  
valtextAlphabytransition.animateFloat(  
transitionSpec={  
tween(1000)  
},label=""  
){  
when(it){  
SwitchState.CLOSE->1f  
SwitchState.OPEN->0f  
}  
}  
Box(  
modifier=Modifier  
.size(150.dp)  
.padding(8.dp)  
.clip(RoundedCornerShape(10.dp))  
.clickable{  
selectedState=if(selectedState==SwitchState.OPEN)SwitchState.CLOSEelse  
SwitchState.OPEN  
}){  
Image(  
painter=painterResource(id=R.mipmap.rabit2),  
contentDescription=stringResource(R.string.description),  
contentScale=ContentScale.FillBounds  
)  
Text(  
text="点我",  
fontSize=30.sp,  
fontWeight=FontWeight.W900,  
color=Color.White,  
modifier=Modifier  
.align(Alignment.Center)  
.alpha(textAlpha)  
)  
Box(  
modifier=Modifier  
.align(Alignment.BottomCenter)  
.fillMaxWidth()  
.height(40.dp)  
.padding(top=selectBarPadding)  
.background(Color.DarkGray)  
){  
Row(  
modifier=Modifier  
.align(Alignment.Center)  
.alpha(1-textAlpha)  
){  
Icon(  
imageVector=Icons.Filled.CheckCircle,  
contentDescription="star",  
tint=Color.White  
)  
Spacer(modifier=Modifier.width(2.dp))  
Text(  
text="已选择",  
fontSize=20.sp,  
fontWeight=FontWeight.W900,  
color=Color.White  
)  
}  
}  
}  
}

UI作用

Jetpack Compose(十七)Compose动画初级别动画API

(1)createChildTransition创立子动画

Transition能够运用createChildTransition创立子动画,比方鄙人面的场景中。咱们期望经过Transition来同步操控DialerButton和NumberPad的显隐,可是关于DailerButton和NumberPad来说,各自只需求关心自己的状况。经过createChildTranstion将DailerState转化成Boolean类型State,能够更好地完结关注点分离。子动画的动画数值核算来自于父动画,某种程度上说,createChildTransition更像是一种map。伪代码如下所示:

enum class DialerState { DialerMinimized, NumberPad }
@Composable
fun DialerButton(isVisibleTransition: Transition<Boolean>) {
    ...
}
@Composable
fun NumberPad(isVisibleTransition: Transition<Boolean>) {
    ...
}
@OptIn(ExperimentalTransitionApi::class)
@Composable
fun Dialer(dialerState: DialerState) {
    val transition = updateTransition(dialerState, label = "")
    Box {
        NumberPad(
            transition.createChildTransition {	//createChildTransition
                it == DialerState.NumberPad
            })
        DialerButton(
            transition.createChildTransition {	//createChildTransition
                it == DialerState.DialerMinimized
            })
    }
}

(2)与AnimatedVisibility和AnimatedContent合作运用

AnimatedVisibility和AnimatedContent有针对Transition的扩展函数,将Transition的State转化成所需的TargetState。借助这两个扩展函数,能够将AnimatedVisibility和AnimatedContent的动画状况经过Transition对外露出,以供运用。

看下面的示例代码:

    @OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class)
    @Composable
    fun TestAnimateCombine() {
        var selected by remember { mutableStateOf(false) }
        //当selected被更改时触发过渡动画
        val transition = updateTransition(selected, label = "")
        //界说动画
        //改动边框色彩
        val borderColor by transition.animateColor(label = "") { isSelected ->
            if (isSelected) Color.Magenta else Color.White
        }
        //改动维度
        val elevation by transition.animateDp(label = "") { isSelected ->
            if (isSelected) 10.dp else 2.dp
        }
        Surface(
            onClick = { selected = !selected },
            modifier = Modifier.size(200.dp),
            shape = RoundedCornerShape(8.dp),
            border = BorderStroke(2.dp, borderColor),
            tonalElevation = elevation,
            shadowElevation = elevation
        ) {
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            ) {
                //AnimatedVisibility作为过渡动画的一部分
                //扩展函数
                transition.AnimatedVisibility(
                    visible = { targetSelected -> targetSelected },
                    enter = expandVertically(),
                    exit = shrinkVertically()
                ) {
                    Text(text = "It is fine today.")
                }
                //AnimatedContent作为过渡动画的一部分
                //扩展函数
                transition.AnimatedContent { targetState ->
                    if (targetState) {
                        Text(text = "Selected")
                    } else {
                        Icon(
                            imageVector = Icons.Default.Phone, contentDescription = stringResource(
                                R.string.description
                            )
                        )
                    }
                }
            }
        }
    }

UI作用

关于AnimatedContent扩展函数来说,transition所包括的状况数值会被转化成targetValue参数传入。而关于AnimatedVisibility扩展函数来说,则需求经过转化器将包括的状况数值转化成其所需的Boolean类型visible参数。

(3)封装并复用Transition动画

在简略的场景下,在用户界面中运用UpdateTransition创立Transition并直接操作它完结动画是没有问题的。然而,假如需求处理一个具有许多动画特点的杂乱场景,或许期望把Transition动画的完结与用户界面分隔。

能够经过创立一个持有一切动画值的类和一个回来该类实例的“更新”函数来做到这一点。Transition动画的完结被提取到单独的函数中,便于后续进行复用。

下面是一种方法和思路:

    //经过枚举界说状况
    enum class BoxState { Collapsed, Expanded }
    //经过BoxState枚举界说状况
    //运用示例
    @Composable
    fun AnimatingBox(boxState: BoxState) {
        //updateTransitionData经过传入的枚举类型获取动画数据
        val transitionData = updateTransitionData(boxState)
        //将动画数据赋值给UI树
        Box(
            modifier = Modifier
                .background(color = transitionData.color)
                .size(transitionData.size)
        )
    }
    //自界说保存动画数值的类
    //import androidx.compose.runtime.State
    private class TransitionData(
        color: State<Color>,
        size: State<Dp>
    ) {
        val color by color
        val size by size
    }
    //创立一个Transition并回来其动画值
    //经过BoxState枚举界说状况
    @Composable
    private fun updateTransitionData(boxState: BoxState): TransitionData {
        val transition = updateTransition(boxState, label = "")
        val color = transition.animateColor(label = "") { state ->
            when (state) {
                BoxState.Collapsed -> Color.Gray
                BoxState.Expanded -> Color.Red
            }
        }
        val size = transition.animateDp(label = "") { state ->
            when (state) {
                BoxState.Collapsed -> 64.dp
                BoxState.Expanded -> 128.dp
            }
        }
        return remember(transition) { TransitionData(color, size) }
    }

2、rememberInfiniteTransition

InfinitTransition从姓名上便能够知道其便是一个无限循环版的Transition。一旦动画开端履行,便会不断循环下去,直至Composable生命周期完毕。

子动画能够用animateColor、animatedFloat或animatedValue等进行增加,另外还需求指定infiniteRepeatableSpec来设置动画循环播映方法。

看下面的代码示例:

    @Composable
    fun TestInfiniteTransition() {
        val infiniteTransition = rememberInfiniteTransition(label = "")
        val color by infiniteTransition.animateColor(
            initialValue = Color.Red,//初始值
            targetValue = Color.Green, //终究值
            animationSpec = infiniteRepeatable(   //创立一个可无限次重复的InfiniteRepeatableSpec
                //一个动画值的转化继续1秒,平缓方法为LinearEasing
                animation = tween(1000, easing = LinearEasing),
                repeatMode = RepeatMode.Reverse
                //指定动画重复运行的方法
                // Reverse为initialValue->targetValue,targetValue->initialValue
                // Repeat为initialValue->targetValue,initialValue->targetValue
            ), label = ""
        )
        Box(
            Modifier
                .fillMaxSize()
                .background(color)
        )
    }

UI作用

Jetpack Compose(十七)Compose动画初级别动画API

在上面的比方中,运用infiniteRepeatable创立了infiniteRepeatableSpec,其中运用tween创立一个单次动画的AnimationSpec,这是一个继续时长1000 ms的线性衰减动画。经过repeatMode参数指定动画循环播映方法为Reverse,此外还有一种方法是Repeat。从姓名上能够直观地看出两者的区别。Reverse会在到达完毕状况后,原路回来起始状况从头开端,而Repeat则会从当即回到起始状况从头开端。

三、AnimationSpec动画规格

在前面呈现的代码中屡次见到animationSpec参数。大部分的Compose动画API都支撑经过animationSpec参数界说动画作用:

val alpha: Float by animateFloatAsState(
 targetValue = if (enabled) 1f else 0.5f,
    //设置一个时长300ms的补间动画
 animationSpec = tween(durationMillis =300, easing = FastOutSlowInEasing)
 )

在上面的代码中运用tween创立了一个AnimationSpec实例。

看一下AnimationSpec源码:

interface AnimationSpec<T> {
    fun <V : AnimationVector> vectorize(  
        converter: TwoWayConverter<T, V>  
    ): VectorizedAnimationSpec<V>  
}

AnimatioinSpec是一个单办法接口,泛型T是当时动画数值类型,vectorize用来创立一个VectorizedAnimationSpec,即一个矢量动画的装备。矢量动画是经过函数运算生成的,而AnimationVector便是用来参加核算的动画矢量。TwoWayConverter将T类型的状况值转化成参加动画核算的矢量数据。

Compose供给了多种AnimationSpec的子类,分别依据不同的VectorizedAnimationSpec完结不同动画作用的核算。例如TweenSpec用来完结两点间的补间动画,SpringSpec完结依据物理作用的动画,SnapSpec是一个即时生效的动画。

接下来看看各种AnimationSpec的构建,以及它们所供给的实践动画作用。

1、spring弹跳动画

运用spring会创立一个SpringSpec实例,可用来创立一个依据物理特性的弹跳动画,它的动画估值将在当时值和目标值之间按照绷簧物理运动轨道进行变化。看下面一段代码:

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioHighBouncy,  //dampingRatio
        stiffness = Spring.StiffnessMedium    //stiffness
    ), label = ""
)

spring有三个参数:dampingRatio、stiffness和visibilityThreshold,前两个参数主要用来操控弹跳动画的动画作用。源码:

@Stable
fun <T> spring(
    dampingRatio: Float = Spring.DampingRatioNoBouncy,
    stiffness: Float = Spring.StiffnessMedium,
    visibilityThreshold: T? = null
): SpringSpec<T> =
    SpringSpec(dampingRatio, stiffness, visibilityThreshold)

(1)dampingRation

dampingRation表明绷簧的阻尼比。阻尼比用于描绘绷簧振荡逐步衰减的状况。阻尼比能够界说振荡从一次弹跳到下一次弹跳所衰减的速度有多快。以下是不同阻尼比下的弹力衰减状况:

  • 当dampingRation>1时会呈现过阻尼现象,这会使目标快速地回来到中止方位。
  • 当dampingRation=1时会呈现临界阻尼现象,这会使目标在最短时刻内回来到中止方位。
  • 当1>dampingRation>0时会呈现欠阻尼现象,这会使目标环绕终究中止方位进行屡次反复震动。
  • 当dampingRation=0时会呈现无阻尼现象,这会使目标永久振荡下去。

留意阻尼比不能小于零。

Compose为spring供给了一组常用的阻尼比常量。

object Spring {
const val StiffnessHigh = 10_000f  
const val StiffnessMedium = 1500f    
const val StiffnessMediumLow = 400f  
const val StiffnessLow = 200f  
const val StiffnessVeryLow = 50f  
const val DampingRatioHighBouncy = 0.2f  
const val DampingRatioMediumBouncy = 0.5f   
const val DampingRatioLowBouncy = 0.75f   
const val DampingRatioNoBouncy = 1f 
const val DefaultDisplacementThreshold = 0.01f  
}

假如不额外指定,默许会选用DampingRatioNoBouncy。此刻会呈现临界阻尼现象,目标会在很短的时刻内恢复中止而不发生振荡。

(2)stiffness

stiffness界说绷簧的刚度值。刚度值越大,绷簧到中止状况的速度越快。Compose为stiffness界说的常量如上段代码所示。

stiffness的默许值为StiffnessMedium,表明到中止进程的速度适中。许多动画API内部对AnimationSpec运用的默许值均为spring,例如animate*AsState以及updateTransition等。因为spring的动画作用依据物理原理,使动画愈加实在自然。

留意:刚度值有必要大于0。

(3)visibilityThreshold

spring的最终一个参数visibilityThreshold是一个泛型,此泛型与targetValue类型保持一致。由开发者指定一个阈值,当动画到达这个阈值时,动画会当即中止。

2、tween补间动画

运用tween能够创立一个TweenSpec实例,TweenSpec是DurationBasedAnimationSpec的子类。从基类姓名能够感受到,TweenSpec的动画有必要在规则时刻内完结,所以它不能像SpringSpec那样完全依据物理规律进行动画,它的动画作用是依据时刻参数核算的,能够运用Easing来指定不同的时刻曲线动画作用。

tween有三个参数,如下所示:

  val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = tween(
        durationMillis = 300,//动画履行时刻(ms)
        delayMillis = 50,//能够指定动画的延迟履行
        easing = LinearOutSlowInEasing//衰减曲线动画作用
    ), label = ""
)

3、keyframes关键帧动画

相关于tween动画只能在开端和完毕两点之间运用动画作用,keyframes能够更精细地操控动画,它答应在开端和完毕之间刺进关键帧节点,节点与节点之间的动画过渡能够运用不同作用。

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
    }, label = ""
)

keyframes会回来一个KeyFramesSpec实例,因为它也是DurationBasedAnimationSpec的子类,所以它也是一种需求在规则时刻内完结的动画。关键帧节点的界说便是由时刻戳、动画数值,以及动画作用等组成。比方0.2f at 15 with FastOutLinearInEasing表明在15ms时刻value应该到达0.2f,而且运用FastOutLinearInEasing动画作用。keyframes内部经过选用中缀运算符的装备方法使整个装备进程愈加友爱。

4、repeatable循环动画

运用repeatable能够创立一个RepeatableSpec实例。前面所介绍的动画都是单次动画,而这儿的repeatable是一个可循环播映的动画,能够指定TweenSpec或许KeyFramesSpec以及循环播映的方法。

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = repeatable(
        iterations = 3,
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ), label = ""
)

上面的代码表明,让一个tween动画循环播映三次,循环形式是往返履行。咱们一共有两种循环形式可选:

  • RepeatMode. Reverse:往返履行,状况值到达目标值后,再原路回来到初始值。
  • RepeatMode. Restart:从头履行,状况值到达目标值后,当即从初始值从头开端履行。

留意:repeatable函数的参数animation有必要是一个DurationBasedAnimationSpec子类,spring不支撑循环播映。这是能够了解的,因为一个永动的绷簧的确违反物理规律。

5、infiniteRepeatable无限循环动画

infiniteRepeatable顾名思义,便是无限履行的RepeatableSpec,因而没有iterations参数。它将创立并回来一个InfiniteRepeatableSpec实例。

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = infiniteRepeatable(
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = ""
)

前面介绍过rememberInfiniteTransition,这是一种无限循环的Transition动画,因而它只能对无限循环的动画进行组合,它的animationSpec有必要运用infiniteRepeatable来创立。下面是一个示例代码:

@Preview
@Composable
fun InfinitRepetableDemo() {
    val infiniteTransition = rememberInfiniteTransition(label = "")
    val degrees by infiniteTransition.animateFloat(   //界说旋转角度
        initialValue = 0f,
        targetValue = 359f,
        animationSpec = infiniteRepeatable(
            animation = keyframes {
                durationMillis = 1500
                0F at 0
                359F at 1500
            }), label = ""
    )
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = "Hello Compose",
            modifier = Modifier.rotate(degrees = degrees)   //运用动画的旋转角度
        )
    }
}

UI作用

Jetpack Compose(十七)Compose动画初级别动画API

如上所述,infiniteTransition经过animateFloat增加了一个Float类型的动画,此处animationSpec有必要指定一个InfiniteRepeatableSpec类型实例,这儿创立了一个无限循环的关键帧动画。

6、snap快闪动画

snap会创立一个SnapSpec实例,这是一种特殊动画,它的targetValue发生变化时,当时值会当即更新为targetValue。因为没有中心过渡,动画会瞬间完结,常用于越过过场动画的场景。咱们也能够设置delayMillis参数来延迟动画的发动时刻。

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = snap(delayMillis = 50), label = ""
)

下面是一个示例,经过snap马上修正值

@Composable
private fun SnapAnimationExample() {
    var targetValue by remember { mutableFloatStateOf(0f) }
    //创立动画运用SnapSpec
    val animatedValue by animateValueAsState(
        targetValue = targetValue,
        typeConverter = Float.VectorConverter,
        animationSpec = snap(delayMillis = 1000), label = ""   //延迟1秒履行
    )
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        //创立一个按钮修正targetValue的值
        Button(
            onClick = {
                targetValue = if (targetValue == 0f) 1f else 0f   //点击后修正targetValue的值
            },
            modifier = Modifier.padding(8.dp)
        ) {
            Text("Toggle Animation")
        }
        Spacer(modifier = Modifier.height(16.dp))
        // Display the animated value
        //修正文本
        Text("Animated Value: $animatedValue", style = MaterialTheme.typography.bodyMedium)
    }
}

UI作用

Jetpack Compose(十七)Compose动画初级别动画API

7、运用Easing操控动画节奏

在介绍tween动画时提到过Easing。咱们知道Tween与Keyframes都是依据时刻核算的动画,Easing本质上便是一个依据时刻参数的函数(实践是一个单办法接口),它的输入和输出都是0f~1f的浮点数值。

package androidx.compose.animation.core
@Stable  
fun interface Easing {  
    fun transform(fraction: Float): Float  
}

输入值表明当时动画在时刻上的进展,回来值是则是当时value的进展,1.0表明已经到达targetValue。不同的Easing算法能够完结不同的动画加快、减速作用,因而也能够将Easing了解为动画的瞬时速度。Compose内部供给了多种内置的Easing曲线,可满足大多数的运用场景,如下图所示。

Jetpack Compose(十七)Compose动画初级别动画API

另外还能够运用CubicBezierEasing三阶贝塞尔曲线自界说恣意Easing,上述几种预设的曲线也都是运用CubicBezierEasing完结的。

val FastOutSlowInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)
val LinearOutSlowInEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)  
val FastOutLinearInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f)  
val LinearEasing: Easing = Easing { fraction -> fraction }

四、AnimationVector动画矢量值

矢量动画是依据动画矢量值AnimationVector核算的。前面的章节中咱们了解到,animae*AsState依据Animatable将Color、Float、Dp等数据类型的数值转化成可动画类型,其本质便是将这些数据类型转化成AnimationVector参加动画核算。

Animatable的构造函数有三个参数:

@Suppress("NotCloseable")
class Animatable<T, V : AnimationVector>(  
    initialValue: T,     //T类型的动画初始值
    val typeConverter: TwoWayConverter<T, V>,   //将T类型的数值与V类型的AnimationVector进行转化
    private val visibilityThreshold: T? = null,   //动画消失的阈值,默以为null
    val label: String = "Animatable"  
) {..}

1、TwoWayConverter

源码如下:

interface TwoWayConverter<T, V : AnimationVector> {
    val convertToVector: (T) -> V   
    val convertFromVector: (V) -> T  
}

从TwoWayConverter接口界说能够看出,它能够将恣意T类型的数值转化为规范的AnimationVector,反之亦然。这样,任何数值类型都能够随着动画改动数值。

不同类型的数值能够依据需求与不同的AnimationVectorXD进行转化,这儿的X代表了信息的维度。例如一个Int能够与AnimationVector1D相互转化,AnimationVector1D只包括一个浮点数信息。

private val IntToVector: TwoWayConverter<Int, AnimationVector1D> =
TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })

相同,Size中包括width和height两个维度的信息,能够与AnimationVector2D进行转化,Color中包括red、green、blue和alpha 4个数值,能够与AnimatIonVector4D进行转化。当然Compose已经为常用类型供给了TwoWayConverter的拓宽完结,能够在这些类型的伴生目标中找到它们,而且能够在animate*AsState中直接运用。

package androidx.compose.animation.core
val Float.Companion.VectorConverter: TwoWayConverter<Float, AnimationVector1D>  
get() = FloatToVector  
val Int.Companion.VectorConverter: TwoWayConverter<Int, AnimationVector1D>  
get() = IntToVector
val Rect.Companion.VectorConverter: TwoWayConverter<Rect, AnimationVector4D>  
get() = RectToVector  
val Dp.Companion.VectorConverter: TwoWayConverter<Dp, AnimationVector1D>  
get() = DpToVector  
val DpOffset.Companion.VectorConverter: TwoWayConverter<DpOffset, AnimationVector2D>  
get() = DpOffsetToVector  
val Size.Companion.VectorConverter: TwoWayConverter<Size, AnimationVector2D>  
get() = SizeToVector  
val Offset.Companion.VectorConverter: TwoWayConverter<Offset, AnimationVector2D>  
get() = OffsetToVector  
val IntOffset.Companion.VectorConverter: TwoWayConverter<IntOffset, AnimationVector2D>  
get() = IntOffsetToVector  
val IntSize.Companion.VectorConverter: TwoWayConverter<IntSize, AnimationVector2D>  
get() = IntSizeToVector

2、自界说完结TwoWayConverter

关于没有供给默许支撑的数据类型,能够为其自界说对应的TwoWayConverter。例如针对MySize这个自界说类型来自界说完结TwoWayConverter,然后运用animateValueAsState为MySize增加动画作用。

data class MySize(val width: Dp, val height: Dp)
@Composable
fun MyAnimation(targetSize: MySize) {
    val animSize: MySize by animateValueAsState<MySize, AnimationVector2D>(
        targetSize,
        TwoWayConverter(
            convertToVector = { size: MySize ->
                //Extract a float value from each of the 'Dp  fields
                AnimationVector2D(size.width.value, size.height.value)
            },
            convertFromVector = { vector: AnimationVector2D ->
                MySize(vector.v1.dp, vector.v2.dp)
            }), label = ""
    )
}

参考资料

本文为学习博客,内容来自书籍《Jetpack Compose 从入门到实战》,代码为详细实践。致谢!