From fab0a5eba8fe064c26ce6db7c65efaad02fc185e Mon Sep 17 00:00:00 2001 From: meyou <2636699780@qq.com> Date: Mon, 18 May 2026 23:05:01 +0800 Subject: [PATCH] =?UTF-8?q?=E9=81=BF=E5=85=8D=E6=9C=88/=E5=B9=B4=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=E5=88=87=E6=8D=A2=E6=97=B6=E6=95=B4=E6=A3=B5=E6=A0=91?= =?UTF-8?q?=E9=94=80=E6=AF=81=EF=BC=9A=E5=85=B1=E5=AD=98=20+=20Modifier.al?= =?UTF-8?q?pha=20=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Perfetto trace 显示 Compose:onForgotten 耗时 600ms,根因是 if(!isYearView)/if(isYearView) 条件渲染导致整棵子树在切换时被销毁重建。 修复: 1. 月视图和年视图始终共存于组合树中 2. 通过 Modifier.alpha() 控制可见性和触摸事件分发 3. graphicsLayer 仅保留 scale 动画,alpha 移出到 Modifier 层 4. 简化 toggleYearView():移除 withFrameNanos/animJob.join() 的复杂协程逻辑 两个视图通过 yearViewProgress 驱动的交叉淡入淡出同步切换, 消除 onForgotten 的组件销毁开销。 --- .../plus/rua/project/CalendarViewModel.kt | 34 +++----- .../plus/rua/project/ui/CalendarMonthView.kt | 78 ++++++++----------- 2 files changed, 43 insertions(+), 69 deletions(-) diff --git a/shared/src/commonMain/kotlin/plus/rua/project/CalendarViewModel.kt b/shared/src/commonMain/kotlin/plus/rua/project/CalendarViewModel.kt index eeb795a..d0df349 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/CalendarViewModel.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/CalendarViewModel.kt @@ -108,13 +108,12 @@ class CalendarViewModel( /** * 切换年视图。仅在展开态可用。 * - * 切换瞬间立即翻转 isYearView,让对应方向的目标视图立刻接管渲染, - * 当前视图被直接移除;动画只作用在目标视图的 scale/alpha 上。 + * 月/年视图始终共存于组合树中,由 alpha 控制可见性。 + * 翻转 isYearView 后启动 Animatable 动画,驱动对应方向视图的 scale/alpha 变化。 */ fun toggleYearView() { yearViewJob?.cancel() yearViewJob = coroutineScope.launch { - // 折叠态先展开回月视图,再切换年视图 if (isCollapsed) { _collapseAnimatable.animateTo( 0f, spring(dampingRatio = 0.8f, stiffness = 400f) @@ -122,32 +121,19 @@ class CalendarViewModel( isCollapsed = false } if (isYearView) { - // 年 → 月:先启动动画(年视图开始淡出),等一帧后翻转 isYearView(月视图开始组合) - composeTraceBeginSection("YearView→MonthView") - _yearViewAnimatable.snapTo(1f) - val animJob = launch { - _yearViewAnimatable.animateTo( - 0f, tween(400, easing = FastOutSlowInEasing) - ) - } - withFrameNanos { } + // 年 → 月:动画驱动 yearViewProgress 1f→0f,月视图同步放大/淡入 + _yearViewAnimatable.animateTo( + 0f, tween(400, easing = FastOutSlowInEasing) + ) isYearView = false - animJob.join() - composeTraceEndSection() } else { - // 月 → 年:先启动动画(月视图开始缩小),等一帧后翻转 isYearView(年视图开始组合) - composeTraceBeginSection("MonthView→YearView") + // 月 → 年:动画驱动 yearViewProgress 0f→1f,年视图同步缩小/淡入 yearViewYear = selectedDate.year _yearViewAnimatable.snapTo(0f) - val animJob = launch { - _yearViewAnimatable.animateTo( - 1f, tween(400, easing = FastOutSlowInEasing) - ) - } - withFrameNanos { } + _yearViewAnimatable.animateTo( + 1f, tween(400, easing = FastOutSlowInEasing) + ) isYearView = true - animJob.join() - composeTraceEndSection() } } } 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 11d6366..4f14a5f 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt @@ -59,8 +59,6 @@ import kotlinx.datetime.number import kotlinx.datetime.plus import kotlinx.datetime.todayIn import plus.rua.project.CalendarViewModel -import plus.rua.project.composeTraceBeginSection -import plus.rua.project.composeTraceEndSection import kotlin.math.abs import kotlin.time.Clock @@ -208,36 +206,33 @@ fun CalendarMonthView( screenHeightPx = size.height } ) { - // 月视图层:仅在非年视图时渲染,年视图激活时立即移除。 - if (!viewModel.isYearView) { - composeTraceBeginSection("MonthView:Compose") + // 月视图层:始终存在于组合树中,通过 alpha 控制可见性/触摸,避免 isYearView + // 切换时触发整棵树销毁(Compose:onForgotten 600ms)。scale 动画保留在 graphicsLayer。 + val monthProgress = 1f - viewModel.yearViewProgress + val layoutReady = rowHeightPx > 0 + val monthAlpha = if (layoutReady) monthProgress.coerceIn(0f, 1f) else 0f + Box( + modifier = Modifier + .fillMaxSize() + .alpha(monthAlpha) + .graphicsLayer { + val scale = lerp(0.3f, 1f, monthProgress) + scaleX = scale + scaleY = scale + transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY) + } + ) { val dragRangeMinPx = with(density) { DRAG_RANGE_MIN_DP.dp.toPx() } val dragRangePx = if (effectiveRowHeightPx > 0) { maxOf((effectiveWeeks - 1) * effectiveRowHeightPx.toFloat(), dragRangeMinPx) } else { dragRangeMinPx } - - val monthProgress = 1f - viewModel.yearViewProgress - // 组合阶段计算:lambda 捕获快照值,避免 draw 阶段读到已更新的 rowHeightPx - // 但 layout 仍用旧值导致行堆叠 - val layoutReady = rowHeightPx > 0 - Box( + Column( modifier = Modifier .fillMaxSize() - .graphicsLayer { - val scale = lerp(0.3f, 1f, monthProgress) - scaleX = scale - scaleY = scale - alpha = if (layoutReady) monthProgress.coerceIn(0f, 1f) else 0f - transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY) - } + .padding(horizontal = HORIZONTAL_PADDING_DP.dp) ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = HORIZONTAL_PADDING_DP.dp) - ) { MonthHeader( year = currentYear, month = currentMonth, @@ -319,25 +314,21 @@ fun CalendarMonthView( ) } } - composeTraceEndSection() - } - - // 年视图层:标题固定,HorizontalPager 只包裹网格。 - if (viewModel.isYearView) { - val yearProgress = viewModel.yearViewProgress - composeTraceBeginSection("YearView:Compose") - Column( - modifier = Modifier - .fillMaxSize() - .graphicsLayer { - val scale = lerp(3.3f, 1f, yearProgress) - scaleX = scale - scaleY = scale - alpha = yearProgress.coerceIn(0f, 1f) - transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY) - } - .padding(horizontal = HORIZONTAL_PADDING_DP.dp) - ) { + // 年视图层:始终存在于组合树中,通过 alpha 控制可见性/触摸。 + val yearProgress = viewModel.yearViewProgress + val yearAlpha = yearProgress.coerceIn(0f, 1f) + Column( + modifier = Modifier + .fillMaxSize() + .alpha(yearAlpha) + .graphicsLayer { + val scale = lerp(3.3f, 1f, yearProgress) + scaleX = scale + scaleY = scale + transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY) + } + .padding(horizontal = HORIZONTAL_PADDING_DP.dp) + ) { YearHeader( year = viewModel.yearViewYear, onYearChange = { newYear -> @@ -383,9 +374,6 @@ fun CalendarMonthView( ) } } - composeTraceEndSection() - } - // FAB 浮动按钮 FloatingActionButton( onClick = { isMenuExpanded = !isMenuExpanded },