From a4bd56a8a9fba17d912706e7fc7d2f4517b467f4 Mon Sep 17 00:00:00 2001 From: xfy Date: Mon, 18 May 2026 17:05:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9C=88=E2=86=92=E5=B9=B4?= =?UTF-8?q?=E8=A7=86=E5=9B=BE=E5=88=87=E6=8D=A2=E6=80=A7=E8=83=BD=EF=BC=88?= =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E8=BD=AE=EF=BC=89=EF=BC=9A=E5=BB=B6=E8=BF=9F?= =?UTF-8?q?=E9=A2=84=E7=BB=84=E5=90=88=20+=20=E7=BC=93=E5=AD=98=E6=8F=90?= =?UTF-8?q?=E5=8D=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 年视图 beyondViewportPageCount 动画完成后再恢复为 1,消除切换后 283ms onMeasure 阻塞 - MiniMonth 主题色提取到 YearGridView 级别 remember 缓存(72 次 → 1 次读取) - TextMeasurer 从每 MiniMonth 独立实例改为 YearGridView 共享(12 个 → 1 个) - 预测量 1-31 × 3 颜色的 TextLayoutResult(93 个缓存),消除 360+ 次 measure() 调用 - 12 个月的 generateMiniMonthDays 在 YearGridView 级别预计算并缓存 --- .../plus/rua/project/ui/CalendarMonthView.kt | 6 +- .../plus/rua/project/ui/YearGridView.kt | 111 ++++++++++++------ 2 files changed, 78 insertions(+), 39 deletions(-) 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 38a9916..93974eb 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt @@ -52,6 +52,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.datetime.DatePeriod import kotlinx.datetime.LocalDate @@ -100,10 +101,11 @@ fun CalendarMonthView( isMenuExpanded = false } - // 年视图首帧后恢复预组合,避免首帧同时组合 3 页 × 12 月 = 36 个 MiniMonth + // 年视图动画完成后再恢复预组合,避免动画期间触发邻页组合阻塞帧 LaunchedEffect(viewModel.isYearView) { if (viewModel.isYearView) { - withFrameNanos { } + snapshotFlow { viewModel.yearViewProgress } + .first { it >= 1f } yearPagerBeyondViewport = 1 } else { yearPagerBeyondViewport = 0 diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/YearGridView.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/YearGridView.kt index daeeb0c..9ca71ca 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/YearGridView.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/YearGridView.kt @@ -25,12 +25,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextMeasurer +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.drawText import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.datetime.DatePeriod @@ -43,6 +44,16 @@ import plus.rua.project.composeTraceEndSection private val WEEKDAY_LABELS = listOf("一", "二", "三", "四", "五", "六", "日") +private data class MiniMonthColors( + val titleSelected: Color, + val titleNormal: Color, + val weekday: Color, + val day: Color, + val otherMonth: Color, + val todayBg: Color, + val todayText: Color +) + /** * 年视图 4×3 月历网格。 * @@ -61,6 +72,41 @@ fun YearGridView( modifier: Modifier = Modifier ) { composeTraceBeginSection("YearGridView:$year") + + // P0-F: 主题色在 YearGridView 级别一次性读取并缓存 + val colorScheme = MaterialTheme.colorScheme + val colors = remember(colorScheme) { + MiniMonthColors( + titleSelected = colorScheme.primary, + titleNormal = colorScheme.onSurface, + weekday = colorScheme.onSurface.copy(alpha = 0.4f), + day = colorScheme.onSurface.copy(alpha = 0.6f), + otherMonth = colorScheme.onSurface.copy(alpha = 0.2f), + todayBg = colorScheme.primaryContainer, + todayText = colorScheme.onPrimaryContainer + ) + } + + // P0-F: 预计算全年 12 个月的日期数据,翻年时复用 + val monthDays = remember(year) { + (1..12).map { generateMiniMonthDays(year, it) } + } + + // P0-G: 共享 TextMeasurer + val textMeasurer = rememberTextMeasurer() + val dayTextStyle = remember { TextStyle(fontSize = 8.sp, lineHeight = 12.sp) } + + // P0-D: 预测量 1..31 × 3 种颜色 = 93 个 TextLayoutResult + val dayLayouts = remember(textMeasurer, dayTextStyle, colors) { + val days = 1..31 + val colorList = listOf(colors.day, colors.todayText, colors.otherMonth) + days.flatMap { d -> + colorList.map { c -> + (d to c) to textMeasurer.measure(d.toString(), dayTextStyle.copy(color = c)) + } + }.toMap() + } + Column( modifier = modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally @@ -81,10 +127,14 @@ fun YearGridView( (0 until 3).forEach { col -> val month = row * 3 + col + 1 MiniMonth( - year = year, month = month, isSelected = month == selectedMonth, today = today, + days = monthDays[month - 1], + colors = colors, + textMeasurer = textMeasurer, + dayTextStyle = dayTextStyle, + dayLayouts = dayLayouts, onClick = { onMonthClick(month) }, modifier = Modifier.weight(1f) ) @@ -101,29 +151,18 @@ fun YearGridView( */ @Composable private fun MiniMonth( - year: Int, month: Int, isSelected: Boolean, today: LocalDate, + days: List, + colors: MiniMonthColors, + textMeasurer: TextMeasurer, + dayTextStyle: TextStyle, + dayLayouts: Map, androidx.compose.ui.text.TextLayoutResult>, onClick: () -> Unit, modifier: Modifier = Modifier ) { - val days = remember(year, month) { generateMiniMonthDays(year, month) } - val titleColor = if (isSelected) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.onSurface - } - val weekdayColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f) - val dayColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) - val otherMonthColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f) - val todayBgColor = MaterialTheme.colorScheme.primaryContainer - val todayTextColor = MaterialTheme.colorScheme.onPrimaryContainer - - val textMeasurer = rememberTextMeasurer() - val dayTextStyle = remember { - TextStyle(fontSize = 8.sp, lineHeight = 12.sp) - } + val titleColor = if (isSelected) colors.titleSelected else colors.titleNormal Column( modifier = modifier @@ -148,7 +187,7 @@ private fun MiniMonth( WEEKDAY_LABELS.forEach { label -> Text( text = label, - color = weekdayColor, + color = colors.weekday, fontSize = 8.sp, textAlign = TextAlign.Center, modifier = Modifier.weight(1f) @@ -170,34 +209,32 @@ private fun MiniMonth( val centerY = row * rowHeightPx + rowHeightPx / 2f val isToday = dayData.date == today && dayData.isCurrentMonth - val text = if (dayData.isCurrentMonth) dayData.date.day.toString() else "" + val dayNum = if (dayData.isCurrentMonth) dayData.date.day else 0 val textColor: Color = when { - !dayData.isCurrentMonth -> otherMonthColor - isToday -> todayTextColor - else -> dayColor + !dayData.isCurrentMonth -> colors.otherMonth + isToday -> colors.todayText + else -> colors.day } if (isToday) { val radius = cellWidth.coerceAtMost(rowHeightPx) / 2f * 0.8f drawCircle( - color = todayBgColor, + color = colors.todayBg, radius = radius, center = Offset(centerX, centerY) ) } - if (text.isNotEmpty()) { - val measured = textMeasurer.measure( - text = text, - style = dayTextStyle.copy(color = textColor) - ) - drawText( - textLayoutResult = measured, - topLeft = Offset( - x = centerX - measured.size.width / 2f, - y = centerY - measured.size.height / 2f + if (dayNum > 0) { + dayLayouts[dayNum to textColor]?.let { layoutResult -> + drawText( + textLayoutResult = layoutResult, + topLeft = Offset( + x = centerX - layoutResult.size.width / 2f, + y = centerY - layoutResult.size.height / 2f + ) ) - ) + } } } }