这一节主要了解一下LinearOutSlowInEasing,在Jetpack Compose中,LinearOutSlowInEasing是一个预定义的缓动函数,用于控制动画的速度变化曲线,使动画看起来更自然、流畅以符合特定交互预期,简单总结如下:
API
Kotlin
val LinearOutSlowInEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)
本质是三次贝塞尔曲线实现的缓动函数,参数(0f,0f,0.2f,1f)定义了速率变化轨迹:
动画前半段:速率接近匀速(Linear Out);
动画后半段:速率逐渐降低(Slow In),结束时平滑停止。
场景:
1 抽屉式导航栏、底部弹窗、列表项展开/折叠。
2 按钮点击反馈、图片浏览器的缩略图展开、卡片选中效果。
3 按钮状态切换(启用/禁用)、页面背景渐变、通知栏淡入淡出。
栗子:
Kotlin
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.size
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
@Composable
fun ProgressBarDemo() {
val progress = remember { Animatable(0f) }
val coroutineScope = rememberCoroutineScope()
LaunchedEffect(Unit) {
coroutineScope.launch {
progress.animateTo(
targetValue = 1f,
animationSpec = tween(
durationMillis = 1500,
easing = LinearOutSlowInEasing
)
)
}
}
Canvas(modifier = Modifier.size(100.dp)) {
drawCircle(
color = Color.LightGray,
radius = size.minDimension / 2 - 4.dp.toPx(),
style = Stroke(width = 8.dp.toPx())
)
drawArc(
color = Color.Blue,
startAngle = -90f,
sweepAngle = progress.value * 360f,
useCenter = false,
style = Stroke(width = 8.dp.toPx(), cap = StrokeCap.Round)
)
}
}
Kotlin
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Menu
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DrawerSidebarDemo() {
var isDrawerOpen by remember { mutableStateOf(false) }
val drawerOffset by animateDpAsState(
targetValue = if (isDrawerOpen) 0.dp else -280.dp,
animationSpec = tween(
durationMillis = 400,
easing = LinearOutSlowInEasing
)
)
val contentScale by animateFloatAsState(
targetValue = if (isDrawerOpen) 0.95f else 1f,
animationSpec = tween(
durationMillis = 400,
easing = LinearOutSlowInEasing
)
)
val contentAlpha by animateFloatAsState(
targetValue = if (isDrawerOpen) 0.7f else 1f,
animationSpec = tween(
durationMillis = 400,
easing = LinearOutSlowInEasing
)
)
val contentShadow by animateDpAsState(
targetValue = if (isDrawerOpen) 10.dp else 0.dp,
animationSpec = tween(
durationMillis = 400,
easing = LinearOutSlowInEasing
)
)
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.width(280.dp)
.fillMaxHeight()
.offset(x = drawerOffset)
.background(MaterialTheme.colorScheme.primaryContainer)
.padding(20.dp)
) {
Text("侧边菜单", style = MaterialTheme.typography.headlineSmall)
Spacer(modifier = Modifier.height(30.dp))
listOf("首页", "消息", "设置", "关于").forEach { item ->
Text(
text = item,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
style = MaterialTheme.typography.bodyLarge
)
}
}
Column(
modifier = Modifier
.fillMaxSize()
.scale(contentScale)
.alpha(contentAlpha)
.shadow(contentShadow)
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
TopAppBar(
title = { Text("主页面") },
navigationIcon = {
IconButton(onClick = { isDrawerOpen = !isDrawerOpen }) {
Icon(Icons.Outlined.Menu, contentDescription = "菜单")
}
}
)
Spacer(modifier = Modifier.height(50.dp))
Text(
text = if (isDrawerOpen) "抽屉已打开" else "点击左上角菜单打开抽屉",
style = MaterialTheme.typography.bodyLarge
)
}
}
}
注意:
1 避免与过短的动画时长搭配,LinearOutSlowInEasing的"慢入"阶段需要一定时间才能体现缓冲效果。
2 避免过度使用,在复杂列表或高频触发动画的场景,过多动画可能引发性能问题。