From a44ef44b42a1f6af53f3c6b9c2f2d18c4ec2fb6d Mon Sep 17 00:00:00 2001 From: xfy Date: Thu, 14 May 2026 15:44:00 +0800 Subject: [PATCH] Refactor collapse animation to overlay selected row instead of shrinking rows Selected row now moves up via offset + zIndex to cover other rows, which stay in place. Collapse offset calculation excludes MonthHeader and WeekdayHeader heights so only the grid portion is collapsed. Co-Authored-By: Claude Opus 4.7 --- .../plus/rua/project/ui/CalendarMonthPage.kt | 90 +++++++++---------- .../plus/rua/project/ui/CalendarMonthView.kt | 20 +++-- 2 files changed, 57 insertions(+), 53 deletions(-) diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthPage.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthPage.kt index 6b5c6d6..3aff4de 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthPage.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthPage.kt @@ -3,7 +3,7 @@ package plus.rua.project.ui import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -11,9 +11,11 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex import kotlinx.datetime.DatePeriod import kotlinx.datetime.LocalDate import kotlinx.datetime.minus @@ -22,12 +24,13 @@ import kotlinx.datetime.plus /** * 月度日历网格页,6×7 布局,支持折叠动画。 * - * 折叠时选中行保持原高,上方行向上收缩、下方行向下收缩,模拟"挤压"效果。 + * 折叠时选中行保持原高并向上移动覆盖其他行,其他行保持原位不动。 + * 选中行通过 offset + zIndex 实现覆盖效果。 * * @param year 年份 * @param month 月份(1-12) * @param selectedDate 当前选中日期 - * @param today 今天的日期,用于高亮标记 + * @param today 今天的日期 * @param onDateClick 日期点击回调 * @param collapseProgress 折叠进度,0f=展开(6行),1f=折叠(仅选中行可见) * @param modifier 外部布局修饰符 @@ -53,54 +56,46 @@ fun CalendarMonthPage( } var rowHeightPx by remember { mutableIntStateOf(0) } - val rowMeasured = rowHeightPx > 0 - Column(modifier = modifier) { + // 选中行上移距离 = 上方行数 × 行高 × progress + val selectedOffsetPx = if (rowHeightPx > 0) { + -(selectedWeekIndex.toFloat() * rowHeightPx.toFloat() * collapseProgress) + } else { + 0f + } + val selectedOffsetDp = with(density) { selectedOffsetPx.toDp() } + + Column(modifier = modifier.clipToBounds()) { weeks.forEachIndexed { weekIndex, week -> - val isAboveSelected = weekIndex < selectedWeekIndex - val isBelowSelected = weekIndex > selectedWeekIndex + val isSelected = weekIndex == selectedWeekIndex - val rowScale = when { - isAboveSelected || isBelowSelected -> 1f - collapseProgress - else -> 1f - } - - val rowHeightDp = if (rowMeasured && rowScale > 0.01f) { - with(density) { (rowHeightPx * rowScale).toDp() } - } else if (!rowMeasured) { - // First frame: let aspectRatio determine height naturally - null - } else { - 0.dp - } - - val shouldShow = rowHeightDp == null || rowHeightDp > 0.dp - - if (shouldShow) { - Row( - modifier = Modifier - .fillMaxWidth() - .then( - if (rowHeightDp != null) Modifier.height(rowHeightDp) - else Modifier - ) - .onSizeChanged { size -> - if (size.height > 0 && !rowMeasured) { - rowHeightPx = size.height - } + Row( + modifier = Modifier + .fillMaxWidth() + .zIndex(if (isSelected) 1f else 0f) + .then( + if (isSelected && rowHeightPx > 0) { + Modifier.offset(y = selectedOffsetDp) + } else { + Modifier + } + ) + .onSizeChanged { size -> + if (size.height > 0 && rowHeightPx == 0) { + rowHeightPx = size.height } - .padding(vertical = 2.dp) - ) { - week.forEach { dayData -> - DayCell( - date = dayData.date, - isCurrentMonth = dayData.isCurrentMonth, - isSelected = dayData.date == selectedDate, - isToday = dayData.date == today, - onClick = { onDateClick(dayData.date) }, - modifier = Modifier.weight(1f) - ) } + .padding(vertical = 2.dp) + ) { + week.forEach { dayData -> + DayCell( + date = dayData.date, + isCurrentMonth = dayData.isCurrentMonth, + isSelected = dayData.date == selectedDate, + isToday = dayData.date == today, + onClick = { onDateClick(dayData.date) }, + modifier = Modifier.weight(1f) + ) } } } @@ -112,13 +107,12 @@ private data class DayData( val isCurrentMonth: Boolean ) -@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口 +@Suppress("DEPRECATION") private fun generateMonthDays(year: Int, month: Int): List { val firstOfMonth = LocalDate(year, month, 1) val offset = firstOfMonth.dayOfWeek.ordinal val startDate = firstOfMonth.minus(DatePeriod(days = offset)) - // 6行×7列=42格,覆盖跨月首尾周,保证网格完整 return (0 until 42).map { i -> val date = startDate.plus(DatePeriod(days = i)) DayData( 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 6576007..a182629 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt @@ -46,13 +46,16 @@ fun CalendarMonthView( var calendarHeightPx by remember { mutableIntStateOf(0) } var screenHeightPx by remember { mutableIntStateOf(0) } var expandedCalendarHeightPx by remember { mutableIntStateOf(0) } + var monthHeaderHeightPx by remember { mutableIntStateOf(0) } + var weekdayHeaderHeightPx by remember { mutableIntStateOf(0) } - // collapseProgress: 0f=月视图(6行), 1f=周视图(1行) - // 折叠偏移量 = 进度 × 展开高度的5/6(保留1行可见) + // 日历网格高度 = 总高度 - MonthHeader - WeekdayHeader + val expandedGridHeightPx = expandedCalendarHeightPx - monthHeaderHeightPx - weekdayHeaderHeightPx + // 折叠偏移量 = 进度 × 网格5行高度(保留1行可见) val collapseOffsetPx = if (viewModel.isCollapsed) { 0 } else { - -(viewModel.collapseProgress * expandedCalendarHeightPx * 5f / 6f).toInt() + -(viewModel.collapseProgress * expandedGridHeightPx * 5f / 6f).toInt() } val cardTopPx = if (viewModel.isCollapsed) { calendarHeightPx @@ -79,9 +82,16 @@ fun CalendarMonthView( MonthHeader( year = currentYear, month = currentMonth, - weekNumber = viewModel.getIsoWeekNumber(viewModel.selectedDate) + weekNumber = viewModel.getIsoWeekNumber(viewModel.selectedDate), + modifier = Modifier.onSizeChanged { size -> + monthHeaderHeightPx = size.height + } + ) + WeekdayHeader( + modifier = Modifier.fillMaxWidth().onSizeChanged { size -> + weekdayHeaderHeightPx = size.height + } ) - WeekdayHeader(modifier = Modifier.fillMaxWidth()) if (viewModel.isCollapsed) { WeekPager( selectedDate = viewModel.selectedDate,