refactor: 优化 FAB 共享元素转场动画

This commit is contained in:
Super12138 2026-02-08 11:27:41 +08:00
parent fd2dbe7735
commit da0728d390
4 changed files with 80 additions and 31 deletions

View file

@ -2,6 +2,7 @@ package cn.super12138.todo.ui.navigation
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation3.runtime.NavKey
@ -22,28 +23,9 @@ import cn.super12138.todo.ui.pages.settings.SettingsMain
import cn.super12138.todo.ui.pages.tasks.TasksPage
import cn.super12138.todo.ui.theme.fadeThrough
import cn.super12138.todo.ui.theme.materialSharedAxisX
import cn.super12138.todo.ui.theme.veilFade
import cn.super12138.todo.ui.viewmodels.MainViewModel
private const val INITIAL_OFFSET_FACTOR = 0.10f
private fun settingsTransition() = NavDisplay.transitionSpec {
materialSharedAxisX(
initialOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() },
targetOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() }
)
} + NavDisplay.popTransitionSpec {
materialSharedAxisX(
initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() },
targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }
)
} + NavDisplay.predictivePopTransitionSpec {
materialSharedAxisX(
initialOffsetX = { -(it * INITIAL_OFFSET_FACTOR).toInt() },
targetOffsetX = { (it * INITIAL_OFFSET_FACTOR).toInt() }
)
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun TopNavigation(
@ -55,7 +37,33 @@ fun TopNavigation(
backStack.removeLast()
}
// val veilColor = MaterialTheme.colorScheme.surfaceDim
val veilColor = MaterialTheme.colorScheme.surfaceDim
fun editorTransition() = NavDisplay.transitionSpec {
veilFade(veilColor)
} + NavDisplay.popTransitionSpec {
veilFade(veilColor)
} + NavDisplay.predictivePopTransitionSpec {
veilFade(veilColor)
}
val initialOffestFactor = 0.10f
fun settingsTransition() = NavDisplay.transitionSpec {
materialSharedAxisX(
initialOffsetX = { (it * initialOffestFactor).toInt() },
targetOffsetX = { -(it * initialOffestFactor).toInt() }
)
} + NavDisplay.popTransitionSpec {
materialSharedAxisX(
initialOffsetX = { -(it * initialOffestFactor).toInt() },
targetOffsetX = { (it * initialOffestFactor).toInt() }
)
} + NavDisplay.predictivePopTransitionSpec {
materialSharedAxisX(
initialOffsetX = { -(it * initialOffestFactor).toInt() },
targetOffsetX = { (it * initialOffestFactor).toInt() }
)
}
SharedTransitionLayout {
NavDisplay(
backStack = backStack.backStack,
@ -88,7 +96,7 @@ fun TopNavigation(
)
}
entry<TodoScreen.Editor.Add> {
entry<TodoScreen.Editor.Add>(metadata = editorTransition()) {
TodoAddPage(
onSave = {
viewModel.addTodo(it)
@ -98,7 +106,7 @@ fun TopNavigation(
)
}
entry<TodoScreen.Editor.Edit> { editorArgs ->
entry<TodoScreen.Editor.Edit>(metadata = editorTransition()) { editorArgs ->
TodoEditPage(
toDo = editorArgs.toDo,
onSave = {

View file

@ -53,10 +53,13 @@ fun SharedTransitionScope.TodoAddPage(
onNavigateUp: () -> Unit
) = TodoEditorPage(
toDo = null,
modifier = modifier.sharedBounds(
sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION),
animatedVisibilityScope = LocalNavAnimatedContentScope.current
),
modifier = modifier
.sharedBounds(
sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION),
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds
)
.skipToLookaheadSize(), // 这个修饰符必须放后面
onSave = onSave,
onDelete = {},
onNavigateUp = onNavigateUp
@ -76,13 +79,14 @@ fun SharedTransitionScope.TodoEditPage(
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds
),
//TODO: 没想好加不加 .skipToLookaheadSize(),
onSave = onSave,
onDelete = onDelete,
onNavigateUp = onNavigateUp
)
@Composable
fun TodoEditorPage(
fun SharedTransitionScope.TodoEditorPage(
modifier: Modifier = Modifier,
toDo: TodoEntity? = null,
onSave: (TodoEntity) -> Unit,

View file

@ -27,7 +27,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.zIndex
import androidx.navigation3.ui.LocalNavAnimatedContentScope
import cn.super12138.todo.R
import cn.super12138.todo.constants.Constants
@ -87,7 +86,8 @@ fun SharedTransitionScope.TasksPage(
modifier = Modifier
.sharedBounds(
sharedContentState = rememberSharedContentState(key = Constants.KEY_TODO_FAB_TRANSITION),
animatedVisibilityScope = animatedVisibilityScope
animatedVisibilityScope = animatedVisibilityScope,
resizeMode = SharedTransitionScope.ResizeMode.RemeasureToBounds
)
.animateFloatingActionButton(
visible = !inSelectedMode,

View file

@ -3,6 +3,7 @@ package cn.super12138.todo.ui.theme
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.FastOutLinearInEasing
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing
@ -12,6 +13,9 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.unveilIn
import androidx.compose.animation.veilOut
import androidx.compose.ui.graphics.Color
/**
* 来自https://github.com/Ashinch/ReadYou/blob/main/app/src/main/java/me/ash/reader/ui/motion/MaterialSharedAxis.kt
@ -114,4 +118,37 @@ fun fadeThroughOut(
durationMillis = durationMillis,
easing = FastOutLinearInEasing
)
)
)
fun veilFade(
initialColor: Color,
durationMillis: Int = 200
): ContentTransform = ContentTransform(
veilFadeIn(
initialColor = initialColor,
durationMillis = durationMillis
),
veilFadeOut(
initialColor = initialColor,
durationMillis = durationMillis
)
)
@OptIn(ExperimentalAnimationApi::class)
fun veilFadeIn(
initialColor: Color,
durationMillis: Int = DefaultMotionDuration,
): EnterTransition =
fadeIn(
) + unveilIn(
initialColor = initialColor
)
@OptIn(ExperimentalAnimationApi::class)
fun veilFadeOut(
initialColor: Color,
durationMillis: Int = DefaultMotionDuration,
): ExitTransition =
fadeOut() + veilOut(
targetColor = initialColor
)