Measure row height from DayCell instead of reverse-calculating from Column

Replace the fragile approach of computing rowHeightPx by dividing
Column height by weeks count with direct measurement via onSizeChanged
on the first DayCell row. Add estimated row height fallback based on
screen width and cell aspect ratio so the pager can be constrained
before the first measurement completes. Remove lockedRowHeightPx,
expandedWeeksCount, and calendarHeightPx state variables that are no
longer needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-05-15 00:57:03 +08:00
parent 785267b8bb
commit b95f748839
3 changed files with 43 additions and 31 deletions

View File

@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
@ -35,6 +36,7 @@ fun CalendarMonthPage(
onDateClick: (LocalDate) -> Unit, onDateClick: (LocalDate) -> Unit,
collapseProgress: Float, collapseProgress: Float,
rowHeightPx: Int, rowHeightPx: Int,
onRowHeightMeasured: ((Int) -> Unit)? = null,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val days = remember(year, month) { val days = remember(year, month) {
@ -113,6 +115,13 @@ fun CalendarMonthPage(
) )
.offset(y = yOffsetDp) .offset(y = yOffsetDp)
.padding(vertical = 4.dp) .padding(vertical = 4.dp)
.then(
if (weekIndex == 0 && rowHeightPx == 0) {
Modifier.onSizeChanged { size ->
if (size.height > 0) onRowHeightMeasured?.invoke(size.height)
}
} else Modifier
)
) { ) {
week.forEach { dayData -> week.forEach { dayData ->
DayCell( DayCell(

View File

@ -31,6 +31,7 @@ import kotlin.time.Clock
import plus.rua.project.CalendarViewModel import plus.rua.project.CalendarViewModel
private const val START_PAGE = Int.MAX_VALUE / 2 private const val START_PAGE = Int.MAX_VALUE / 2
private const val ROW_PADDING_DP = 4
/** /**
* 日历主界面包含月/周视图切换和折叠动画 * 日历主界面包含月/周视图切换和折叠动画
@ -51,25 +52,19 @@ fun CalendarMonthView(
var currentMonth by remember { mutableIntStateOf(viewModel.currentMonth) } var currentMonth by remember { mutableIntStateOf(viewModel.currentMonth) }
val density = LocalDensity.current val density = LocalDensity.current
var calendarHeightPx by remember { mutableIntStateOf(0) }
var monthHeaderHeightPx by remember { mutableIntStateOf(0) } var monthHeaderHeightPx by remember { mutableIntStateOf(0) }
var weekdayHeaderHeightPx by remember { mutableIntStateOf(0) } var weekdayHeaderHeightPx by remember { mutableIntStateOf(0) }
var rowHeightPx by remember { mutableIntStateOf(0) }
@Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口
var currentWeeksCount by remember { mutableIntStateOf(calculateWeeksCount(today.year, today.monthNumber)) }
var screenWidthPx by remember { mutableIntStateOf(0) }
var screenHeightPx by remember { mutableIntStateOf(0) } var screenHeightPx by remember { mutableIntStateOf(0) }
var currentWeeksCount by remember { mutableIntStateOf(6) }
var expandedWeeksCount by remember { mutableIntStateOf(6) }
var lockedRowHeightPx by remember { mutableIntStateOf(0) }
val pagerState = rememberPagerState(initialPage = START_PAGE, pageCount = { Int.MAX_VALUE }) val pagerState = rememberPagerState(initialPage = START_PAGE, pageCount = { Int.MAX_VALUE })
val p = viewModel.collapseProgress val p = viewModel.collapseProgress
val headerHeightPx = monthHeaderHeightPx + weekdayHeaderHeightPx val headerHeightPx = monthHeaderHeightPx + weekdayHeaderHeightPx
val rowPaddingPx = with(density) { ROW_PADDING_DP.dp.toPx() }.toInt()
// 行高:优先使用锁定值(折叠过程中不变),否则用实时计算初始化
val rowHeightPx = if (lockedRowHeightPx > 0) {
lockedRowHeightPx
} else if (calendarHeightPx > 0 && expandedWeeksCount > 0) {
(calendarHeightPx - headerHeightPx) / expandedWeeksCount
} else 0
// 滑动偏移插值行数 // 滑动偏移插值行数
val offsetFraction by remember { derivedStateOf { pagerState.currentPageOffsetFraction } } val offsetFraction by remember { derivedStateOf { pagerState.currentPageOffsetFraction } }
@ -81,24 +76,33 @@ fun CalendarMonthView(
currentWeeksCount.toFloat() currentWeeksCount.toFloat()
} }
// 预估行高DayCell aspectRatio=1宽度 = (screenWidth - horizontalPadding) / 7
// 加上 Row 的 vertical padding (4dp × 2)
val estimatedRowHeightPx = if (screenWidthPx > 0) {
val cellWidth = (screenWidthPx - with(density) { 32.dp.toPx() }) / 7
val rowPadding = with(density) { 8.dp.toPx() }
(cellWidth + rowPadding).toInt()
} else 0
val effectiveRowHeightPx = if (rowHeightPx > 0) rowHeightPx else estimatedRowHeightPx
// 折叠时网格高度公式(与 CalendarMonthPage 一致): // 折叠时网格高度公式(与 CalendarMonthPage 一致):
// gridH = rowH × (1 + (weeks-1) × (1-p)) // gridH = rowH × (1 + (weeks-1) × (1-p))
val gridHeightPx = if (rowHeightPx > 0) { val gridHeightPx = if (effectiveRowHeightPx > 0) {
val rowH = rowHeightPx.toFloat() val rowH = effectiveRowHeightPx.toFloat()
val weeks = interpolatedWeeks val weeks = interpolatedWeeks
if (p > 0f) { if (p > 0.01f) {
(rowH * (1 + (weeks - 1) * (1f - p))).toInt() (rowH * (1 + (weeks - 1) * (1f - p))).toInt()
} else { } else {
(rowH * weeks).toInt() (rowH * weeks).toInt()
} }
} else 0 } else 0
val rowPaddingPx = with(density) { 4.dp.toPx() }.toInt() val calendarAreaHeightPx = headerHeightPx + gridHeightPx + rowPaddingPx
val cardHeightPx = if (screenHeightPx > 0 && calendarAreaHeightPx > 0) screenHeightPx - calendarAreaHeightPx else 0
val cardTopPx = headerHeightPx + gridHeightPx + rowPaddingPx // 当 rowHeightPx 已知时,用计算的高度约束 pager否则让 pager 自由扩展以测量行高
val cardHeightPx = screenHeightPx - cardTopPx val pagerModifier = if (rowHeightPx > 0 && gridHeightPx > 0) {
val pagerModifier = if (p > 0.01f && rowHeightPx > 0) {
Modifier Modifier
.height(with(density) { gridHeightPx.toDp() }) .height(with(density) { gridHeightPx.toDp() })
.clipToBounds() .clipToBounds()
@ -111,17 +115,11 @@ fun CalendarMonthView(
.fillMaxSize() .fillMaxSize()
.statusBarsPadding() .statusBarsPadding()
.onSizeChanged { size -> .onSizeChanged { size ->
screenWidthPx = size.width
screenHeightPx = size.height screenHeightPx = size.height
} }
) { ) {
Column(modifier = Modifier.padding(horizontal = 16.dp).onSizeChanged { size -> Column(modifier = Modifier.padding(horizontal = 16.dp)) {
calendarHeightPx = size.height
if (p < 0.01f) {
expandedWeeksCount = currentWeeksCount
val calculated = (size.height - headerHeightPx) / currentWeeksCount
if (calculated > 0) lockedRowHeightPx = calculated
}
}) {
MonthHeader( MonthHeader(
year = currentYear, year = currentYear,
month = currentMonth, month = currentMonth,
@ -133,7 +131,7 @@ fun CalendarMonthView(
WeekdayHeader( WeekdayHeader(
modifier = Modifier.fillMaxWidth().onSizeChanged { size -> modifier = Modifier.fillMaxWidth().onSizeChanged { size ->
weekdayHeaderHeightPx = size.height weekdayHeaderHeightPx = size.height
}.padding(bottom = 4.dp) }.padding(bottom = ROW_PADDING_DP.dp)
) )
// 完全折叠且无动画时显示 WeekPager否则显示 CalendarPager含下拉恢复过程 // 完全折叠且无动画时显示 WeekPager否则显示 CalendarPager含下拉恢复过程
if (viewModel.isCollapsed && viewModel.collapseProgress >= 1f) { if (viewModel.isCollapsed && viewModel.collapseProgress >= 1f) {
@ -166,13 +164,16 @@ fun CalendarMonthView(
rowHeightPx = rowHeightPx, rowHeightPx = rowHeightPx,
onWeeksChanged = { weeks -> onWeeksChanged = { weeks ->
currentWeeksCount = weeks currentWeeksCount = weeks
if (p < 0.01f) expandedWeeksCount = weeks },
onRowHeightMeasured = { h ->
if (h > 0 && rowHeightPx == 0) rowHeightPx = h
}, },
pagerState = pagerState, pagerState = pagerState,
modifier = pagerModifier modifier = pagerModifier
) )
} }
} }
if (cardHeightPx > 0) { if (cardHeightPx > 0) {
BottomCard( BottomCard(
viewModel = viewModel, viewModel = viewModel,

View File

@ -39,6 +39,7 @@ fun CalendarPager(
collapseProgress: Float, collapseProgress: Float,
rowHeightPx: Int, rowHeightPx: Int,
onWeeksChanged: ((Int) -> Unit)? = null, onWeeksChanged: ((Int) -> Unit)? = null,
onRowHeightMeasured: ((Int) -> Unit)? = null,
pagerState: PagerState, pagerState: PagerState,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
@ -80,7 +81,8 @@ fun CalendarPager(
} }
}, },
collapseProgress = collapseProgress, collapseProgress = collapseProgress,
rowHeightPx = rowHeightPx rowHeightPx = rowHeightPx,
onRowHeightMeasured = onRowHeightMeasured
) )
} }
} }