MiniMonth 纯 Canvas 绘制:消除 96 个 Text 组件的测量开销

trace 显示 TextStringSimpleNode::measure 累计 100+ms,是 measure 阶段
主要开销。MiniMonth 中 12 个标题 Text + 84 个星期标签 Text = 96 个
Text 组件。

修复:
1. YearGridView 中预计算 titleLayouts + weekdayLayouts
2. MiniMonth 改为单个 Canvas 统一绘制标题 + 星期行 + 日期
3. 消除所有 Text 组件,TextStringSimpleNode::measure 归零
This commit is contained in:
meyou 2026-05-18 23:24:04 +08:00
parent 25940c8a24
commit 987b965956
No known key found for this signature in database

View File

@ -26,12 +26,10 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextMeasurer
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import kotlinx.datetime.DatePeriod import kotlinx.datetime.DatePeriod
@ -107,6 +105,30 @@ fun YearGridView(
}.toMap() }.toMap()
} }
// P0-H: 预测量月份标题(选中/非选中两种颜色)
val titleLayouts = remember(textMeasurer, colors) {
(1..12).flatMap { month ->
val text = "${month}"
listOf(
(month to true) to textMeasurer.measure(
text,
TextStyle(fontSize = 10.sp, color = colors.titleSelected, fontWeight = FontWeight.Bold)
),
(month to false) to textMeasurer.measure(
text,
TextStyle(fontSize = 10.sp, color = colors.titleNormal)
)
)
}.toMap()
}
// P0-H: 预测量星期标签
val weekdayLayouts = remember(textMeasurer, colors) {
WEEKDAY_LABELS.associate { label ->
label to textMeasurer.measure(label, TextStyle(fontSize = 8.sp, color = colors.weekday))
}
}
Column( Column(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
@ -132,9 +154,9 @@ fun YearGridView(
today = today, today = today,
days = monthDays[month - 1], days = monthDays[month - 1],
colors = colors, colors = colors,
textMeasurer = textMeasurer,
dayTextStyle = dayTextStyle,
dayLayouts = dayLayouts, dayLayouts = dayLayouts,
titleLayouts = titleLayouts,
weekdayLayouts = weekdayLayouts,
onClick = { onMonthClick(month) }, onClick = { onMonthClick(month) },
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
@ -147,7 +169,9 @@ fun YearGridView(
} }
/** /**
* 精简版月历月份标题 + 星期行 + 日期数字网格 * 精简版月历月份标题 + 星期行 + 日期数字网格全部 Canvas 绘制
*
* 消除 Text 组件避免 TextStringSimpleNode::measure 开销
*/ */
@Composable @Composable
private fun MiniMonth( private fun MiniMonth(
@ -156,13 +180,20 @@ private fun MiniMonth(
today: LocalDate, today: LocalDate,
days: List<MiniDayData>, days: List<MiniDayData>,
colors: MiniMonthColors, colors: MiniMonthColors,
textMeasurer: TextMeasurer,
dayTextStyle: TextStyle,
dayLayouts: Map<Pair<Int, Color>, androidx.compose.ui.text.TextLayoutResult>, dayLayouts: Map<Pair<Int, Color>, androidx.compose.ui.text.TextLayoutResult>,
titleLayouts: Map<Pair<Int, Boolean>, androidx.compose.ui.text.TextLayoutResult>,
weekdayLayouts: Map<String, androidx.compose.ui.text.TextLayoutResult>,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val titleColor = if (isSelected) colors.titleSelected else colors.titleNormal val density = LocalDensity.current
val dayRowCount = days.size / 7
val titleHeightPx = with(density) { 14.sp.toPx() }
val weekdayHeightPx = with(density) { 12.sp.toPx() }
val dayCellHeightPx = with(density) { (12.sp.toPx() + 4.dp.toPx()) }
val totalHeight = with(density) {
(titleHeightPx + weekdayHeightPx + dayRowCount * dayCellHeightPx).toDp()
}
Column( Column(
modifier = modifier modifier = modifier
@ -171,42 +202,40 @@ private fun MiniMonth(
.padding(vertical = 2.dp), .padding(vertical = 2.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
// 月份标题 Canvas(modifier = Modifier.fillMaxWidth().height(totalHeight)) {
Text( val cellWidth = size.width / 7f
text = "${month}",
color = titleColor, // 1. 绘制标题
fontSize = 10.sp, val titleLayout = titleLayouts[month to isSelected]!!
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, drawText(
textAlign = TextAlign.Center textLayoutResult = titleLayout,
) topLeft = Offset(
// 星期行 (size.width - titleLayout.size.width) / 2f,
Row( 0f
modifier = Modifier.fillMaxWidth(), )
horizontalArrangement = Arrangement.SpaceEvenly )
) {
WEEKDAY_LABELS.forEach { label -> // 2. 绘制星期行
Text( val weekdayY = titleHeightPx
text = label, WEEKDAY_LABELS.forEachIndexed { i, label ->
color = colors.weekday, val layout = weekdayLayouts[label]!!
fontSize = 8.sp, drawText(
textAlign = TextAlign.Center, textLayoutResult = layout,
modifier = Modifier.weight(1f) topLeft = Offset(
i * cellWidth + (cellWidth - layout.size.width) / 2f,
weekdayY + (weekdayHeightPx - layout.size.height) / 2f
)
) )
} }
}
// 日期网格 — Canvas 绘制 // 3. 绘制日期网格
val density = LocalDensity.current val dayGridY = titleHeightPx + weekdayHeightPx
val dayRowCount = days.size / 7
val canvasHeight = with(density) { (dayRowCount * (12.sp.toPx() + 4.dp.toPx())).toDp() }
Canvas(modifier = Modifier.fillMaxWidth().height(canvasHeight)) {
val cellWidth = size.width / 7f
val rowHeightPx = size.height / dayRowCount
days.forEachIndexed { index, dayData -> days.forEachIndexed { index, dayData ->
val row = index / 7 val row = index / 7
val col = index % 7 val col = index % 7
val centerX = col * cellWidth + cellWidth / 2f val centerX = col * cellWidth + cellWidth / 2f
val centerY = row * rowHeightPx + rowHeightPx / 2f val centerY = dayGridY + row * dayCellHeightPx + dayCellHeightPx / 2f
val isToday = dayData.date == today && dayData.isCurrentMonth val isToday = dayData.date == today && dayData.isCurrentMonth
val dayNum = if (dayData.isCurrentMonth) dayData.date.day else 0 val dayNum = if (dayData.isCurrentMonth) dayData.date.day else 0
@ -217,7 +246,7 @@ private fun MiniMonth(
} }
if (isToday) { if (isToday) {
val radius = cellWidth.coerceAtMost(rowHeightPx) / 2f * 0.8f val radius = cellWidth.coerceAtMost(dayCellHeightPx) / 2f * 0.8f
drawCircle( drawCircle(
color = colors.todayBg, color = colors.todayBg,
radius = radius, radius = radius,