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.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
@ -35,6 +36,7 @@ fun CalendarMonthPage(
onDateClick: (LocalDate) -> Unit,
collapseProgress: Float,
rowHeightPx: Int,
onRowHeightMeasured: ((Int) -> Unit)? = null,
modifier: Modifier = Modifier
) {
val days = remember(year, month) {
@ -113,6 +115,13 @@ fun CalendarMonthPage(
)
.offset(y = yOffsetDp)
.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 ->
DayCell(

View File

@ -31,6 +31,7 @@ import kotlin.time.Clock
import plus.rua.project.CalendarViewModel
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) }
val density = LocalDensity.current
var calendarHeightPx by remember { mutableIntStateOf(0) }
var monthHeaderHeightPx 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 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 p = viewModel.collapseProgress
val headerHeightPx = monthHeaderHeightPx + weekdayHeaderHeightPx
// 行高:优先使用锁定值(折叠过程中不变),否则用实时计算初始化
val rowHeightPx = if (lockedRowHeightPx > 0) {
lockedRowHeightPx
} else if (calendarHeightPx > 0 && expandedWeeksCount > 0) {
(calendarHeightPx - headerHeightPx) / expandedWeeksCount
} else 0
val rowPaddingPx = with(density) { ROW_PADDING_DP.dp.toPx() }.toInt()
// 滑动偏移插值行数
val offsetFraction by remember { derivedStateOf { pagerState.currentPageOffsetFraction } }
@ -81,24 +76,33 @@ fun CalendarMonthView(
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 一致):
// gridH = rowH × (1 + (weeks-1) × (1-p))
val gridHeightPx = if (rowHeightPx > 0) {
val rowH = rowHeightPx.toFloat()
val gridHeightPx = if (effectiveRowHeightPx > 0) {
val rowH = effectiveRowHeightPx.toFloat()
val weeks = interpolatedWeeks
if (p > 0f) {
if (p > 0.01f) {
(rowH * (1 + (weeks - 1) * (1f - p))).toInt()
} else {
(rowH * weeks).toInt()
}
} 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
val cardHeightPx = screenHeightPx - cardTopPx
val pagerModifier = if (p > 0.01f && rowHeightPx > 0) {
// 当 rowHeightPx 已知时,用计算的高度约束 pager否则让 pager 自由扩展以测量行高
val pagerModifier = if (rowHeightPx > 0 && gridHeightPx > 0) {
Modifier
.height(with(density) { gridHeightPx.toDp() })
.clipToBounds()
@ -111,17 +115,11 @@ fun CalendarMonthView(
.fillMaxSize()
.statusBarsPadding()
.onSizeChanged { size ->
screenWidthPx = size.width
screenHeightPx = size.height
}
) {
Column(modifier = Modifier.padding(horizontal = 16.dp).onSizeChanged { size ->
calendarHeightPx = size.height
if (p < 0.01f) {
expandedWeeksCount = currentWeeksCount
val calculated = (size.height - headerHeightPx) / currentWeeksCount
if (calculated > 0) lockedRowHeightPx = calculated
}
}) {
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
MonthHeader(
year = currentYear,
month = currentMonth,
@ -133,7 +131,7 @@ fun CalendarMonthView(
WeekdayHeader(
modifier = Modifier.fillMaxWidth().onSizeChanged { size ->
weekdayHeaderHeightPx = size.height
}.padding(bottom = 4.dp)
}.padding(bottom = ROW_PADDING_DP.dp)
)
// 完全折叠且无动画时显示 WeekPager否则显示 CalendarPager含下拉恢复过程
if (viewModel.isCollapsed && viewModel.collapseProgress >= 1f) {
@ -166,13 +164,16 @@ fun CalendarMonthView(
rowHeightPx = rowHeightPx,
onWeeksChanged = { weeks ->
currentWeeksCount = weeks
if (p < 0.01f) expandedWeeksCount = weeks
},
onRowHeightMeasured = { h ->
if (h > 0 && rowHeightPx == 0) rowHeightPx = h
},
pagerState = pagerState,
modifier = pagerModifier
)
}
}
if (cardHeightPx > 0) {
BottomCard(
viewModel = viewModel,
@ -196,4 +197,4 @@ private fun calculateWeeksCountForPage(page: Int, today: LocalDate): Int {
val year = totalMonths / 12
val month = totalMonths % 12 + 1
return calculateWeeksCount(year, month)
}
}

View File

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