feat: 年月视图转场添加 BottomCard 滑入动画和淡入淡出效果

- CalendarMonthView: 为 BottomCard 添加滑入/淡出动效
- 优化 AnimatedContent 过渡:fade + slideInVertically/slideOutVertically
- YearGridView: 提取 sharedKey 变量

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xfy 2026-05-20 16:46:09 +08:00
parent 9ad619c105
commit e1345cc071
2 changed files with 24 additions and 3 deletions

View File

@ -12,7 +12,10 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -23,6 +26,7 @@ import androidx.compose.foundation.layout.Column
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.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.statusBarsPadding
@ -94,6 +98,15 @@ fun CalendarMonthView(
@Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口 @Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口
val currentMonth by remember { derivedStateOf { viewModel.selectedDate.month.number } } val currentMonth by remember { derivedStateOf { viewModel.selectedDate.month.number } }
val density = LocalDensity.current
// BottomCard 滑入动画1f=完全隐藏在下方0f=完全显示
val bottomCardSlideProgress by animateFloatAsState(
targetValue = if (viewModel.isYearView) 1f else 0f,
animationSpec = tween(350, delayMillis = 100, easing = FastOutSlowInEasing),
label = "bottomCardSlide"
)
LocalDensity.current LocalDensity.current
var rowHeightPx by remember { mutableIntStateOf(0) } var rowHeightPx by remember { mutableIntStateOf(0) }
@ -159,7 +172,11 @@ fun CalendarMonthView(
targetState = viewModel.isYearView, targetState = viewModel.isYearView,
label = "month_year_transition", label = "month_year_transition",
transitionSpec = { transitionSpec = {
fadeIn(tween(0)) togetherWith fadeOut(tween(0)) val enter = fadeIn(tween(300, easing = FastOutSlowInEasing)) +
slideInVertically(tween(300, easing = FastOutSlowInEasing)) { it / 6 }
val exit = fadeOut(tween(200, easing = FastOutSlowInEasing)) +
slideOutVertically(tween(200, easing = FastOutSlowInEasing)) { -it / 6 }
enter togetherWith exit
}, },
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { isYearView -> ) { isYearView ->
@ -215,7 +232,10 @@ fun CalendarMonthView(
viewModel = viewModel, viewModel = viewModel,
today = today, today = today,
rowHeightPx = rowHeightPx, rowHeightPx = rowHeightPx,
modifier = Modifier.fillMaxWidth() modifier = Modifier
.fillMaxWidth()
.offset(y = with(density) { (bottomCardSlideProgress * 200).dp })
.alpha(1f - bottomCardSlideProgress)
) )
} }
} }

View File

@ -165,6 +165,7 @@ fun YearGridView(
) { ) {
(0 until 3).forEach { col -> (0 until 3).forEach { col ->
val month = row * 3 + col + 1 val month = row * 3 + col + 1
val sharedKey = "month_grid_${year}_$month"
with(sharedTransitionScope) { with(sharedTransitionScope) {
MiniMonth( MiniMonth(
month = month, month = month,
@ -180,7 +181,7 @@ fun YearGridView(
.weight(1f) .weight(1f)
.sharedElement( .sharedElement(
sharedContentState = rememberSharedContentState( sharedContentState = rememberSharedContentState(
key = "month_grid_${year}_$month" key = sharedKey
), ),
animatedVisibilityScope = animatedVisibilityScope, animatedVisibilityScope = animatedVisibilityScope,
boundsTransform = { _, _ -> boundsTransform = { _, _ ->