优化月→年视图切换性能(第二轮):延迟预组合 + 缓存提升
- 年视图 beyondViewportPageCount 动画完成后再恢复为 1,消除切换后 283ms onMeasure 阻塞 - MiniMonth 主题色提取到 YearGridView 级别 remember 缓存(72 次 → 1 次读取) - TextMeasurer 从每 MiniMonth 独立实例改为 YearGridView 共享(12 个 → 1 个) - 预测量 1-31 × 3 颜色的 TextLayoutResult(93 个缓存),消除 360+ 次 measure() 调用 - 12 个月的 generateMiniMonthDays 在 YearGridView 级别预计算并缓存
This commit is contained in:
parent
914e882fe1
commit
a4bd56a8a9
@ -52,6 +52,7 @@ import androidx.compose.ui.input.pointer.pointerInput
|
|||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.DatePeriod
|
import kotlinx.datetime.DatePeriod
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
@ -100,10 +101,11 @@ fun CalendarMonthView(
|
|||||||
isMenuExpanded = false
|
isMenuExpanded = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 年视图首帧后恢复预组合,避免首帧同时组合 3 页 × 12 月 = 36 个 MiniMonth
|
// 年视图动画完成后再恢复预组合,避免动画期间触发邻页组合阻塞帧
|
||||||
LaunchedEffect(viewModel.isYearView) {
|
LaunchedEffect(viewModel.isYearView) {
|
||||||
if (viewModel.isYearView) {
|
if (viewModel.isYearView) {
|
||||||
withFrameNanos { }
|
snapshotFlow { viewModel.yearViewProgress }
|
||||||
|
.first { it >= 1f }
|
||||||
yearPagerBeyondViewport = 1
|
yearPagerBeyondViewport = 1
|
||||||
} else {
|
} else {
|
||||||
yearPagerBeyondViewport = 0
|
yearPagerBeyondViewport = 0
|
||||||
|
|||||||
@ -25,12 +25,13 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
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.text.TextMeasurer
|
||||||
|
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.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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import kotlinx.datetime.DatePeriod
|
import kotlinx.datetime.DatePeriod
|
||||||
@ -43,6 +44,16 @@ import plus.rua.project.composeTraceEndSection
|
|||||||
|
|
||||||
private val WEEKDAY_LABELS = listOf("一", "二", "三", "四", "五", "六", "日")
|
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 月历网格。
|
* 年视图 4×3 月历网格。
|
||||||
*
|
*
|
||||||
@ -61,6 +72,41 @@ fun YearGridView(
|
|||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
composeTraceBeginSection("YearGridView:$year")
|
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(
|
Column(
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
@ -81,10 +127,14 @@ fun YearGridView(
|
|||||||
(0 until 3).forEach { col ->
|
(0 until 3).forEach { col ->
|
||||||
val month = row * 3 + col + 1
|
val month = row * 3 + col + 1
|
||||||
MiniMonth(
|
MiniMonth(
|
||||||
year = year,
|
|
||||||
month = month,
|
month = month,
|
||||||
isSelected = month == selectedMonth,
|
isSelected = month == selectedMonth,
|
||||||
today = today,
|
today = today,
|
||||||
|
days = monthDays[month - 1],
|
||||||
|
colors = colors,
|
||||||
|
textMeasurer = textMeasurer,
|
||||||
|
dayTextStyle = dayTextStyle,
|
||||||
|
dayLayouts = dayLayouts,
|
||||||
onClick = { onMonthClick(month) },
|
onClick = { onMonthClick(month) },
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
@ -101,29 +151,18 @@ fun YearGridView(
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun MiniMonth(
|
private fun MiniMonth(
|
||||||
year: Int,
|
|
||||||
month: Int,
|
month: Int,
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
today: LocalDate,
|
today: LocalDate,
|
||||||
|
days: List<MiniDayData>,
|
||||||
|
colors: MiniMonthColors,
|
||||||
|
textMeasurer: TextMeasurer,
|
||||||
|
dayTextStyle: TextStyle,
|
||||||
|
dayLayouts: Map<Pair<Int, Color>, androidx.compose.ui.text.TextLayoutResult>,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val days = remember(year, month) { generateMiniMonthDays(year, month) }
|
val titleColor = if (isSelected) colors.titleSelected else colors.titleNormal
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@ -148,7 +187,7 @@ private fun MiniMonth(
|
|||||||
WEEKDAY_LABELS.forEach { label ->
|
WEEKDAY_LABELS.forEach { label ->
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
color = weekdayColor,
|
color = colors.weekday,
|
||||||
fontSize = 8.sp,
|
fontSize = 8.sp,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
@ -170,34 +209,32 @@ private fun MiniMonth(
|
|||||||
val centerY = row * rowHeightPx + rowHeightPx / 2f
|
val centerY = row * rowHeightPx + rowHeightPx / 2f
|
||||||
|
|
||||||
val isToday = dayData.date == today && dayData.isCurrentMonth
|
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 {
|
val textColor: Color = when {
|
||||||
!dayData.isCurrentMonth -> otherMonthColor
|
!dayData.isCurrentMonth -> colors.otherMonth
|
||||||
isToday -> todayTextColor
|
isToday -> colors.todayText
|
||||||
else -> dayColor
|
else -> colors.day
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isToday) {
|
if (isToday) {
|
||||||
val radius = cellWidth.coerceAtMost(rowHeightPx) / 2f * 0.8f
|
val radius = cellWidth.coerceAtMost(rowHeightPx) / 2f * 0.8f
|
||||||
drawCircle(
|
drawCircle(
|
||||||
color = todayBgColor,
|
color = colors.todayBg,
|
||||||
radius = radius,
|
radius = radius,
|
||||||
center = Offset(centerX, centerY)
|
center = Offset(centerX, centerY)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text.isNotEmpty()) {
|
if (dayNum > 0) {
|
||||||
val measured = textMeasurer.measure(
|
dayLayouts[dayNum to textColor]?.let { layoutResult ->
|
||||||
text = text,
|
drawText(
|
||||||
style = dayTextStyle.copy(color = textColor)
|
textLayoutResult = layoutResult,
|
||||||
)
|
topLeft = Offset(
|
||||||
drawText(
|
x = centerX - layoutResult.size.width / 2f,
|
||||||
textLayoutResult = measured,
|
y = centerY - layoutResult.size.height / 2f
|
||||||
topLeft = Offset(
|
)
|
||||||
x = centerX - measured.size.width / 2f,
|
|
||||||
y = centerY - measured.size.height / 2f
|
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user