feat(main, editor): 更加流畅的待办内容动画

This commit is contained in:
Super12138 2025-02-06 08:41:39 +08:00
parent 4d432935b3
commit de27d5ddb5
6 changed files with 68 additions and 28 deletions

View file

@ -5,6 +5,7 @@ object Constants {
const val GITHUB_REPO = "https://github.com/Super12138/ToDo/" const val GITHUB_REPO = "https://github.com/Super12138/ToDo/"
const val KEY_TODO_FAB_TRANSITION = "todo_fab" const val KEY_TODO_FAB_TRANSITION = "todo_fab"
const val KEY_TODO_CONTENT_TRANSITION = "todo_content"
const val SP_NAME = "cn.super12138.todo_preferences" const val SP_NAME = "cn.super12138.todo_preferences"

View file

@ -70,7 +70,7 @@ fun TodoNavigation(
toDo = viewModel.selectedEditTodo, toDo = viewModel.selectedEditTodo,
onSave = { onSave = {
viewModel.addTodo(it) viewModel.addTodo(it)
viewModel.setEditTodoItem(null) // viewModel.setEditTodoItem(null)
navController.navigateUp() navController.navigateUp()
}, },
onDelete = { onDelete = {
@ -82,7 +82,7 @@ fun TodoNavigation(
}, },
onNavigateUp = { onNavigateUp = {
navController.navigateUp() navController.navigateUp()
viewModel.setEditTodoItem(null) // viewModel.setEditTodoItem(null)
}, },
sharedTransitionScope = this@SharedTransitionLayout, sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this@composable animatedVisibilityScope = this@composable

View file

@ -66,7 +66,7 @@ fun TodoEditorPage(
animatedVisibilityScope: AnimatedVisibilityScope, animatedVisibilityScope: AnimatedVisibilityScope,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
BackHandler { BackHandler { // TODO: 实现用户更改后的阻止返回
onNavigateUp() onNavigateUp()
} }
val context = LocalContext.current val context = LocalContext.current
@ -141,18 +141,25 @@ fun TodoEditorPage(
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
TextField( with(sharedTransitionScope) {
value = text, TextField(
onValueChange = { text = it }, value = text,
label = { Text(stringResource(R.string.placeholder_add_todo)) }, onValueChange = { text = it },
isError = isError, label = { Text(stringResource(R.string.placeholder_add_todo)) },
supportingText = { isError = isError,
AnimatedVisibility(isError) { supportingText = {
Text(stringResource(R.string.error_no_task_content)) AnimatedVisibility(isError) {
} Text(stringResource(R.string.error_no_task_content))
}, }
modifier = Modifier.fillMaxWidth() },
) modifier = Modifier
.fillMaxWidth()
.sharedBounds(
sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_${toDo?.id}"),
animatedVisibilityScope = animatedVisibilityScope
)
)
}
Spacer(Modifier.size(5.dp)) Spacer(Modifier.size(5.dp))

View file

@ -95,7 +95,10 @@ fun MainPage(
) { ) {
TodoFAB( TodoFAB(
expanded = isExpanded, expanded = isExpanded,
onClick = toTodoEditPage, onClick = {
viewModel.setEditTodoItem(null) // 每次添加待办前清除上一次已选待办
toTodoEditPage()
},
modifier = Modifier.sharedElement( modifier = Modifier.sharedElement(
state = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION), state = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION),
animatedVisibilityScope = animatedVisibilityScope animatedVisibilityScope = animatedVisibilityScope
@ -146,6 +149,8 @@ fun MainPage(
} }
}, },
selectedTodoIds = selectedTodoIds.value, selectedTodoIds = selectedTodoIds.value,
sharedTransitionScope = sharedTransitionScope,
animatedVisibilityScope = animatedVisibilityScope,
modifier = Modifier modifier = Modifier
.weight(3f) .weight(3f)
.fillMaxSize() .fillMaxSize()
@ -189,6 +194,8 @@ fun MainPage(
} }
}, },
selectedTodoIds = selectedTodoIds.value, selectedTodoIds = selectedTodoIds.value,
sharedTransitionScope = sharedTransitionScope,
animatedVisibilityScope = animatedVisibilityScope,
modifier = Modifier modifier = Modifier
.weight(3f) .weight(3f)
.fillMaxSize() .fillMaxSize()

View file

@ -1,5 +1,8 @@
package cn.super12138.todo.ui.pages.main package cn.super12138.todo.ui.pages.main
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@ -24,6 +27,7 @@ import cn.super12138.todo.ui.pages.main.components.TodoCard
import my.nanihadesuka.compose.LazyColumnScrollbar import my.nanihadesuka.compose.LazyColumnScrollbar
import my.nanihadesuka.compose.ScrollbarSettings import my.nanihadesuka.compose.ScrollbarSettings
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable @Composable
fun ManagerFragment( fun ManagerFragment(
state: LazyListState, state: LazyListState,
@ -32,6 +36,8 @@ fun ManagerFragment(
onItemLongClick: (TodoEntity) -> Unit = {}, onItemLongClick: (TodoEntity) -> Unit = {},
onItemChecked: (TodoEntity) -> Unit = {}, onItemChecked: (TodoEntity) -> Unit = {},
selectedTodoIds: List<Int>, selectedTodoIds: List<Int>,
sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val context = LocalContext.current val context = LocalContext.current
@ -66,6 +72,7 @@ fun ManagerFragment(
key = { it.id } key = { it.id }
) { item -> ) { item ->
TodoCard( TodoCard(
id = item.id,
content = item.content, content = item.content,
subject = Subjects.fromId(item.subject).getDisplayName(context), subject = Subjects.fromId(item.subject).getDisplayName(context),
completed = item.isCompleted, completed = item.isCompleted,
@ -74,6 +81,8 @@ fun ManagerFragment(
onCardClick = { onItemClick(item) }, onCardClick = { onItemClick(item) },
onCardLongClick = { onItemLongClick(item) }, onCardLongClick = { onItemLongClick(item) },
onChecked = { onItemChecked(item) }, onChecked = { onItemChecked(item) },
sharedTransitionScope = sharedTransitionScope,
animatedVisibilityScope = animatedVisibilityScope,
modifier = Modifier modifier = Modifier
.padding(vertical = 5.dp) .padding(vertical = 5.dp)
.animateItem() // TODO: 设置动画时间 .animateItem() // TODO: 设置动画时间

View file

@ -1,6 +1,9 @@
package cn.super12138.todo.ui.pages.main.components package cn.super12138.todo.ui.pages.main.components
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.basicMarquee
@ -31,14 +34,15 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants
import cn.super12138.todo.logic.model.Priority import cn.super12138.todo.logic.model.Priority
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class)
@Composable @Composable
fun TodoCard( fun TodoCard(
id: Int,
content: String, content: String,
subject: String, subject: String,
completed: Boolean, completed: Boolean,
@ -47,6 +51,8 @@ fun TodoCard(
onCardClick: () -> Unit = {}, onCardClick: () -> Unit = {},
onCardLongClick: () -> Unit = {}, onCardLongClick: () -> Unit = {},
onChecked: () -> Unit = {}, onChecked: () -> Unit = {},
sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val context = LocalContext.current val context = LocalContext.current
@ -103,14 +109,21 @@ fun TodoCard(
} }
} }
) { ) {
Text( with(sharedTransitionScope) {
text = content, Text(
style = MaterialTheme.typography.titleLarge, text = content,
maxLines = 1, style = MaterialTheme.typography.titleLarge,
overflow = TextOverflow.Ellipsis, maxLines = 1,
textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None, overflow = TextOverflow.Ellipsis,
modifier = Modifier.basicMarquee() // TODO: 后续评估性能影响 textDecoration = if (completed) TextDecoration.LineThrough else TextDecoration.None,
) modifier = Modifier
.sharedBounds(
sharedContentState = rememberSharedContentState("${Constants.KEY_TODO_CONTENT_TRANSITION}_$id"),
animatedVisibilityScope = animatedVisibilityScope
)
.basicMarquee() // TODO: 后续评估性能影响
)
}
} }
Text( Text(
@ -151,6 +164,7 @@ fun TodoCard(
} }
} }
/*
@Preview(locale = "zh-rCN", showBackground = true) @Preview(locale = "zh-rCN", showBackground = true)
@Composable @Composable
private fun TodoCardPreview() { private fun TodoCardPreview() {
@ -162,6 +176,8 @@ private fun TodoCardPreview() {
selected = false, selected = false,
onCardClick = {}, onCardClick = {},
onCardLongClick = {}, onCardLongClick = {},
onChecked = {} onChecked = {},
sharedTransitionScope = ,
animatedVisibilityScope =
) )
} }*/