feat: 使用 AnimatedContent 平滑切换 CalendarPager ↔ WeekPager

- 用 AnimatedContent 包装 pager 切换,添加 fadeIn/fadeOut 过渡
- 延迟 50ms 切换避免折叠 spring 动画期间的视觉跳跃
- 修复 WeekRow 下方行的 yOffset 计算(移除 phase2 项)
- WeekPager 添加农历缓存支持
- 添加折叠动画调试日志

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xfy 2026-05-26 14:18:50 +08:00
parent 4c53f234cf
commit 2592a5fa55
4 changed files with 86 additions and 16 deletions

View File

@ -20,8 +20,11 @@ import kotlinx.datetime.plus
import kotlinx.datetime.todayIn
import plus.rua.project.ui.COLLAPSE_THRESHOLD
import plus.rua.project.ui.getMonthGridInfo
import android.util.Log
import kotlin.time.Clock
private const val TAG_VM = "CalendarExpand"
/**
* 日历日期数据用于网格单元格渲染
*
@ -285,7 +288,9 @@ class CalendarViewModel(
* @param delta 拖拽增量已归一化到 [0,1] 区间
*/
fun onExpandDrag(delta: Float) {
val old = _collapseProgress.value
_collapseProgress.value = (_collapseProgress.value + delta).coerceIn(0f, 1f)
Log.d(TAG_VM, "onExpandDrag: delta=$delta old=$old new=${_collapseProgress.value}")
}
/**
@ -295,13 +300,16 @@ class CalendarViewModel(
*/
fun onExpandDragEnd() {
val progress = _collapseProgress.value
if (progress < (1 - COLLAPSE_THRESHOLD)) {
val result = if (progress < (1 - COLLAPSE_THRESHOLD)) {
_isCollapsed.value = false
_collapseProgress.value = 0f
"EXPANDED"
} else {
_isCollapsed.value = true
_collapseProgress.value = 1f
"COLLAPSED (bounce back)"
}
Log.d(TAG_VM, "onExpandDragEnd: progress=$progress threshold=${1 - COLLAPSE_THRESHOLD} result=$result")
}
/**

View File

@ -21,6 +21,7 @@ import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import android.util.Log
import kotlinx.datetime.DatePeriod
import kotlinx.datetime.LocalDate
import kotlinx.datetime.minus
@ -30,6 +31,8 @@ import plus.rua.project.DayCellInfo
import plus.rua.project.LunarCache
import plus.rua.project.ShiftKind
private const val TAG_CMP = "CalendarExpandAnim"
/**
* 月度日历网格页面支持两阶段折叠动画
@ -185,7 +188,7 @@ private fun WeekRow(
!hasAnchor -> weekIndex * h - collapseProgress * weeksSize * h
isAnchor -> anchorIndex * h * (1f - phase1)
isAbove -> weekIndex * h - phase1 * anchorIndex * h
isBelow -> weekIndex * h - phase1 * anchorIndex * h - phase2 * belowRowsHeight
isBelow -> weekIndex * h - phase1 * anchorIndex * h
else -> weekIndex * h
}
} else 0f
@ -198,6 +201,10 @@ private fun WeekRow(
else -> 1f
}
if (isAnchor || isBelow) {
Log.d(TAG_CMP, "WeekRow[$weekIndex]: isAnchor=$isAnchor isAbove=$isAbove isBelow=$isBelow phase1=$phase1 phase2=$phase2 yOffsetPx=$yOffsetPx rowAlpha=$rowAlpha collapseProgress=$collapseProgress")
}
if (rowAlpha > 0.01f) {
Row(
modifier = Modifier

View File

@ -520,20 +520,66 @@ private fun CalendarPagerArea(
modifier
}
CalendarPager(
selectedDate = selectedDate,
today = today,
onDateClick = onDateClick,
onMonthChanged = onMonthChanged,
collapseProgress = collapseProgress,
rowHeightPx = rowHeightPx,
effectiveWeeks = effectiveWeeks,
shiftKindAt = shiftKindAt,
showLegalHoliday = showLegalHoliday,
onRowHeightMeasured = onRowHeightMeasured,
pagerState = pagerState,
// 延迟切换:等折叠 spring 动画完全稳定后再切到 WeekPager避免视觉跳跃
var showWeekPager by remember { mutableStateOf(false) }
LaunchedEffect(isCollapsed, collapseProgress) {
if (isCollapsed && collapseProgress >= 0.999f) {
delay(50)
showWeekPager = true
} else if (!isCollapsed) {
showWeekPager = false
}
}
AnimatedContent(
targetState = showWeekPager,
transitionSpec = { fadeIn(tween(80)) togetherWith fadeOut(tween(80)) },
label = "pager_switch",
modifier = pagerModifier
)
) { useWeekPager ->
if (useWeekPager) {
WeekPager(
selectedDate = selectedDate,
today = today,
onDateClick = onDateClick,
onWeekChanged = { weekMonday ->
val weekSunday = weekMonday.plus(DatePeriod(days = 6))
val date = when {
today in weekMonday..weekSunday -> today
weekMonday.month != weekSunday.month -> {
if (weekMonday < selectedDate) {
@Suppress("DEPRECATION")
LocalDate(weekSunday.year, weekSunday.month.number, 1)
} else {
weekMonday
}
}
else -> weekMonday
}
onDateClick(date)
},
shiftKindAt = shiftKindAt,
showLegalHoliday = showLegalHoliday,
modifier = Modifier
)
} else {
CalendarPager(
selectedDate = selectedDate,
today = today,
onDateClick = onDateClick,
onMonthChanged = onMonthChanged,
collapseProgress = collapseProgress,
rowHeightPx = rowHeightPx,
effectiveWeeks = effectiveWeeks,
shiftKindAt = shiftKindAt,
showLegalHoliday = showLegalHoliday,
onRowHeightMeasured = onRowHeightMeasured,
pagerState = pagerState,
modifier = Modifier
)
}
}
}
@Composable

View File

@ -18,10 +18,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.runBlocking
import kotlinx.datetime.DatePeriod
import kotlinx.datetime.LocalDate
import kotlinx.datetime.daysUntil
import kotlinx.datetime.plus
import plus.rua.project.DayCellInfo
import plus.rua.project.LunarCache
import plus.rua.project.ShiftKind
import plus.rua.project.composeTraceBeginSection
import plus.rua.project.composeTraceEndSection
@ -96,6 +99,11 @@ fun WeekPager(
) {
(0 until 7).forEach { dayOffset ->
val date = weekMonday.plus(DatePeriod(days = dayOffset))
val lunarData = remember(date) {
runBlocking {
LunarCache.default.getOrCompute(date)
}
}
DayCell(
date = date,
isCurrentMonth = date.month == selectedDate.month
@ -106,7 +114,8 @@ fun WeekPager(
showLegalHoliday = showLegalHoliday,
onClick = { onDateClick(date) },
modifier = Modifier.weight(1f),
interactionSource = interactionSource
interactionSource = interactionSource,
lunarData = lunarData
)
}
}