很多动画 API 通常接受用于自定义其行为的参数。
使用 AnimationSpec
参数自定义动画
大多数动画 API 允许开发者通过可选的 AnimationSpec
参数来自定义动画规范。
开发者可以使用不同类型的 AnimationSpec
来创建不同类型的动画。
使用 spring
创建基于物理特性的动画
spring
可在起始值和结束值之间创建基于物理特性的动画。它接受 2 个参数:dampingRatio
和 stiffness
。
dampingRatio
定义弹簧的弹性。默认值为 Spring.DampingRatioNoBouncy
。
ts
@Composable
private fun example1() {
val enabled = remember { mutableStateOf(true) }
val boxSize = 50
val stiffnessList = arrayListOf(Spring.StiffnessVeryLow, Spring.StiffnessLow, Spring.StiffnessMediumLow, Spring.StiffnessMedium, Spring.StiffnessHigh)
val selectedStiffness = remember {
mutableStateOf(0)
}
val dampingRatioNoBouncyTranslationX = animateFloatAsState(if (enabled.value) dipToPx(15) else screenWidth().dp.value - dipToPx(boxSize) - dipToPx(15),
label = "animateFloatAsState",
animationSpec = spring(dampingRatio = DampingRatioNoBouncy, stiffness = stiffnessList[selectedStiffness.value])
)
val dampingRatioLowBouncyTranslationX = animateFloatAsState(if (enabled.value) dipToPx(15) else screenWidth().dp.value - dipToPx(boxSize) - dipToPx(15),
label = "animateFloatAsState",
animationSpec = spring(dampingRatio = DampingRatioLowBouncy, stiffness = stiffnessList[selectedStiffness.value])
)
val dampingRatioMediumBouncyTranslationX = animateFloatAsState(if (enabled.value) dipToPx(15) else screenWidth().dp.value - dipToPx(boxSize) - dipToPx(15),
label = "animateFloatAsState",
animationSpec = spring(dampingRatio = DampingRatioMediumBouncy, stiffness = stiffnessList[selectedStiffness.value])
)
val dampingRatioHighBouncyTranslationX = animateFloatAsState(if (enabled.value) dipToPx(15) else screenWidth().dp.value - dipToPx(boxSize) - dipToPx(15),
label = "animateFloatAsState",
animationSpec = spring(dampingRatio = DampingRatioHighBouncy, stiffness = stiffnessList[selectedStiffness.value])
)
Column(modifier = Modifier.fillMaxWidth()) {
Box(
Modifier
.padding(top = 15.dp)
.size(boxSize.dp)
.graphicsLayer(translationX = dampingRatioNoBouncyTranslationX.value)
.background(Color.Red)
)
Text(text = "DampingRatioNoBouncy", modifier = Modifier.padding(all = 15.dp))
Box(
Modifier
.padding(top = 15.dp)
.size(boxSize.dp)
.graphicsLayer(translationX = dampingRatioLowBouncyTranslationX.value)
.background(Color.Red)
)
Text(text = "DampingRatioLowBouncy", modifier = Modifier.padding(all = 15.dp))
Box(
Modifier
.padding(top = 15.dp)
.size(boxSize.dp)
.graphicsLayer(translationX = dampingRatioMediumBouncyTranslationX.value)
.background(Color.Red)
)
Text(text = "DampingRatioMediumBouncy", modifier = Modifier.padding(all = 15.dp))
Box(
Modifier
.padding(top = 15.dp)
.size(boxSize.dp)
.graphicsLayer(translationX = dampingRatioHighBouncyTranslationX.value)
.background(Color.Red)
)
Text(text = "DampingRatioHighBouncy", modifier = Modifier.padding(all = 15.dp))
Text(text = "当前 stiffness (移动速度):${when(selectedStiffness.value) {
0-> "StiffnessVeryLow"
1-> "StiffnessLow"
2-> "StiffnessMediumLow"
3-> "StiffnessMedium"
4-> "StiffnessHigh"
else -> "StiffnessVeryLow"
}}", modifier = Modifier.padding(all = 15.dp))
Slider(
value = selectedStiffness.value.toFloat(),
onValueChange = { selectedStiffness.value = it.toInt() },
modifier = Modifier.padding(horizontal = 15.dp),
steps = stiffnessList.size - 2,
valueRange = 0f..stiffnessList.size - 1f
)
Button(modifier = Modifier.padding(horizontal = 15.dp), onClick = {
enabled.value = !enabled.value
}) {
Text(text = if (enabled.value) "正向" else "反向")
}
}
}
使用 tween
通过加/减速曲线在起始值和结束值之间添加动画效果
ts
@Composable
private fun example2() {
val enabled = remember { mutableStateOf(true) }
val boxSize = 50
val x1 = remember { mutableStateOf(0f) }
val y1 = remember { mutableStateOf(0f) }
val x2 = remember { mutableStateOf(1f) }
val y2 = remember { mutableStateOf(1f) }
val easingList = arrayListOf(FastOutSlowInEasing,LinearOutSlowInEasing, FastOutLinearInEasing, LinearEasing, CubicBezierEasing(x1.value, y1.value, x2.value, y2.value))
val selectedEasing = remember {
mutableStateOf(0)
}
val canvasSize = 200
val canvasSizeDp = dipToPx(dp = canvasSize)
val canvasPointSize = 20f
val translationX = animateFloatAsState(if (enabled.value) dipToPx(15) else screenWidth().dp.value - dipToPx(boxSize) - dipToPx(15),
label = "animateFloatAsState",
animationSpec = tween(durationMillis = 1000, easing = easingList[selectedEasing.value])
)
Column(modifier = Modifier.fillMaxWidth()) {
Text(text = "tween 动画", modifier = Modifier.padding(all = 15.dp))
Box(
Modifier
.padding(top = 15.dp)
.size(boxSize.dp)
.graphicsLayer(translationX = translationX.value)
.background(Color.Red)
)
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable { selectedEasing.value = 0 }) {
RadioButton(selected = selectedEasing.value == 0, onClick = { selectedEasing.value = 0 })
Text(text = "FastOutSlowInEasing")
}
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable { selectedEasing.value = 1 }) {
RadioButton(selected = selectedEasing.value == 1, onClick = { selectedEasing.value = 1 })
Text(text = "LinearOutSlowInEasing")
}
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable { selectedEasing.value = 2 }) {
RadioButton(selected = selectedEasing.value == 2, onClick = { selectedEasing.value = 2 })
Text(text = "FastOutLinearInEasing")
}
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable { selectedEasing.value = 3 }) {
RadioButton(selected = selectedEasing.value == 3, onClick = { selectedEasing.value = 3 })
Text(text = "LinearEasing")
}
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.clickable { selectedEasing.value = 4 }) {
RadioButton(selected = selectedEasing.value == 4, onClick = { selectedEasing.value = 4 })
Text(text = "CubicBezierEasing")
}
if (selectedEasing.value == 4) {
Canvas(modifier = Modifier
.padding(start = 15.dp)
.size(canvasSize.dp), onDraw = {
drawRect(color = Color.Gray)
drawCircle(color = Color.Red, radius = canvasPointSize, center = Offset((x1.value * canvasSizeDp).coerceIn(canvasPointSize, canvasSizeDp - canvasPointSize),
(canvasSizeDp - y1.value * canvasSizeDp).coerceIn(canvasPointSize, canvasSizeDp - canvasPointSize)))
drawCircle(color = Color.Blue, radius = canvasPointSize, center = Offset((x2.value * canvasSizeDp).coerceIn(canvasPointSize, canvasSizeDp - canvasPointSize),
(canvasSizeDp - y2.value * canvasSizeDp).coerceIn(canvasPointSize, canvasSizeDp - canvasPointSize)))
})
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "x1=%.1f".format(x1.value), modifier = Modifier.padding(start = 15.dp))
Slider(value = x1.value, onValueChange = { x1.value = it }, modifier = Modifier
.width(200.dp)
.padding(start = 15.dp))
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "y1=%.1f".format(y1.value), modifier = Modifier.padding(start = 15.dp))
Slider(value = y1.value, onValueChange = { y1.value = it }, modifier = Modifier
.width(200.dp)
.padding(start = 15.dp))
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "x2=%.1f".format(x2.value), modifier = Modifier.padding(start = 15.dp))
Slider(value = x2.value, onValueChange = { x2.value = it }, modifier = Modifier
.width(200.dp)
.padding(start = 15.dp))
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "y2=%.1f".format(y2.value), modifier = Modifier.padding(start = 15.dp))
Slider(value = y2.value, onValueChange = { y2.value = it }, modifier = Modifier
.width(200.dp)
.padding(start = 15.dp))
}
}
Button(modifier = Modifier.padding(horizontal = 15.dp), onClick = {
enabled.value = !enabled.value
}) {
Text(text = if (enabled.value) "正向" else "反向")
}
}
}