feat: 转场动画优化 & 任务界面 MD3E 适配 & 待办卡片样式更新

This commit is contained in:
Super12138 2026-02-05 20:43:03 +08:00
parent 2b28b446eb
commit 5b87a17a82
8 changed files with 182 additions and 93 deletions

View file

@ -38,7 +38,7 @@ android {
applicationId = "cn.super12138.todo" applicationId = "cn.super12138.todo"
minSdk = 24 minSdk = 24
targetSdk = 36 targetSdk = 36
versionCode = 950 versionCode = 951
versionName = "2.3.3" versionName = "2.3.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

View file

@ -21,7 +21,7 @@ object TodoDefaults {
/** /**
* 待办卡片默认高度 * 待办卡片默认高度
*/ */
val toDoCardHeight = 80.dp val toDoCardHeight = 86.dp
/** /**
* 设置项水平边距 * 设置项水平边距

View file

@ -2,11 +2,6 @@ package cn.super12138.todo.ui.navigation
import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.SharedTransitionLayout import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.animation.unveilIn
import androidx.compose.animation.veilOut
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -17,8 +12,13 @@ import cn.super12138.todo.ui.pages.editor.TodoAddPage
import cn.super12138.todo.ui.pages.editor.TodoEditPage import cn.super12138.todo.ui.pages.editor.TodoEditPage
import cn.super12138.todo.ui.pages.overview.OverviewPage import cn.super12138.todo.ui.pages.overview.OverviewPage
import cn.super12138.todo.ui.pages.tasks.TasksPage import cn.super12138.todo.ui.pages.tasks.TasksPage
import cn.super12138.todo.ui.theme.fadeThrough
import cn.super12138.todo.ui.viewmodels.MainViewModel import cn.super12138.todo.ui.viewmodels.MainViewModel
/**
* 来自https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/transition/MaterialFadeThrough.java#L33
*/
@OptIn(ExperimentalAnimationApi::class) @OptIn(ExperimentalAnimationApi::class)
@Composable @Composable
fun TopNavigation( fun TopNavigation(
@ -53,12 +53,12 @@ fun TopNavigation(
* } * }
*/ */
val veilColor = MaterialTheme.colorScheme.surfaceDim // val veilColor = MaterialTheme.colorScheme.surfaceDim
SharedTransitionLayout { SharedTransitionLayout {
NavDisplay( NavDisplay(
backStack = backStack.backStack, backStack = backStack.backStack,
onBack = ::onBack, onBack = ::onBack,
transitionSpec = { /*transitionSpec = {
fadeIn() togetherWith veilOut(targetColor = veilColor) fadeIn() togetherWith veilOut(targetColor = veilColor)
}, },
popTransitionSpec = { popTransitionSpec = {
@ -66,6 +66,15 @@ fun TopNavigation(
}, },
predictivePopTransitionSpec = { predictivePopTransitionSpec = {
unveilIn(initialColor = veilColor) togetherWith fadeOut() unveilIn(initialColor = veilColor) togetherWith fadeOut()
},*/
transitionSpec = {
fadeThrough()
},
popTransitionSpec = {
fadeThrough()
},
predictivePopTransitionSpec = {
fadeThrough()
}, },
entryProvider = entryProvider { entryProvider = entryProvider {
entry<TodoScreen.Overview> { entry<TodoScreen.Overview> {

View file

@ -13,23 +13,20 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation3.ui.LocalNavAnimatedContentScope import androidx.navigation3.ui.LocalNavAnimatedContentScope
@ -45,9 +42,9 @@ import cn.super12138.todo.ui.components.TopAppBarScaffold
import cn.super12138.todo.ui.pages.editor.components.TodoCategoryChip import cn.super12138.todo.ui.pages.editor.components.TodoCategoryChip
import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField import cn.super12138.todo.ui.pages.editor.components.TodoCategoryTextField
import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField import cn.super12138.todo.ui.pages.editor.components.TodoContentTextField
import cn.super12138.todo.ui.pages.editor.components.TodoMarkAsCompletedCheckbox
import cn.super12138.todo.ui.pages.editor.components.TodoPrioritySlider import cn.super12138.todo.ui.pages.editor.components.TodoPrioritySlider
import cn.super12138.todo.ui.pages.editor.state.rememberEditorState import cn.super12138.todo.ui.pages.editor.state.rememberEditorState
import cn.super12138.todo.utils.VibrationUtils
@Composable @Composable
fun SharedTransitionScope.TodoAddPage( fun SharedTransitionScope.TodoAddPage(
@ -92,8 +89,6 @@ private fun TodoEditorPage(
onNavigateUp: () -> Unit onNavigateUp: () -> Unit
) { ) {
// TODO: 本页及其相关组件重组性能检查优化 // TODO: 本页及其相关组件重组性能检查优化
val view = LocalView.current
val uiState = rememberEditorState(initialTodo = toDo) val uiState = rememberEditorState(initialTodo = toDo)
val originalCategories by DataStoreManager.categoriesFlow.collectAsState(initial = emptyList()) val originalCategories by DataStoreManager.categoriesFlow.collectAsState(initial = emptyList())
@ -180,11 +175,11 @@ private fun TodoEditorPage(
verticalArrangement = Arrangement.spacedBy(5.dp), verticalArrangement = Arrangement.spacedBy(5.dp),
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
item { item(key = 0) {
Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding)) Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding))
} }
item { item(key=1) {
TodoContentTextField( TodoContentTextField(
value = uiState.toDoContent, value = uiState.toDoContent,
onValueChange = { uiState.toDoContent = it }, onValueChange = { uiState.toDoContent = it },
@ -193,7 +188,7 @@ private fun TodoEditorPage(
) )
} }
item { item(key = 2) {
Text( Text(
text = stringResource(R.string.label_category), text = stringResource(R.string.label_category),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
@ -222,7 +217,7 @@ private fun TodoEditorPage(
} }
} }
item { item (key = 3){
Text( Text(
text = stringResource(R.string.label_priority), text = stringResource(R.string.label_priority),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
@ -234,34 +229,22 @@ private fun TodoEditorPage(
) )
} }
item { item(key = 4) {
if (toDo != null) { if (toDo != null) {
Text( Text(
text = stringResource(R.string.label_more), text = stringResource(R.string.label_more),
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
Row( TodoMarkAsCompletedCheckbox(
verticalAlignment = Alignment.CenterVertically, checked = uiState.isCompleted,
onCheckedChange = { uiState.isCompleted = it },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { )
Text(
text = stringResource(R.string.tip_mark_completed),
style = MaterialTheme.typography.labelLarge,
modifier = Modifier.padding(end = 10.dp)
)
Switch(
checked = uiState.isCompleted,
onCheckedChange = {
VibrationUtils.performHapticFeedback(view)
uiState.isCompleted = it
}
)
}
} }
} }
item { item (key = 5){
Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding)) Spacer(modifier = Modifier.size(TodoDefaults.screenVerticalPadding))
} }
} }

View file

@ -0,0 +1,59 @@
package cn.super12138.todo.ui.pages.editor.components
import android.view.HapticFeedbackConstants
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.selection.toggleable
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import cn.super12138.todo.R
import cn.super12138.todo.utils.VibrationUtils
@Composable
fun TodoMarkAsCompletedCheckbox(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier
) {
val view = LocalView.current
val interactionSource = remember { MutableInteractionSource() }
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier
.toggleable(
value = checked,
onValueChange = {
VibrationUtils.performHapticFeedback(view, HapticFeedbackConstants.LONG_PRESS)
onCheckedChange(it)
},
role = Role.Checkbox,
indication = null,
interactionSource = interactionSource,
)
) {
Text(
text = stringResource(R.string.tip_mark_completed),
style = MaterialTheme.typography.labelLarge
)
Checkbox(
checked = checked,
onCheckedChange = {
VibrationUtils.performHapticFeedback(view, HapticFeedbackConstants.LONG_PRESS)
onCheckedChange(it)
},
interactionSource = interactionSource
)
}
}

View file

@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -27,7 +27,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation3.ui.LocalNavAnimatedContentScope import androidx.navigation3.ui.LocalNavAnimatedContentScope
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants import cn.super12138.todo.constants.Constants
@ -103,7 +102,7 @@ fun SharedTransitionScope.TasksPage(
) { ) {
LazyColumn( LazyColumn(
state = listState, state = listState,
verticalArrangement = Arrangement.spacedBy(5.dp), verticalArrangement = Arrangement.spacedBy(TodoDefaults.settingsItemPadding),
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
item { item {
@ -120,32 +119,34 @@ fun SharedTransitionScope.TasksPage(
) )
} }
} else { } else {
items( itemsIndexed(
items = filteredTodoList, items = filteredTodoList,
key = { it.id } key = { _, task -> task.id }
) { ) { index, task ->
TodoCard( TodoCard(
// id = item.id, // id = item.id,
content = it.content, content = task.content,
category = it.category, category = task.category,
completed = it.isCompleted, completed = task.isCompleted,
priority = Priority.fromFloat(it.priority), priority = Priority.fromFloat(task.priority),
selected = selectedTodoIds.contains(it.id), selected = selectedTodoIds.contains(task.id),
onCardClick = { onCardClick = {
if (inSelectedMode) { if (inSelectedMode) {
viewModel.toggleTodoSelection(it) viewModel.toggleTodoSelection(task)
} else { } else {
toTodoEditPage(it) toTodoEditPage(task)
} }
}, },
onCardLongClick = { viewModel.toggleTodoSelection(it) }, onCardLongClick = { viewModel.toggleTodoSelection(task) },
onChecked = { onChecked = {
viewModel.updateTodo(it.copy(isCompleted = true)) viewModel.updateTodo(task.copy(isCompleted = true))
viewModel.playConfetti() viewModel.playConfetti()
}, },
topRounded = index == 0,
bottomRounded = index == filteredTodoList.size - 1,
modifier = Modifier modifier = Modifier
.sharedBounds( .sharedBounds(
sharedContentState = rememberSharedContentState(key = "${Constants.KEY_TODO_ITEM_TRANSITION}_${it.id}"), sharedContentState = rememberSharedContentState(key = "${Constants.KEY_TODO_ITEM_TRANSITION}_${task.id}"),
animatedVisibilityScope = LocalNavAnimatedContentScope.current animatedVisibilityScope = LocalNavAnimatedContentScope.current
) )
.animateItem( .animateItem(

View file

@ -1,6 +1,7 @@
package cn.super12138.todo.ui.pages.tasks.components package cn.super12138.todo.ui.pages.tasks.components
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.expandHorizontally import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
@ -17,10 +18,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.material3.Badge import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -28,6 +27,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -42,6 +42,7 @@ import cn.super12138.todo.logic.model.Priority
import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.TodoDefaults
import cn.super12138.todo.utils.VibrationUtils import cn.super12138.todo.utils.VibrationUtils
import cn.super12138.todo.utils.containerColor import cn.super12138.todo.utils.containerColor
import cn.super12138.todo.utils.getPartialRoundedShape
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@ -55,19 +56,22 @@ fun TodoCard(
selected: Boolean, selected: Boolean,
onCardClick: () -> Unit = {}, onCardClick: () -> Unit = {},
onCardLongClick: () -> Unit = {}, onCardLongClick: () -> Unit = {},
onChecked: () -> Unit = {} onChecked: () -> Unit = {},
shape: CornerBasedShape = TodoDefaults.SettingsItemDefaultShape,
roundedShape: CornerBasedShape = TodoDefaults.SettingsItemRoundedShape,
topRounded: Boolean = false,
bottomRounded: Boolean = false
) { ) {
val view = LocalView.current val view = LocalView.current
Card( // TODO: 滑动删除
colors = CardDefaults.cardColors(containerColor = TodoDefaults.ContainerColor), val animatedContainerColor by animateColorAsState(if (selected) MaterialTheme.colorScheme.secondaryContainer else TodoDefaults.ContainerColor)
modifier = modifier
.fillMaxWidth()
.height(TodoDefaults.toDoCardHeight)
) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = modifier
.fillMaxSize() .fillMaxWidth()
.height(TodoDefaults.toDoCardHeight)
.clip(shape.getPartialRoundedShape(topRounded, bottomRounded, roundedShape))
.combinedClickable( .combinedClickable(
onClick = { onClick = {
VibrationUtils.performHapticFeedback(view) VibrationUtils.performHapticFeedback(view)
@ -77,7 +81,8 @@ fun TodoCard(
// 因为 combinedClickable 在更新的 Compose 里已经处理好了触感反馈 // 因为 combinedClickable 在更新的 Compose 里已经处理好了触感反馈
onLongClick = onCardLongClick onLongClick = onCardLongClick
) )
.padding(horizontal = 15.dp) .background(animatedContainerColor)
.padding(horizontal = TodoDefaults.screenHorizontalPadding)
) { ) {
AnimatedVisibility( AnimatedVisibility(
visible = selected, visible = selected,
@ -104,40 +109,42 @@ fun TodoCard(
} }
Column( Column(
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.spacedBy(
space = 5.dp,
alignment = Alignment.CenterVertically
),
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.fillMaxSize() .fillMaxSize()
) { ) {
BadgedBox( Text(
badge = { text = content,
Badge( style = MaterialTheme.typography.titleLarge,
containerColor = priority.containerColor(), maxLines = 1,
modifier = Modifier.padding(start = 5.dp) overflow = TextOverflow.Ellipsis,
) { textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None,
Text( modifier = Modifier.basicMarquee() // TODO: 后续评估性能影响
text = stringResource(priority.nameRes), )
textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None,
) Row(
} verticalAlignment = Alignment.CenterVertically,
} horizontalArrangement = Arrangement.spacedBy(5.dp)
) { ) {
Badge(containerColor = MaterialTheme.colorScheme.primary) {
Text(
text = category.ifEmpty { stringResource(R.string.tip_default_category) },
style = MaterialTheme.typography.labelMedium,
textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None,
maxLines = 1
)
}
Text( Text(
text = content, text = stringResource(priority.nameRes),
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.labelMedium.copy(priority.containerColor()),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None,
modifier = Modifier.basicMarquee() // TODO: 后续评估性能影响
) )
} }
Text(
text = category.ifEmpty { stringResource(R.string.tip_default_category) },
style = MaterialTheme.typography.labelMedium,
textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None,
maxLines = 1
)
} }
AnimatedVisibility(!selected && !completed) { AnimatedVisibility(!selected && !completed) {
@ -172,7 +179,7 @@ fun TodoCard(
) )
}*/ }*/
} }
}
} }
/* /*

View file

@ -9,6 +9,7 @@ import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutHorizontally
@ -17,6 +18,8 @@ import androidx.compose.animation.slideOutHorizontally
*/ */
private const val ProgressThreshold = 0.35f private const val ProgressThreshold = 0.35f
private const val DEFAULT_START_SCALE = 0.95f //0.92f
private const val THRESHOLD_ALPHA = 0.5f
private val Int.ForOutgoing: Int private val Int.ForOutgoing: Int
get() = (this * ProgressThreshold).toInt() get() = (this * ProgressThreshold).toInt()
@ -84,4 +87,31 @@ fun materialSharedAxisXOut(
delayMillis = 0, delayMillis = 0,
easing = FastOutLinearInEasing easing = FastOutLinearInEasing
) )
)
fun fadeThrough(
durationMillis: Int = 200,
): ContentTransform = ContentTransform(
fadeThroughIn(durationMillis = durationMillis),
fadeThroughOut(durationMillis = durationMillis)
)
fun fadeThroughIn(
durationMillis: Int = DefaultMotionDuration,
): EnterTransition =
fadeIn(
animationSpec = tween(durationMillis),
initialAlpha = THRESHOLD_ALPHA
) + scaleIn(
animationSpec = tween(durationMillis),
initialScale = DEFAULT_START_SCALE
)
fun fadeThroughOut(
durationMillis: Int = DefaultMotionDuration,
): ExitTransition = fadeOut(
animationSpec = tween(
durationMillis = durationMillis,
easing = FastOutLinearInEasing
)
) )