避免月/年视图切换时整棵树销毁:共存 + 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,让对应方向的目标视图立刻接管渲染,
|
* 月/年视图始终共存于组合树中,由 alpha 控制可见性。
|
||||||
* 当前视图被直接移除;动画只作用在目标视图的 scale/alpha 上。
|
* 翻转 isYearView 后启动 Animatable 动画,驱动对应方向视图的 scale/alpha 变化。
|
||||||
*/
|
*/
|
||||||
fun toggleYearView() {
|
fun toggleYearView() {
|
||||||
yearViewJob?.cancel()
|
yearViewJob?.cancel()
|
||||||
yearViewJob = coroutineScope.launch {
|
yearViewJob = coroutineScope.launch {
|
||||||
// 折叠态先展开回月视图,再切换年视图
|
|
||||||
if (isCollapsed) {
|
if (isCollapsed) {
|
||||||
_collapseAnimatable.animateTo(
|
_collapseAnimatable.animateTo(
|
||||||
0f, spring(dampingRatio = 0.8f, stiffness = 400f)
|
0f, spring(dampingRatio = 0.8f, stiffness = 400f)
|
||||||
@ -122,32 +121,19 @@ class CalendarViewModel(
|
|||||||
isCollapsed = false
|
isCollapsed = false
|
||||||
}
|
}
|
||||||
if (isYearView) {
|
if (isYearView) {
|
||||||
// 年 → 月:先启动动画(年视图开始淡出),等一帧后翻转 isYearView(月视图开始组合)
|
// 年 → 月:动画驱动 yearViewProgress 1f→0f,月视图同步放大/淡入
|
||||||
composeTraceBeginSection("YearView→MonthView")
|
_yearViewAnimatable.animateTo(
|
||||||
_yearViewAnimatable.snapTo(1f)
|
0f, tween(400, easing = FastOutSlowInEasing)
|
||||||
val animJob = launch {
|
)
|
||||||
_yearViewAnimatable.animateTo(
|
|
||||||
0f, tween(400, easing = FastOutSlowInEasing)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
withFrameNanos { }
|
|
||||||
isYearView = false
|
isYearView = false
|
||||||
animJob.join()
|
|
||||||
composeTraceEndSection()
|
|
||||||
} else {
|
} else {
|
||||||
// 月 → 年:先启动动画(月视图开始缩小),等一帧后翻转 isYearView(年视图开始组合)
|
// 月 → 年:动画驱动 yearViewProgress 0f→1f,年视图同步缩小/淡入
|
||||||
composeTraceBeginSection("MonthView→YearView")
|
|
||||||
yearViewYear = selectedDate.year
|
yearViewYear = selectedDate.year
|
||||||
_yearViewAnimatable.snapTo(0f)
|
_yearViewAnimatable.snapTo(0f)
|
||||||
val animJob = launch {
|
_yearViewAnimatable.animateTo(
|
||||||
_yearViewAnimatable.animateTo(
|
1f, tween(400, easing = FastOutSlowInEasing)
|
||||||
1f, tween(400, easing = FastOutSlowInEasing)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
withFrameNanos { }
|
|
||||||
isYearView = true
|
isYearView = true
|
||||||
animJob.join()
|
|
||||||
composeTraceEndSection()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,8 +59,6 @@ import kotlinx.datetime.number
|
|||||||
import kotlinx.datetime.plus
|
import kotlinx.datetime.plus
|
||||||
import kotlinx.datetime.todayIn
|
import kotlinx.datetime.todayIn
|
||||||
import plus.rua.project.CalendarViewModel
|
import plus.rua.project.CalendarViewModel
|
||||||
import plus.rua.project.composeTraceBeginSection
|
|
||||||
import plus.rua.project.composeTraceEndSection
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
|
|
||||||
@ -208,36 +206,33 @@ fun CalendarMonthView(
|
|||||||
screenHeightPx = size.height
|
screenHeightPx = size.height
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
// 月视图层:仅在非年视图时渲染,年视图激活时立即移除。
|
// 月视图层:始终存在于组合树中,通过 alpha 控制可见性/触摸,避免 isYearView
|
||||||
if (!viewModel.isYearView) {
|
// 切换时触发整棵树销毁(Compose:onForgotten 600ms)。scale 动画保留在 graphicsLayer。
|
||||||
composeTraceBeginSection("MonthView:Compose")
|
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 dragRangeMinPx = with(density) { DRAG_RANGE_MIN_DP.dp.toPx() }
|
||||||
val dragRangePx = if (effectiveRowHeightPx > 0) {
|
val dragRangePx = if (effectiveRowHeightPx > 0) {
|
||||||
maxOf((effectiveWeeks - 1) * effectiveRowHeightPx.toFloat(), dragRangeMinPx)
|
maxOf((effectiveWeeks - 1) * effectiveRowHeightPx.toFloat(), dragRangeMinPx)
|
||||||
} else {
|
} else {
|
||||||
dragRangeMinPx
|
dragRangeMinPx
|
||||||
}
|
}
|
||||||
|
Column(
|
||||||
val monthProgress = 1f - viewModel.yearViewProgress
|
|
||||||
// 组合阶段计算:lambda 捕获快照值,避免 draw 阶段读到已更新的 rowHeightPx
|
|
||||||
// 但 layout 仍用旧值导致行堆叠
|
|
||||||
val layoutReady = rowHeightPx > 0
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.graphicsLayer {
|
.padding(horizontal = HORIZONTAL_PADDING_DP.dp)
|
||||||
val scale = lerp(0.3f, 1f, monthProgress)
|
|
||||||
scaleX = scale
|
|
||||||
scaleY = scale
|
|
||||||
alpha = if (layoutReady) monthProgress.coerceIn(0f, 1f) else 0f
|
|
||||||
transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY)
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = HORIZONTAL_PADDING_DP.dp)
|
|
||||||
) {
|
|
||||||
MonthHeader(
|
MonthHeader(
|
||||||
year = currentYear,
|
year = currentYear,
|
||||||
month = currentMonth,
|
month = currentMonth,
|
||||||
@ -319,25 +314,21 @@ fun CalendarMonthView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
composeTraceEndSection()
|
// 年视图层:始终存在于组合树中,通过 alpha 控制可见性/触摸。
|
||||||
}
|
val yearProgress = viewModel.yearViewProgress
|
||||||
|
val yearAlpha = yearProgress.coerceIn(0f, 1f)
|
||||||
// 年视图层:标题固定,HorizontalPager 只包裹网格。
|
Column(
|
||||||
if (viewModel.isYearView) {
|
modifier = Modifier
|
||||||
val yearProgress = viewModel.yearViewProgress
|
.fillMaxSize()
|
||||||
composeTraceBeginSection("YearView:Compose")
|
.alpha(yearAlpha)
|
||||||
Column(
|
.graphicsLayer {
|
||||||
modifier = Modifier
|
val scale = lerp(3.3f, 1f, yearProgress)
|
||||||
.fillMaxSize()
|
scaleX = scale
|
||||||
.graphicsLayer {
|
scaleY = scale
|
||||||
val scale = lerp(3.3f, 1f, yearProgress)
|
transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY)
|
||||||
scaleX = scale
|
}
|
||||||
scaleY = scale
|
.padding(horizontal = HORIZONTAL_PADDING_DP.dp)
|
||||||
alpha = yearProgress.coerceIn(0f, 1f)
|
) {
|
||||||
transformOrigin = TransformOrigin(anchorPivotX, anchorPivotY)
|
|
||||||
}
|
|
||||||
.padding(horizontal = HORIZONTAL_PADDING_DP.dp)
|
|
||||||
) {
|
|
||||||
YearHeader(
|
YearHeader(
|
||||||
year = viewModel.yearViewYear,
|
year = viewModel.yearViewYear,
|
||||||
onYearChange = { newYear ->
|
onYearChange = { newYear ->
|
||||||
@ -383,9 +374,6 @@ fun CalendarMonthView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
composeTraceEndSection()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FAB 浮动按钮
|
// FAB 浮动按钮
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = { isMenuExpanded = !isMenuExpanded },
|
onClick = { isMenuExpanded = !isMenuExpanded },
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user