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