Merge pull request #4 from xunrua/main

MiniMonth 纯 Canvas 绘制:消除 96 个 Text 组件的测量开销
This commit is contained in:
Sonetto 2026-05-18 23:30:22 +08:00 committed by GitHub
commit 3a0a4f885a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

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,