避免月/年视图切换时整棵树销毁:共存 + Modifier.alpha 控制
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 的组件销毁开销。
This commit is contained in:
parent
9fd877485f
commit
fab0a5eba8
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user