diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt index 2506784..6aefa55 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt @@ -41,6 +41,7 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color @@ -331,12 +332,9 @@ fun CalendarMonthView( } } - // 年视图层:仅在年视图激活时渲染;HorizontalPager 支持左右滑动切年。 + // 年视图层:标题固定,HorizontalPager 只包裹网格。 if (viewModel.isYearView) { - HorizontalPager( - state = yearPagerState, - beyondViewportPageCount = 1, - flingBehavior = PagerDefaults.flingBehavior(state = yearPagerState), + Column( modifier = Modifier .fillMaxSize() .graphicsLayer { @@ -346,31 +344,51 @@ fun CalendarMonthView( transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY) } .padding(horizontal = HORIZONTAL_PADDING_DP.dp) - ) { page -> - val pageYear = viewModel.selectedDate.year + (page - START_PAGE) - YearGridView( - year = pageYear, - selectedMonth = if (pageYear == currentYear) currentMonth else 0, - today = today, - onMonthClick = { month -> - viewModel.selectMonthFromYearView(month) - @Suppress("DEPRECATION") // monthNumber 无替代 API - val targetPage = yearMonthToPage( - viewModel.yearViewYear, month, - today.year, today.month.number - ) - if (targetPage != pagerState.currentPage) { - coroutineScope.launch { pagerState.scrollToPage(targetPage) } - } - }, + ) { + YearHeader( + year = viewModel.yearViewYear, onYearChange = { newYear -> - val offset = newYear - pageYear + val offset = newYear - viewModel.yearViewYear val targetPage = yearPagerState.currentPage + offset if (targetPage != yearPagerState.currentPage) { coroutineScope.launch { yearPagerState.animateScrollToPage(targetPage) } } } ) + HorizontalPager( + state = yearPagerState, + beyondViewportPageCount = 1, + flingBehavior = PagerDefaults.flingBehavior(state = yearPagerState), + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { page -> + val pageOffset = abs(yearPagerState.currentPageOffsetFraction) + val isCurrentPage = page == yearPagerState.currentPage + val crossFadeAlpha = if (isCurrentPage) { + 1f - pageOffset + } else { + pageOffset + } + val pageYear = viewModel.selectedDate.year + (page - START_PAGE) + YearGridView( + year = pageYear, + selectedMonth = if (pageYear == currentYear) currentMonth else 0, + today = today, + onMonthClick = { month -> + viewModel.selectMonthFromYearView(month) + @Suppress("DEPRECATION") // monthNumber 无替代 API + val targetPage = yearMonthToPage( + viewModel.yearViewYear, month, + today.year, today.month.number + ) + if (targetPage != pagerState.currentPage) { + coroutineScope.launch { pagerState.scrollToPage(targetPage) } + } + }, + modifier = Modifier.alpha(crossFadeAlpha) + ) + } } } diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarPager.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarPager.kt index 57d20c0..711a6b7 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarPager.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarPager.kt @@ -72,7 +72,12 @@ fun CalendarPager( modifier = modifier ) { page -> val pageOffset = abs(pagerState.currentPageOffsetFraction) - val alpha = 1f - pageOffset.coerceIn(0f, 0.3f) / 0.3f + val isCurrentPage = page == pagerState.currentPage + val alpha = if (isCurrentPage) { + 1f - pageOffset + } else { + pageOffset + } val (year, month) = pageToYearMonth(page, initialYear, initialMonth) CalendarMonthPage( year = year, diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/MonthHeader.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/MonthHeader.kt index ea5b8c9..7a45a9b 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/MonthHeader.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/MonthHeader.kt @@ -2,8 +2,6 @@ package plus.rua.project.ui import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith @@ -52,11 +50,11 @@ fun MonthHeader( targetState = Pair(year, month), transitionSpec = { if (targetState.second > initialState.second) { - slideInVertically(tween(250)) { -it } + fadeIn(tween(250)) togetherWith - slideOutVertically(tween(250)) { it } + fadeOut(tween(250)) + slideInVertically(tween(250)) { -it } togetherWith + slideOutVertically(tween(250)) { it } } else { - slideInVertically(tween(250)) { it } + fadeIn(tween(250)) togetherWith - slideOutVertically(tween(250)) { -it } + fadeOut(tween(250)) + slideInVertically(tween(250)) { it } togetherWith + slideOutVertically(tween(250)) { -it } } } ) { (y, m) -> @@ -70,11 +68,11 @@ fun MonthHeader( targetState = weekNumber, transitionSpec = { if (targetState > initialState) { - slideInVertically(tween(250)) { -it } + fadeIn(tween(250)) togetherWith - slideOutVertically(tween(250)) { it } + fadeOut(tween(250)) + slideInVertically(tween(250)) { -it } togetherWith + slideOutVertically(tween(250)) { it } } else { - slideInVertically(tween(250)) { it } + fadeIn(tween(250)) togetherWith - slideOutVertically(tween(250)) { -it } + fadeOut(tween(250)) + slideInVertically(tween(250)) { it } togetherWith + slideOutVertically(tween(250)) { -it } } }, modifier = Modifier diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/YearGridView.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/YearGridView.kt index ad3b9f8..aefab19 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/YearGridView.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/YearGridView.kt @@ -1,5 +1,10 @@ package plus.rua.project.ui +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.core.tween +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -31,16 +36,12 @@ import kotlinx.datetime.plus private val WEEKDAY_LABELS = listOf("一", "二", "三", "四", "五", "六", "日") /** - * 年度网格视图,显示 4×3 精简月历网格,支持年份切换。 - * - * 每格显示一个精简版月历(月份标题 + 星期行 + 日期数字网格), - * 选中月份高亮,点击进入该月。 + * 年视图 4×3 月历网格。 * * @param year 显示的年份 * @param selectedMonth 当前选中月份(1-12) * @param today 今天的日期 * @param onMonthClick 月份点击回调 - * @param onYearChange 年份切换回调 * @param modifier 外部布局修饰符 */ @Composable @@ -49,49 +50,12 @@ fun YearGridView( selectedMonth: Int, today: LocalDate, onMonthClick: (Int) -> Unit, - onYearChange: (Int) -> Unit, modifier: Modifier = Modifier ) { Column( modifier = modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { - // 年份导航行 - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "‹", - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .clip(CircleShape) - .clickable { onYearChange(year - 1) } - .padding(horizontal = 16.dp, vertical = 8.dp) - ) - Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) { - Text( - text = "${year}年", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Bold - ) - } - Text( - text = "›", - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary, - modifier = Modifier - .clip(CircleShape) - .clickable { onYearChange(year + 1) } - .padding(horizontal = 16.dp, vertical = 8.dp) - ) - } - // 4×3 月历网格 Column( modifier = Modifier @@ -244,3 +208,67 @@ private fun generateMiniMonthDays(year: Int, month: Int): List { ) } } + +/** + * 年视图标题栏,显示年份文字和左右导航箭头。 + * + * 年份切换时文字有垂直滑动过渡动画,方向由新旧年份大小决定。 + * + * @param year 当前年份 + * @param onYearChange 年份切换回调 + * @param modifier 外部布局修饰符 + */ +@Composable +fun YearHeader( + year: Int, + onYearChange: (Int) -> Unit, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "‹", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .clip(CircleShape) + .clickable { onYearChange(year - 1) } + .padding(horizontal = 16.dp, vertical = 8.dp) + ) + Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) { + AnimatedContent( + targetState = year, + transitionSpec = { + if (targetState > initialState) { + slideInVertically(tween(250)) { -it } togetherWith + slideOutVertically(tween(250)) { it } + } else { + slideInVertically(tween(250)) { it } togetherWith + slideOutVertically(tween(250)) { -it } + } + } + ) { y -> + Text( + text = "${y}年", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold + ) + } + } + Text( + text = "›", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .clip(CircleShape) + .clickable { onYearChange(year + 1) } + .padding(horizontal = 16.dp, vertical = 8.dp) + ) + } +}