持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情
我们在传统的Android开发中编写UI动画需要在drawable文件中定义一大堆动画xml文件,在Acitivity设置动画属性并开启,但是在Compose中定义动画就没有这么复杂,接下来就介绍一下Compose中的各种动画Api
Animatable
Animatable是一个基于携程的API,用于设置单个值的动画。我们可以使用此API设置颜色或浮点值的动画。它不同于所有其他动画API,因为您可以在可组合函数之外使用此API。
@Composable
private fun AnimatableSample() {
var isAnimated by remember { mutableStateOf(false) }
val color = remember { Animatable(Color.DarkGray) }
LaunchedEffect(isAnimated) {
color.animateTo(if (isAnimated) Color.Green else Color.Red, animationSpec = tween(2000))
}
Box(
Modifier
.fillMaxWidth()
.fillMaxHeight(0.8f)
.background(color.value)
)
Button(
onClick = { isAnimated = !isAnimated },
modifier = Modifier.padding(top = 10.dp)
) {
Text(text = "Animate Color")
}
}
在之前我们不是学习过Compose的函数副作用吗,在 LaunchedEffect中,我们给出了一个基于 isAnimated值更改颜色的条件。当您按下按钮时,LaunchedEffect中的block就会改变color的颜色值,从红色切换到绿色,为了让大家看到动画效果我们给颜色切换加了间隔持续时间,使用到了tween(),我们还可以使用以下Api来实现自定义动画
-
tween@Stable fun <T> tween( durationMillis: Int = DefaultDurationMillis, //动画持续时间 delayMillis: Int = 0, //动画延时开始时间 easing: Easing = FastOutSlowInEasing //使用缓和曲线在起始值和结束值之间添加动画 ): TweenSpec<T> = TweenSpec(durationMillis, delayMillis, easing) -
keyframeskeframes可以针对不同时间范围的动画帧添加动画效果@Composable fun KeyframesScreen(){ 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 } ) } -
spring@Stable fun <T> spring( dampingRatio: Float = Spring.DampingRatioNoBouncy, //弹簧的阻尼,也就是弹性 阻尼比越低,弹簧越有弹性 stiffness: Float = Spring.StiffnessMedium, //弹簧像结束值步进的速度 visibilityThreshold: T? = null ): SpringSpec<T> = SpringSpec(dampingRatio, stiffness, visibilityThreshold)spring的动画效果和弹簧的感觉非常像 -
repeatable//执行一个重复性动画 @Stable fun <T> repeatable( iterations: Int, //重复次数 animation: DurationBasedAnimationSpec<T>, //重复动画 repeatMode: RepeatMode = RepeatMode.Restart, //重复模式 initialStartOffset: StartOffset = StartOffset(0) //动画的启示偏移时长 ): RepeatableSpec<T> = RepeatableSpec(iterations, animation, repeatMode, initialStartOffset) -
infiniteRepeatableinfiniteRepeatable其实和repeatable非常相似,差别就在于一个是有重复次数,一个是无限重复的动画 -
snapsnap主要用于需要提前结束动画@Stable fun <T> snap( delayMillis: Int = 0 //延时毫秒数 ) = SnapSpec<T>(delayMillis)
animate*AsState
Animate*AsState主要用于为单个值提供动画,这些值可以是Dp、Color、Float、Integer、Offset、Rect、Size
AnimateDpAsState主要用于widget的长度或者高度变化的动画添加
@Composable
fun animateDpAsState (
targetValue : Dp ,
animationSpec : AnimationSpec < Dp > = dpDefaultSpring ,
finishedListener : ( ( Dp ) - > Unit ) ? = null
)
同样也可以为其提供动画效果animationSpec
fun SearchBoxForState(
focusState: MutableState<Boolean>,
modifier: Modifier,
onSearchEvent: () -> Unit,
onClick: () -> Unit
) {
val searchBoxWidth by animateDpAsState(targetValue = if (focusState.value) 679.wdp else 340.wdp, animationSpec = tween())
SearchBox(
searchBoxWidth = searchBoxWidth,
isShowClean = focusState,
modifier = modifier,
onSearchEvent = onSearchEvent,
onClick = onClick
)
}
AnimateColorAsState 为颜色的变化添加过渡动画
@Composable
fun HomeScreen(){
val bgColor by animateColorAsState(if (index % 2 == 0) whiteFFFFFFFF else grayF9F9F9)
ConstraintLayout(
modifier = modifier
.fillMaxWidth()
.height(71.hdp)
.background(bgColor)
){
.....
}
}
AnimateFloatAsState animateDpAsState() 相同。但唯一的区别是我们将浮点值作为目标值。
例如为颜色透明度添加过渡动画等等
@Composable
fun HomeScreen(){
val alpha by animateFloatAsState(targetValue = if (isSelect) 1.0f else 0.7f)
Text(
text = tabName,
style = TextStyle(
color = whiteFFFFFFFF.copy(alpha),
fontSize = 19.spi,
fontWeight = if (isSelect) FontWeight.Bold else FontWeight.Medium
)
)
}
updateTransition 同时制作多个动画
@Composable
fun AnimateScreen(){
//动画启停的状态
var isAnimate by remember { mutableStateOf(false) }
//updateTransitionde 定义
val transition = updateTransition(targetState = isAnimate, label = "transition")
//定义关于Box大小变化的过渡动画
val animateSize by transition.animateDp(transitionSpec ={
tween(1000)
}, label = ""){
if(it) 200.wdp else 50.wdp
}
// //定义关于Box旋转角度变化的过渡动画
val animateRotate by transition.animateFloat(transitionSpec ={
tween(1000)
}, label = ""){
if(it) 360f else 0f
}
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
Column() {
Box(modifier = Modifier
.rotate(animateRotate)
.size(animateSize)
.background(green1ACFB7)
)
TextButton(onClick = {
isAnimate = !isAnimate
}) {
Text(text = "开始")
}
}
}
}
InfiniteTransition
上面的动画效果都是执行一次就不会运行了,那我们怎么创建一个Box无限旋转的动画呢,接下来学习无限过渡的Api-----InfiniteTransition
通过InfiniteTransition创建的动画在进入可组合项时就会开始运行,直到他们从和组合项中移除才会停止
@Composable
fun InfiniteTransitionScreen(){
val infiniteTransition = rememberInfiniteTransition()
val infiniteRotate by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(200),
repeatMode = RepeatMode.Restart //重复模式Restart是重启,Reverses是反转
)
)
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
Column() {
Box(modifier = Modifier
.rotate(infiniteRotate)
.size(100.wdp)
.background(green1ACFB7)
)
}
}
}
我们不仅能使用infiniteTransition.animateFloat创建子动画,还可以使用InfiniteTransition.animateColor、InfiniteTransition.animateValue这些方式来创建一个无限运行的子动画
更多的详情可以参考官方文档