From aa223db519a1814ee4ef991d08f17cb0c21efc74 Mon Sep 17 00:00:00 2001 From: meyou <2636699780@qq.com> Date: Sat, 16 May 2026 17:42:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B9=B4=E6=9C=88=E8=A7=86=E5=9B=BE=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E6=97=B6=E7=AB=8B=E5=8D=B3=E7=A7=BB=E9=99=A4=E6=BA=90?= =?UTF-8?q?=E8=A7=86=E5=9B=BE=EF=BC=8C=E4=BB=85=E5=AF=B9=E7=9B=AE=E6=A0=87?= =?UTF-8?q?=E8=A7=86=E5=9B=BE=E6=92=AD=E6=94=BE=E7=BC=A9=E6=94=BE=E5=8A=A8?= =?UTF-8?q?=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前月↔年切换使用交叉淡入:两层同时合成,源视图渐隐、目标视图渐显。 现改为单向过渡:先翻转 isYearView 让源视图立刻从合成中移除, withFrameNanos 等一帧后再启动目标视图的 scale/alpha 动画,避免抖动。 --- .../plus/rua/project/CalendarViewModel.kt | 13 +- .../plus/rua/project/ui/CalendarMonthView.kt | 228 +++++++++--------- 2 files changed, 126 insertions(+), 115 deletions(-) diff --git a/shared/src/commonMain/kotlin/plus/rua/project/CalendarViewModel.kt b/shared/src/commonMain/kotlin/plus/rua/project/CalendarViewModel.kt index a2454f9..b468972 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/CalendarViewModel.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/CalendarViewModel.kt @@ -88,22 +88,26 @@ class CalendarViewModel( /** * 切换年视图。仅在展开态可用。 + * + * 切换瞬间立即翻转 isYearView,让对应方向的目标视图立刻接管渲染, + * 当前视图被直接移除;动画只作用在目标视图的 scale/alpha 上。 */ fun toggleYearView() { if (isCollapsed) return yearViewJob?.cancel() yearViewJob = coroutineScope.launch { if (isYearView) { + // 年 → 月:先切换状态让月视图开始合成,再等一帧避免首帧抖动 + isYearView = false + withFrameNanos { } _yearViewAnimatable.animateTo( 0f, tween(400, easing = FastOutSlowInEasing) ) - isYearView = false } else { + // 月 → 年:先切换状态让年视图开始合成 yearViewYear = selectedDate.year isYearView = true _yearViewAnimatable.snapTo(0f) - // 等待一帧让年视图先完成首次合成与布局, - // 避免首次进入年视图时动画时间被合成开销吞掉。 withFrameNanos { } _yearViewAnimatable.animateTo( 1f, tween(400, easing = FastOutSlowInEasing) @@ -120,12 +124,13 @@ class CalendarViewModel( val date = if (yearViewYear == today.year && today.month.number == month) today else LocalDate(yearViewYear, month, 1) selectedDate = date + isYearView = false yearViewJob?.cancel() yearViewJob = coroutineScope.launch { + withFrameNanos { } _yearViewAnimatable.animateTo( 0f, tween(400, easing = FastOutSlowInEasing) ) - isYearView = false } } diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt index b2bf002..b7c955b 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt @@ -168,13 +168,15 @@ fun CalendarMonthView( val anchorPivotX = ((currentMonth - 1) % 3 + 0.5f) / 3f val anchorPivotY = ((currentMonth - 1) / 3 + 0.5f) / 4f - // 月视图层缩放:从 1f 缩小到 ~0.3f(年网格单格 vs 月视图大小比) - val monthScale = 1f - yearProgress * 0.7f - val monthAlpha = (1f - yearProgress * 1.4f).coerceIn(0f, 1f) + // 过渡进度:0=目标视图刚出现,1=目标视图完全到位。 + // 月→年时 yearProgress 从 0→1,年→月时从 1→0,因此用 isYearView 同步翻转方向。 + val transitionProgress = if (viewModel.isYearView) yearProgress else 1f - yearProgress + val targetAlpha = transitionProgress.coerceIn(0f, 1f) - // 年视图层缩放:从 ~3.3f 放大到 1f - val yearScale = lerp(3.3f, 1f, yearProgress) - val yearAlpha = ((yearProgress - 0.2f) / 0.8f).coerceIn(0f, 1f) + // 月视图层缩放:从 0.3f(年网格单格大小)放大到 1f + val monthScale = lerp(0.3f, 1f, transitionProgress) + // 年视图层缩放:从 3.3f(月视图被放大到一格那么大的反向比例)缩小到 1f + val yearScale = lerp(3.3f, 1f, transitionProgress) Box( modifier = modifier @@ -185,93 +187,118 @@ fun CalendarMonthView( screenHeightPx = size.height } ) { - // 月视图层 - Column( - modifier = Modifier - .fillMaxSize() - .graphicsLayer { - scaleX = monthScale - scaleY = monthScale - alpha = monthAlpha - transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY) - } - .padding(horizontal = HORIZONTAL_PADDING_DP.dp) - ) { - MonthHeader( - year = currentYear, - month = currentMonth, - weekNumber = viewModel.getIsoWeekNumber(viewModel.selectedDate), - showToday = viewModel.selectedDate != today, - onToggleYearView = { viewModel.toggleYearView() }, - onToday = { - viewModel.selectDate(today) - @Suppress("DEPRECATION") // monthNumber 无替代 API - val targetPage = yearMonthToPage( - today.year, today.month.number, - today.year, today.month.number - ) - if (targetPage != pagerState.currentPage) { - coroutineScope.launch { pagerState.animateScrollToPage(targetPage) } - } - }, - modifier = Modifier.onSizeChanged { size -> - monthHeaderHeightPx = size.height - } - ) - WeekdayHeader( - modifier = Modifier.fillMaxWidth().padding(bottom = ROW_PADDING_DP.dp) - .onSizeChanged { size -> - weekdayHeaderHeightPx = size.height - } - ) - if (viewModel.isCollapsed && viewModel.collapseProgress >= 1f) { - WeekPager( - selectedDate = viewModel.selectedDate, - today = today, - onDateClick = { date -> viewModel.selectDate(date) }, - onWeekChanged = { weekMonday -> - val weekSunday = weekMonday.plus(DatePeriod(days = 6)) - val date = when { - today in weekMonday..weekSunday -> today - weekMonday.month != weekSunday.month -> { - if (weekMonday < viewModel.selectedDate) { - @Suppress("DEPRECATION") // monthNumber 无替代 API - LocalDate(weekSunday.year, weekSunday.month.number, 1) - } else { - weekMonday - } - } - else -> weekMonday - } - viewModel.selectDate(date) - }, - modifier = pagerModifier - ) + // 月视图层:仅在非年视图时渲染,年视图激活时立即移除。 + if (!viewModel.isYearView) { + val dragRangeMinPx = with(density) { DRAG_RANGE_MIN_DP.dp.toPx() } + val dragRangePx = if (effectiveRowHeightPx > 0) { + maxOf((effectiveWeeks - 1) * effectiveRowHeightPx.toFloat(), dragRangeMinPx) } else { - CalendarPager( - selectedDate = viewModel.selectedDate, - today = today, - onDateClick = { date -> viewModel.selectDate(date) }, - onMonthChanged = { year, month -> - @Suppress("DEPRECATION") // monthNumber 无替代 API - val date = if (year == today.year && today.month.number == month) today - else LocalDate(year, month, 1) - viewModel.selectDate(date) - }, - collapseProgress = viewModel.collapseProgress, - rowHeightPx = rowHeightPx, - effectiveWeeks = effectiveWeeks, - onRowHeightMeasured = { h -> - if (h > 0) rowHeightPx = h - }, - pagerState = pagerState, - modifier = pagerModifier - ) + dragRangeMinPx + } + + Box( + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + scaleX = monthScale + scaleY = monthScale + alpha = targetAlpha + transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY) + } + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = HORIZONTAL_PADDING_DP.dp) + ) { + MonthHeader( + year = currentYear, + month = currentMonth, + weekNumber = viewModel.getIsoWeekNumber(viewModel.selectedDate), + showToday = viewModel.selectedDate != today, + onToggleYearView = { viewModel.toggleYearView() }, + onToday = { + viewModel.selectDate(today) + @Suppress("DEPRECATION") // monthNumber 无替代 API + val targetPage = yearMonthToPage( + today.year, today.month.number, + today.year, today.month.number + ) + if (targetPage != pagerState.currentPage) { + coroutineScope.launch { pagerState.animateScrollToPage(targetPage) } + } + }, + modifier = Modifier.onSizeChanged { size -> + monthHeaderHeightPx = size.height + } + ) + WeekdayHeader( + modifier = Modifier.fillMaxWidth().padding(bottom = ROW_PADDING_DP.dp) + .onSizeChanged { size -> + weekdayHeaderHeightPx = size.height + } + ) + if (viewModel.isCollapsed && viewModel.collapseProgress >= 1f) { + WeekPager( + selectedDate = viewModel.selectedDate, + today = today, + onDateClick = { date -> viewModel.selectDate(date) }, + onWeekChanged = { weekMonday -> + val weekSunday = weekMonday.plus(DatePeriod(days = 6)) + val date = when { + today in weekMonday..weekSunday -> today + weekMonday.month != weekSunday.month -> { + if (weekMonday < viewModel.selectedDate) { + @Suppress("DEPRECATION") // monthNumber 无替代 API + LocalDate(weekSunday.year, weekSunday.month.number, 1) + } else { + weekMonday + } + } + else -> weekMonday + } + viewModel.selectDate(date) + }, + modifier = pagerModifier + ) + } else { + CalendarPager( + selectedDate = viewModel.selectedDate, + today = today, + onDateClick = { date -> viewModel.selectDate(date) }, + onMonthChanged = { year, month -> + @Suppress("DEPRECATION") // monthNumber 无替代 API + val date = if (year == today.year && today.month.number == month) today + else LocalDate(year, month, 1) + viewModel.selectDate(date) + }, + collapseProgress = viewModel.collapseProgress, + rowHeightPx = rowHeightPx, + effectiveWeeks = effectiveWeeks, + onRowHeightMeasured = { h -> + if (h > 0) rowHeightPx = h + }, + pagerState = pagerState, + modifier = pagerModifier + ) + } + } + + if (cardHeightPx > 0) { + BottomCard( + viewModel = viewModel, + dragRangePx = dragRangePx, + modifier = Modifier + .fillMaxWidth() + .height(with(density) { cardHeightPx.toDp() }) + .align(Alignment.BottomCenter) + ) + } } } - // 年视图层:HorizontalPager 支持左右滑动切年 - if (viewModel.isYearView || yearProgress > 0.01f) { + // 年视图层:仅在年视图激活时渲染;HorizontalPager 支持左右滑动切年。 + if (viewModel.isYearView) { HorizontalPager( state = yearPagerState, beyondViewportPageCount = 1, @@ -281,7 +308,7 @@ fun CalendarMonthView( .graphicsLayer { scaleX = yearScale scaleY = yearScale - alpha = yearAlpha + alpha = targetAlpha transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY) } .padding(horizontal = HORIZONTAL_PADDING_DP.dp) @@ -312,26 +339,5 @@ fun CalendarMonthView( ) } } - - // BottomCard:年视图时隐藏 - if (yearProgress < 0.01f) { - val dragRangeMinPx = with(density) { DRAG_RANGE_MIN_DP.dp.toPx() } - val dragRangePx = if (effectiveRowHeightPx > 0) { - maxOf((effectiveWeeks - 1) * effectiveRowHeightPx.toFloat(), dragRangeMinPx) - } else { - dragRangeMinPx - } - - if (cardHeightPx > 0) { - BottomCard( - viewModel = viewModel, - dragRangePx = dragRangePx, - modifier = Modifier - .fillMaxWidth() - .height(with(density) { cardHeightPx.toDp() }) - .align(Alignment.BottomCenter) - ) - } - } } }