feat(ui): 基于 SharedTransitionLayout 重构年月视图穿梭缩放动画

This commit is contained in:
xfy 2026-07-23 15:14:20 +08:00
parent de1e3178f6
commit 53086f38e0
2 changed files with 36 additions and 11 deletions

View File

@ -6,10 +6,13 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.EnterTransition import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition import androidx.compose.animation.ExitTransition
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.Spring import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
@ -212,19 +215,23 @@ fun CalendarMonthView(
.onSizeChanged { size -> .onSizeChanged { size ->
screenWidthPx = size.width screenWidthPx = size.width
} }
) {
SharedTransitionLayout(
modifier = Modifier.fillMaxSize()
) { ) {
AnimatedContent( AnimatedContent(
targetState = isYearView, targetState = isYearView,
label = "month_year_transition", label = "month_year_transition",
transitionSpec = { transitionSpec = {
val spatialEasing = FastOutSlowInEasing
val enter = scaleIn( val enter = scaleIn(
initialScale = 0.85f, initialScale = 0.92f,
animationSpec = tween(350, easing = FastOutSlowInEasing) animationSpec = tween(380, easing = spatialEasing)
) + fadeIn(tween(350, easing = LinearOutSlowInEasing)) ) + fadeIn(tween(380, easing = spatialEasing))
val exit = scaleOut( val exit = scaleOut(
targetScale = 0.85f, targetScale = 0.92f,
animationSpec = tween(350, easing = FastOutSlowInEasing) animationSpec = tween(380, easing = spatialEasing)
) + fadeOut(tween(350, easing = FastOutSlowInEasing)) ) + fadeOut(tween(380, easing = spatialEasing))
enter togetherWith exit enter togetherWith exit
}, },
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
@ -289,7 +296,15 @@ fun CalendarMonthView(
shiftKindAt = shiftKindAt, shiftKindAt = shiftKindAt,
onRowHeightMeasured = onRowHeightMeasured, onRowHeightMeasured = onRowHeightMeasured,
pagerState = pagerState, pagerState = pagerState,
modifier = Modifier.clipToBounds() modifier = Modifier
.clipToBounds()
.sharedBounds(
sharedContentState = rememberSharedContentState(key = "month_grid_$currentMonth"),
animatedVisibilityScope = this@AnimatedContent,
boundsTransform = { _, _ ->
spring(stiffness = Spring.StiffnessMediumLow, dampingRatio = Spring.DampingRatioNoBouncy)
}
)
) )
BottomCardArea( BottomCardArea(
viewModel = viewModel, viewModel = viewModel,
@ -349,6 +364,15 @@ fun CalendarMonthView(
} }
composeTraceEndSection() composeTraceEndSection()
}, },
monthModifier = { month ->
Modifier.sharedBounds(
sharedContentState = rememberSharedContentState(key = "month_grid_$month"),
animatedVisibilityScope = this@AnimatedContent,
boundsTransform = { _, _ ->
spring(stiffness = Spring.StiffnessMediumLow, dampingRatio = Spring.DampingRatioNoBouncy)
}
)
},
modifier = Modifier modifier = Modifier
) )
} }
@ -356,7 +380,7 @@ fun CalendarMonthView(
composeTraceEndSection() composeTraceEndSection()
} }
} }
}
// FAB 浮动按钮 // FAB 浮动按钮
FloatingActionButton( FloatingActionButton(
onClick = { isMenuExpanded = !isMenuExpanded }, onClick = { isMenuExpanded = !isMenuExpanded },

View File

@ -77,7 +77,8 @@ fun YearGridView(
selectedMonth: Int, selectedMonth: Int,
today: LocalDate, today: LocalDate,
onMonthClick: (Int) -> Unit, onMonthClick: (Int) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
monthModifier: @Composable (Int) -> Modifier = { Modifier }
) { ) {
composeTraceBeginSection("YearGridView:$year") composeTraceBeginSection("YearGridView:$year")
@ -169,7 +170,7 @@ fun YearGridView(
titleLayouts = titleLayouts, titleLayouts = titleLayouts,
weekdayLayouts = weekdayLayouts, weekdayLayouts = weekdayLayouts,
onClick = { onMonthClick(month) }, onClick = { onMonthClick(month) },
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f).then(monthModifier(month))
) )
} }
} }