Unify height calculation with effectiveWeeks and fix swipe interpolation continuity

Pass effectiveWeeks from CalendarMonthView through CalendarPager to
CalendarMonthPage so both use the same formula H*(1+(weeks-1)*(1-p)).
Fix interpolatedWeeks to anchor on settledPage instead of currentPage,
preventing direction/fraction discontinuity when currentPage jumps during
swipe transitions.

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

View File

@ -19,6 +19,8 @@ import kotlinx.datetime.LocalDate
import kotlinx.datetime.minus
import kotlinx.datetime.plus
private const val TAG = "CalMonthPage"
/**
* 月度日历网格页面支持折叠动画
*
@ -36,6 +38,7 @@ fun CalendarMonthPage(
onDateClick: (LocalDate) -> Unit,
collapseProgress: Float,
rowHeightPx: Int,
effectiveWeeks: Float,
onRowHeightMeasured: ((Int) -> Unit)? = null,
modifier: Modifier = Modifier
) {
@ -52,17 +55,15 @@ fun CalendarMonthPage(
val hasSelectedWeek = selectedWeekIndex >= 0
val H = rowHeightPx.toFloat()
// 总高度 = 选中行高度 + 上方行压缩高度 + 下方行压缩高度
// 使用与 CalendarMonthView 一致的 effectiveWeeks 计算高度,避免滑动中高度不匹配
val totalHeightDp = if (rowHeightPx > 0) {
if (hasSelectedWeek) {
val aboveH = selectedWeekIndex * H * (1f - collapseProgress)
val belowH = (weeks.size - 1 - selectedWeekIndex) * H * (1f - collapseProgress)
val selH = H
with(density) { (aboveH + selH + belowH).toDp() }
} else {
with(density) { (weeks.size * H).toDp() }
}
val p = collapseProgress
val totalPx = H * (1 + (effectiveWeeks - 1) * (1f - p))
println("[$TAG] year=$year month=$month rowH=$rowHeightPx H=$H effWeeks=$effectiveWeeks " +
"weeks.size=${weeks.size} p=$p totalPx=$totalPx selWeek=$selectedWeekIndex")
with(density) { totalPx.toDp() }
} else {
println("[$TAG] year=$year month=$month rowH=0 (not yet measured)")
null
}
@ -161,4 +162,4 @@ private fun generateMonthDays(year: Int, month: Int): List<DayData> {
isCurrentMonth = date.monthNumber == month && date.year == year
)
}
}
}

View File

@ -32,6 +32,7 @@ import plus.rua.project.CalendarViewModel
private const val START_PAGE = Int.MAX_VALUE / 2
private const val ROW_PADDING_DP = 4
private const val TAG = "CalMonthView"
/**
* 日历主界面包含月/周视图切换和折叠动画
@ -67,11 +68,19 @@ fun CalendarMonthView(
val rowPaddingPx = with(density) { ROW_PADDING_DP.dp.toPx() }.toInt()
// 滑动偏移插值行数
// 始终以 settledPage 为锚点currentPage - settledPage 确定方向(-1/0/+1
// abs(offsetFraction) 为过渡进度。
// 这样在 currentPage 跳变前后,方向和进度都是连续的:
// 跳变前: sp=8月, cp=8月, diff=0, offsetFraction>0 → 目标9月, fraction 0→0.5
// 跳变后: sp=8月, cp=9月, diff=+1 → 目标9月, fraction 0.5→0
val offsetFraction by remember { derivedStateOf { pagerState.currentPageOffsetFraction } }
val interpolatedWeeks = if (abs(offsetFraction) > 0.01f) {
val targetPage = if (offsetFraction > 0) pagerState.currentPage + 1 else pagerState.currentPage - 1
val sp = pagerState.settledPage
val diff = pagerState.currentPage - sp // -1, 0, or +1
val targetPage = if (diff != 0) sp + diff else sp + if (offsetFraction > 0) 1 else -1
val baseWeeks = calculateWeeksCountForPage(sp, today)
val targetWeeks = calculateWeeksCountForPage(targetPage, today)
lerp(currentWeeksCount.toFloat(), targetWeeks.toFloat(), abs(offsetFraction))
lerp(baseWeeks.toFloat(), targetWeeks.toFloat(), abs(offsetFraction))
} else {
currentWeeksCount.toFloat()
}
@ -88,19 +97,26 @@ fun CalendarMonthView(
// 折叠时网格高度公式(与 CalendarMonthPage 一致):
// gridH = rowH × (1 + (weeks-1) × (1-p))
val effectiveWeeks = interpolatedWeeks
val gridHeightPx = if (effectiveRowHeightPx > 0) {
val rowH = effectiveRowHeightPx.toFloat()
val weeks = interpolatedWeeks
if (p > 0.01f) {
(rowH * (1 + (weeks - 1) * (1f - p))).toInt()
(rowH * (1 + (effectiveWeeks - 1) * (1f - p))).toInt()
} else {
(rowH * weeks).toInt()
(rowH * effectiveWeeks).toInt()
}
} else 0
val calendarAreaHeightPx = headerHeightPx + gridHeightPx + rowPaddingPx
val cardHeightPx = if (screenHeightPx > 0 && calendarAreaHeightPx > 0) screenHeightPx - calendarAreaHeightPx else 0
println("[$TAG] p=$p rowH=$rowHeightPx estRowH=$estimatedRowHeightPx effRowH=$effectiveRowHeightPx " +
"headerH=$headerHeightPx gridH=$gridHeightPx calAreaH=$calendarAreaHeightPx " +
"screenH=$screenHeightPx cardH=$cardHeightPx " +
"currentWeeks=$currentWeeksCount interpolatedWeeks=$interpolatedWeeks effectiveWeeks=$effectiveWeeks " +
"offsetFraction=$offsetFraction currentPage=${pagerState.currentPage} settledPage=${pagerState.settledPage}")
// 当 rowHeightPx 已知时,用计算的高度约束 pager否则让 pager 自由扩展以测量行高
val pagerModifier = if (rowHeightPx > 0 && gridHeightPx > 0) {
Modifier
@ -162,6 +178,7 @@ fun CalendarMonthView(
},
collapseProgress = viewModel.collapseProgress,
rowHeightPx = rowHeightPx,
effectiveWeeks = effectiveWeeks,
onWeeksChanged = { weeks ->
currentWeeksCount = weeks
},

View File

@ -38,6 +38,7 @@ fun CalendarPager(
onMonthChanged: (year: Int, month: Int) -> Unit,
collapseProgress: Float,
rowHeightPx: Int,
effectiveWeeks: Float,
onWeeksChanged: ((Int) -> Unit)? = null,
onRowHeightMeasured: ((Int) -> Unit)? = null,
pagerState: PagerState,
@ -82,6 +83,7 @@ fun CalendarPager(
},
collapseProgress = collapseProgress,
rowHeightPx = rowHeightPx,
effectiveWeeks = effectiveWeeks,
onRowHeightMeasured = onRowHeightMeasured
)
}