perf: 优化日历翻页重组、农历批量查询与 Canvas 绘制内存分配
This commit is contained in:
parent
69bffd8d20
commit
e16b7e6587
@ -147,9 +147,12 @@ class CalendarViewModel(
|
||||
|
||||
/** 聚合 UI 状态,减少 Compose 层分散订阅导致的重组。 */
|
||||
val uiState: StateFlow<CalendarUiState> = combine(
|
||||
combine(_selectedDate, _isCollapsed, _isYearView) { s, c, y -> Triple(s, c, y) },
|
||||
combine(_yearViewYear, _collapseProgress, _showLegalHoliday) { y, p, h -> Triple(y, p, h) }
|
||||
) { (selectedDate, isCollapsed, isYearView), (yearViewYear, collapseProgress, showLegalHoliday) ->
|
||||
_selectedDate,
|
||||
_isCollapsed,
|
||||
_isYearView,
|
||||
_yearViewYear,
|
||||
combine(_collapseProgress, _showLegalHoliday) { cp, h -> cp to h }
|
||||
) { selectedDate, isCollapsed, isYearView, yearViewYear, (collapseProgress, showLegalHoliday) ->
|
||||
CalendarUiState(
|
||||
selectedDate = selectedDate,
|
||||
isCollapsed = isCollapsed,
|
||||
|
||||
@ -31,6 +31,31 @@ class LunarCache(
|
||||
trimIfNeeded()
|
||||
computed
|
||||
}
|
||||
/**
|
||||
* 批量获取指定日期的信息,一次性加锁并返回 Map。
|
||||
*
|
||||
* @param dates 日期列表
|
||||
* @return 日期 → DayCellInfo 的映射
|
||||
*/
|
||||
suspend fun getOrComputeBatch(dates: List<LocalDate>): Map<LocalDate, DayCellInfo> = mutex.withLock {
|
||||
val result = HashMap<LocalDate, DayCellInfo>(dates.size)
|
||||
var modified = false
|
||||
for (date in dates) {
|
||||
val cached = cache[date]
|
||||
if (cached != null) {
|
||||
result[date] = cached
|
||||
} else {
|
||||
val computed = compute(date)
|
||||
cache[date] = computed
|
||||
result[date] = computed
|
||||
modified = true
|
||||
}
|
||||
}
|
||||
if (modified) {
|
||||
trimIfNeeded()
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量预计算并填充缓存。
|
||||
|
||||
@ -147,15 +147,29 @@ object PhotoProcessor {
|
||||
var result = rotate(source, rotationDegrees)
|
||||
// 2. 裁剪
|
||||
if (cropLeft != null && cropRight != null) {
|
||||
result = crop(result, cropLeft, cropTop, cropRight, cropBottom)
|
||||
val cropped = crop(result, cropLeft, cropTop, cropRight, cropBottom)
|
||||
if (result != source && result != cropped) {
|
||||
result.recycle()
|
||||
}
|
||||
result = cropped
|
||||
}
|
||||
// 3. 合成手写笔触
|
||||
if (strokes.isNotEmpty()) {
|
||||
result = drawStrokes(result, strokes, displayWidth, displayHeight)
|
||||
val withStrokes = drawStrokes(result, strokes, displayWidth, displayHeight)
|
||||
if (result != source && result != withStrokes) {
|
||||
result.recycle()
|
||||
}
|
||||
result = withStrokes
|
||||
}
|
||||
// 4. 落盘(JPEG 90% 质量)
|
||||
FileOutputStream(destFile).use { out ->
|
||||
result.compress(Bitmap.CompressFormat.JPEG, 90, out)
|
||||
try {
|
||||
FileOutputStream(destFile).use { out ->
|
||||
result.compress(Bitmap.CompressFormat.JPEG, 90, out)
|
||||
}
|
||||
} finally {
|
||||
if (result != source && !result.isRecycled) {
|
||||
result.recycle()
|
||||
}
|
||||
}
|
||||
return destFile.absolutePath
|
||||
}
|
||||
|
||||
@ -80,11 +80,7 @@ fun CalendarMonthPage(
|
||||
key1 = year,
|
||||
key2 = month
|
||||
) {
|
||||
val map = mutableMapOf<LocalDate, DayCellInfo>()
|
||||
for (dayData in days) {
|
||||
map[dayData.date] = LunarCache.default.getOrCompute(dayData.date)
|
||||
}
|
||||
value = map
|
||||
value = LunarCache.default.getOrComputeBatch(days.map { it.date })
|
||||
}
|
||||
|
||||
val holidayEdges = remember(lunarDataMap, year, month) {
|
||||
|
||||
@ -14,7 +14,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import plus.rua.project.composeTraceBeginSection
|
||||
import plus.rua.project.composeTraceEndSection
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.LocalDate
|
||||
@ -69,12 +69,6 @@ fun CalendarPager(
|
||||
}
|
||||
}
|
||||
|
||||
val currentPageOffsetFraction by remember {
|
||||
derivedStateOf { pagerState.currentPageOffsetFraction }
|
||||
}
|
||||
val currentPage by remember {
|
||||
derivedStateOf { pagerState.currentPage }
|
||||
}
|
||||
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
@ -82,13 +76,6 @@ fun CalendarPager(
|
||||
flingBehavior = PagerDefaults.flingBehavior(state = pagerState),
|
||||
modifier = modifier.testTag("calendar_pager")
|
||||
) { page ->
|
||||
val pageOffset = abs(currentPageOffsetFraction)
|
||||
val isCurrentPage = page == currentPage
|
||||
val alpha = if (isCurrentPage) {
|
||||
1f - pageOffset
|
||||
} else {
|
||||
pageOffset
|
||||
}
|
||||
val (year, month) = pageToYearMonth(page, initialYear, initialMonth)
|
||||
composeTraceBeginSection("CalendarPager:Page:$year-$month")
|
||||
CalendarMonthPage(
|
||||
@ -117,7 +104,11 @@ fun CalendarPager(
|
||||
shiftKindAt = shiftKindAt,
|
||||
showLegalHoliday = showLegalHoliday,
|
||||
onRowHeightMeasured = onRowHeightMeasured,
|
||||
modifier = Modifier.alpha(alpha)
|
||||
modifier = Modifier.graphicsLayer {
|
||||
val pageOffset = abs(pagerState.currentPageOffsetFraction)
|
||||
val isCurrentPage = page == pagerState.currentPage
|
||||
alpha = if (isCurrentPage) 1f - pageOffset else pageOffset
|
||||
}
|
||||
)
|
||||
composeTraceEndSection()
|
||||
}
|
||||
|
||||
@ -79,7 +79,13 @@ fun getMonthGridInfo(year: Int, month: Int): MonthGridInfo {
|
||||
* @return 网格行数
|
||||
*/
|
||||
fun calculateWeeksCount(year: Int, month: Int): Int {
|
||||
return getMonthGridInfo(year, month).rows
|
||||
val offset = LocalDate(year, Month(month), 1).dayOfWeek.ordinal
|
||||
val daysInMonth = when (month) {
|
||||
2 -> if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) 29 else 28
|
||||
4, 6, 9, 11 -> 30
|
||||
else -> 31
|
||||
}
|
||||
return ((offset + daysInMonth - 1) / 7) + 1
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -15,7 +15,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.datetime.DatePeriod
|
||||
@ -79,17 +79,14 @@ fun WeekPager(
|
||||
modifier = modifier
|
||||
) { page ->
|
||||
composeTraceBeginSection("WeekPager:Page")
|
||||
val pageOffset = abs(pagerState.currentPageOffsetFraction)
|
||||
val isCurrentPage = page == pagerState.currentPage
|
||||
val alpha = if (isCurrentPage) {
|
||||
1f - pageOffset
|
||||
} else {
|
||||
pageOffset
|
||||
}
|
||||
val weekMonday = pageToWeekMonday(page, initialWeekMonday)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.alpha(alpha)
|
||||
.graphicsLayer {
|
||||
val pageOffset = abs(pagerState.currentPageOffsetFraction)
|
||||
val isCurrentPage = page == pagerState.currentPage
|
||||
alpha = if (isCurrentPage) 1f - pageOffset else pageOffset
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.padding(vertical = ROW_PADDING_DP.dp)
|
||||
|
||||
@ -106,30 +106,32 @@ fun YearGridView(
|
||||
val dayTextStyle = remember { TextStyle(fontSize = 8.sp, lineHeight = 12.sp) }
|
||||
|
||||
// 延迟文本测量到下一帧,避免首帧阻塞
|
||||
val dayLayouts by produceState<Map<Pair<Int, Color>, androidx.compose.ui.text.TextLayoutResult>?>(null, textMeasurer, dayTextStyle, colors) {
|
||||
val dayLayouts by produceState<Map<Int, androidx.compose.ui.text.TextLayoutResult>?>(null, textMeasurer, dayTextStyle, colors) {
|
||||
val days = 1..31
|
||||
val colorList = listOf(colors.day, colors.todayText, colors.otherMonth)
|
||||
value = days.flatMap { d ->
|
||||
colorList.map { c ->
|
||||
(d to c) to textMeasurer.measure(d.toString(), dayTextStyle.copy(color = c))
|
||||
val colorTypes = listOf(0 to colors.day, 1 to colors.todayText, 2 to colors.otherMonth)
|
||||
val map = HashMap<Int, androidx.compose.ui.text.TextLayoutResult>(32 * 3)
|
||||
days.forEach { d ->
|
||||
colorTypes.forEach { (type, c) ->
|
||||
map[d * 3 + type] = textMeasurer.measure(d.toString(), dayTextStyle.copy(color = c))
|
||||
}
|
||||
}.toMap()
|
||||
}
|
||||
value = map
|
||||
}
|
||||
|
||||
val titleLayouts by produceState<Map<Pair<Int, Boolean>, androidx.compose.ui.text.TextLayoutResult>?>(null, textMeasurer, colors) {
|
||||
value = (1..12).flatMap { month ->
|
||||
val titleLayouts by produceState<Map<Int, androidx.compose.ui.text.TextLayoutResult>?>(null, textMeasurer, colors) {
|
||||
val map = HashMap<Int, androidx.compose.ui.text.TextLayoutResult>(13 * 2)
|
||||
(1..12).forEach { 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)
|
||||
)
|
||||
map[month * 2 + 1] = textMeasurer.measure(
|
||||
text,
|
||||
TextStyle(fontSize = 10.sp, color = colors.titleSelected, fontWeight = FontWeight.Bold)
|
||||
)
|
||||
}.toMap()
|
||||
map[month * 2] = textMeasurer.measure(
|
||||
text,
|
||||
TextStyle(fontSize = 10.sp, color = colors.titleNormal)
|
||||
)
|
||||
}
|
||||
value = map
|
||||
}
|
||||
|
||||
val weekdayLayouts by produceState<Map<String, androidx.compose.ui.text.TextLayoutResult>?>(null, textMeasurer, colors) {
|
||||
@ -193,8 +195,8 @@ private fun MiniMonth(
|
||||
today: LocalDate,
|
||||
days: List<MiniDayData>,
|
||||
colors: MiniMonthColors,
|
||||
dayLayouts: Map<Pair<Int, Color>, androidx.compose.ui.text.TextLayoutResult>?,
|
||||
titleLayouts: Map<Pair<Int, Boolean>, androidx.compose.ui.text.TextLayoutResult>?,
|
||||
dayLayouts: Map<Int, androidx.compose.ui.text.TextLayoutResult>?,
|
||||
titleLayouts: Map<Int, androidx.compose.ui.text.TextLayoutResult>?,
|
||||
weekdayLayouts: Map<String, androidx.compose.ui.text.TextLayoutResult>?,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
@ -226,7 +228,7 @@ private fun MiniMonth(
|
||||
val cellWidth = size.width / 7f
|
||||
|
||||
// 1. 绘制标题
|
||||
val titleLayout = tl[month to isSelected]!!
|
||||
val titleLayout = tl[month * 2 + (if (isSelected) 1 else 0)]!!
|
||||
drawText(
|
||||
textLayoutResult = titleLayout,
|
||||
topLeft = Offset(
|
||||
@ -259,9 +261,14 @@ private fun MiniMonth(
|
||||
|
||||
val isToday = dayData.date == today && dayData.isCurrentMonth
|
||||
val dayNum = if (dayData.isCurrentMonth) dayData.date.day else 0
|
||||
val textColor: Color = when {
|
||||
!dayData.isCurrentMonth -> colors.otherMonth
|
||||
isToday -> colors.todayText
|
||||
val colorType = when {
|
||||
!dayData.isCurrentMonth -> 2
|
||||
isToday -> 1
|
||||
else -> 0
|
||||
}
|
||||
val textColor: Color = when (colorType) {
|
||||
2 -> colors.otherMonth
|
||||
1 -> colors.todayText
|
||||
else -> colors.day
|
||||
}
|
||||
|
||||
@ -275,7 +282,7 @@ private fun MiniMonth(
|
||||
}
|
||||
|
||||
if (dayNum > 0) {
|
||||
dl[dayNum to textColor]?.let { layoutResult ->
|
||||
dl[dayNum * 3 + colorType]?.let { layoutResult ->
|
||||
drawText(
|
||||
textLayoutResult = layoutResult,
|
||||
topLeft = Offset(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user