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 kotlinx.datetime.todayIn
import plus.rua.project.ui.COLLAPSE_THRESHOLD import plus.rua.project.ui.COLLAPSE_THRESHOLD
import plus.rua.project.ui.getMonthGridInfo import plus.rua.project.ui.getMonthGridInfo
import android.util.Log
import kotlin.time.Clock import kotlin.time.Clock
private const val TAG_VM = "CalendarExpand"
/** /**
* 日历日期数据用于网格单元格渲染 * 日历日期数据用于网格单元格渲染
* *
@ -285,7 +288,9 @@ class CalendarViewModel(
* @param delta 拖拽增量已归一化到 [0,1] 区间 * @param delta 拖拽增量已归一化到 [0,1] 区间
*/ */
fun onExpandDrag(delta: Float) { fun onExpandDrag(delta: Float) {
val old = _collapseProgress.value
_collapseProgress.value = (_collapseProgress.value + delta).coerceIn(0f, 1f) _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() { fun onExpandDragEnd() {
val progress = _collapseProgress.value val progress = _collapseProgress.value
if (progress < (1 - COLLAPSE_THRESHOLD)) { val result = if (progress < (1 - COLLAPSE_THRESHOLD)) {
_isCollapsed.value = false _isCollapsed.value = false
_collapseProgress.value = 0f _collapseProgress.value = 0f
"EXPANDED"
} else { } else {
_isCollapsed.value = true _isCollapsed.value = true
_collapseProgress.value = 1f _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.platform.LocalDensity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import android.util.Log
import kotlinx.datetime.DatePeriod import kotlinx.datetime.DatePeriod
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.datetime.minus import kotlinx.datetime.minus
@ -30,6 +31,8 @@ import plus.rua.project.DayCellInfo
import plus.rua.project.LunarCache import plus.rua.project.LunarCache
import plus.rua.project.ShiftKind import plus.rua.project.ShiftKind
private const val TAG_CMP = "CalendarExpandAnim"
/** /**
* 月度日历网格页面支持两阶段折叠动画 * 月度日历网格页面支持两阶段折叠动画
@ -185,7 +188,7 @@ private fun WeekRow(
!hasAnchor -> weekIndex * h - collapseProgress * weeksSize * h !hasAnchor -> weekIndex * h - collapseProgress * weeksSize * h
isAnchor -> anchorIndex * h * (1f - phase1) isAnchor -> anchorIndex * h * (1f - phase1)
isAbove -> weekIndex * h - phase1 * anchorIndex * h isAbove -> weekIndex * h - phase1 * anchorIndex * h
isBelow -> weekIndex * h - phase1 * anchorIndex * h - phase2 * belowRowsHeight isBelow -> weekIndex * h - phase1 * anchorIndex * h
else -> weekIndex * h else -> weekIndex * h
} }
} else 0f } else 0f
@ -198,6 +201,10 @@ private fun WeekRow(
else -> 1f 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) { if (rowAlpha > 0.01f) {
Row( Row(
modifier = Modifier modifier = Modifier

View File

@ -520,6 +520,50 @@ private fun CalendarPagerArea(
modifier modifier
} }
// 延迟切换:等折叠 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( CalendarPager(
selectedDate = selectedDate, selectedDate = selectedDate,
today = today, today = today,
@ -532,9 +576,11 @@ private fun CalendarPagerArea(
showLegalHoliday = showLegalHoliday, showLegalHoliday = showLegalHoliday,
onRowHeightMeasured = onRowHeightMeasured, onRowHeightMeasured = onRowHeightMeasured,
pagerState = pagerState, pagerState = pagerState,
modifier = pagerModifier modifier = Modifier
) )
} }
}
}
@Composable @Composable
private fun BottomCardArea( private fun BottomCardArea(

View File

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