perf: 优化 Compose 渲染性能
- DayCell: 5 个 animate*AsState 合并为 updateTransition,减少 80% 动画状态对象 - CalendarMonthPage: produceState/remember key 从 List/Map 改为 year/month 原始值 - CalendarPager: derivedStateOf 提取 currentPageOffsetFraction,beyondViewportPageCount 设为 1 - CalendarViewModel: 三层嵌套 combine 扁平化为 6 参数 combine Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e934d33cfa
commit
28f777abe4
@ -3,6 +3,7 @@ package plus.rua.project
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@ -51,6 +52,37 @@ data class CalendarUiState(
|
||||
val showLegalHoliday: Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
* 将六个 [Flow] 合并为一个 [Flow],使用 [transform] 处理最新值。
|
||||
*
|
||||
* kotlinx-coroutines 1.11 仅内置到 5 参数的 [combine] 重载,
|
||||
* 此扩展用于扁平化 6 个 StateFlow 的合并,避免多层嵌套产生的中间流。
|
||||
*/
|
||||
private inline fun <T1, T2, T3, T4, T5, T6, R> combine(
|
||||
flow: Flow<T1>,
|
||||
flow2: Flow<T2>,
|
||||
flow3: Flow<T3>,
|
||||
flow4: Flow<T4>,
|
||||
flow5: Flow<T5>,
|
||||
flow6: Flow<T6>,
|
||||
crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
|
||||
): Flow<R> = combine(
|
||||
combine(flow, flow2, flow3, flow4, flow5) { t1, t2, t3, t4, t5 ->
|
||||
Quintuple(t1, t2, t3, t4, t5)
|
||||
},
|
||||
flow6
|
||||
) { quintuple, t6 ->
|
||||
transform(quintuple.first, quintuple.second, quintuple.third, quintuple.fourth, quintuple.fifth, t6)
|
||||
}
|
||||
|
||||
private data class Quintuple<T1, T2, T3, T4, T5>(
|
||||
val first: T1,
|
||||
val second: T2,
|
||||
val third: T3,
|
||||
val fourth: T4,
|
||||
val fifth: T5
|
||||
)
|
||||
|
||||
/**
|
||||
* 日历状态管理,持有选中日期、折叠状态和 ISO 周号计算逻辑。
|
||||
*
|
||||
@ -137,17 +169,20 @@ class CalendarViewModel(
|
||||
|
||||
/** 聚合 UI 状态,减少 Compose 层分散订阅导致的重组。 */
|
||||
val uiState: StateFlow<CalendarUiState> = combine(
|
||||
combine(_selectedDate, _isCollapsed, ::Pair),
|
||||
combine(_isYearView, _yearViewYear, ::Pair),
|
||||
combine(_collapseProgress, _showLegalHoliday, ::Pair)
|
||||
) { dateCollapsed, yearViewYearPair, progressHoliday ->
|
||||
_selectedDate,
|
||||
_isCollapsed,
|
||||
_isYearView,
|
||||
_yearViewYear,
|
||||
_collapseProgress,
|
||||
_showLegalHoliday
|
||||
) { selectedDate, isCollapsed, isYearView, yearViewYear, collapseProgress, showLegalHoliday ->
|
||||
CalendarUiState(
|
||||
selectedDate = dateCollapsed.first,
|
||||
isCollapsed = dateCollapsed.second,
|
||||
isYearView = yearViewYearPair.first,
|
||||
yearViewYear = yearViewYearPair.second,
|
||||
collapseProgress = progressHoliday.first,
|
||||
showLegalHoliday = progressHoliday.second
|
||||
selectedDate = selectedDate,
|
||||
isCollapsed = isCollapsed,
|
||||
isYearView = isYearView,
|
||||
yearViewYear = yearViewYear,
|
||||
collapseProgress = collapseProgress,
|
||||
showLegalHoliday = showLegalHoliday
|
||||
)
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
@ -245,7 +280,7 @@ class CalendarViewModel(
|
||||
}
|
||||
|
||||
/**
|
||||
* 折叠状态下下拉恢复,delta 为负值(向下拖)推动 progress 向 0。
|
||||
* 折叠状态下拉恢复,delta 为负值(向下拖)推动 progress 向 0。
|
||||
*
|
||||
* @param delta 拖拽增量,已归一化到 [0,1] 区间
|
||||
*/
|
||||
|
||||
@ -72,7 +72,8 @@ fun CalendarMonthPage(
|
||||
|
||||
val holidayBadges by produceState(
|
||||
initialValue = emptyMap<LocalDate, String?>(),
|
||||
key1 = days
|
||||
key1 = year,
|
||||
key2 = month
|
||||
) {
|
||||
val map = mutableMapOf<LocalDate, String?>()
|
||||
for (dayData in days) {
|
||||
@ -82,7 +83,7 @@ fun CalendarMonthPage(
|
||||
value = map
|
||||
}
|
||||
|
||||
val holidayEdges = remember(holidayBadges, days) {
|
||||
val holidayEdges = remember(holidayBadges, year, month) {
|
||||
val map = mutableMapOf<LocalDate, HolidayEdgeInfo>()
|
||||
for (dayData in days) {
|
||||
val date = dayData.date
|
||||
@ -99,7 +100,7 @@ fun CalendarMonthPage(
|
||||
}
|
||||
|
||||
val weeks = remember(days) { days.chunked(7) }
|
||||
val anchorIndex = remember(weeks, selectedDate) {
|
||||
val anchorIndex = remember(year, month, selectedDate) {
|
||||
weeks.indexOfFirst { week -> week.any { it.date == selectedDate } }
|
||||
}
|
||||
val totalHeightDp = if (rowHeightPx > 0) {
|
||||
|
||||
@ -5,6 +5,8 @@ import androidx.compose.foundation.pager.PagerDefaults
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
@ -66,14 +68,21 @@ fun CalendarPager(
|
||||
}
|
||||
}
|
||||
|
||||
val currentPageOffsetFraction by remember {
|
||||
derivedStateOf { pagerState.currentPageOffsetFraction }
|
||||
}
|
||||
val currentPage by remember {
|
||||
derivedStateOf { pagerState.currentPage }
|
||||
}
|
||||
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
beyondViewportPageCount = 0,
|
||||
beyondViewportPageCount = 1,
|
||||
flingBehavior = PagerDefaults.flingBehavior(state = pagerState),
|
||||
modifier = modifier
|
||||
) { page ->
|
||||
val pageOffset = abs(pagerState.currentPageOffsetFraction)
|
||||
val isCurrentPage = page == pagerState.currentPage
|
||||
val pageOffset = abs(currentPageOffsetFraction)
|
||||
val isCurrentPage = page == currentPage
|
||||
val alpha = if (isCurrentPage) {
|
||||
1f - pageOffset
|
||||
} else {
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
package plus.rua.project.ui
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.animateColor
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@ -91,52 +92,61 @@ fun DayCell(
|
||||
else -> DayCellState.NORMAL
|
||||
}
|
||||
|
||||
val revealProgress by animateFloatAsState(
|
||||
targetValue = when (currentState) {
|
||||
val transition = updateTransition(targetState = currentState, label = "DayCell")
|
||||
|
||||
val revealProgress by transition.animateFloat(
|
||||
transitionSpec = { tween(150, easing = FastOutSlowInEasing) },
|
||||
label = "revealProgress"
|
||||
) { state ->
|
||||
when (state) {
|
||||
DayCellState.SELECTED, DayCellState.SELECTED_TODAY -> 1f
|
||||
else -> 0f
|
||||
},
|
||||
animationSpec = tween(150, easing = FastOutSlowInEasing),
|
||||
label = "revealProgress"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val contentColor by animateColorAsState(
|
||||
targetValue = when (currentState) {
|
||||
val contentColor by transition.animateColor(
|
||||
transitionSpec = { tween(150, easing = FastOutSlowInEasing) },
|
||||
label = "contentColor"
|
||||
) { state ->
|
||||
when (state) {
|
||||
DayCellState.SELECTED_TODAY -> MaterialTheme.colorScheme.onPrimaryContainer
|
||||
DayCellState.SELECTED -> MaterialTheme.colorScheme.primary
|
||||
DayCellState.TODAY -> MaterialTheme.colorScheme.primary
|
||||
DayCellState.OTHER_MONTH -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
|
||||
DayCellState.NORMAL -> MaterialTheme.colorScheme.onSurface
|
||||
},
|
||||
animationSpec = tween(150, easing = FastOutSlowInEasing),
|
||||
label = "contentColor"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 选中今天:实心填充 primaryContainer;其他状态不填充。
|
||||
val selectedFillColor by animateColorAsState(
|
||||
targetValue = when (currentState) {
|
||||
val selectedFillColor by transition.animateColor(
|
||||
transitionSpec = { tween(150, easing = FastOutSlowInEasing) },
|
||||
label = "selectedFillColor"
|
||||
) { state ->
|
||||
when (state) {
|
||||
DayCellState.SELECTED_TODAY -> MaterialTheme.colorScheme.primaryContainer
|
||||
else -> Color.Transparent
|
||||
},
|
||||
animationSpec = tween(150, easing = FastOutSlowInEasing),
|
||||
label = "selectedFillColor"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 选中非今天:绘制描边圆,避免遮挡右上角角标。
|
||||
val selectedOutlineAlpha by animateFloatAsState(
|
||||
targetValue = when (currentState) {
|
||||
val selectedOutlineAlpha by transition.animateFloat(
|
||||
transitionSpec = { tween(150, easing = FastOutSlowInEasing) },
|
||||
label = "selectedOutlineAlpha"
|
||||
) { state ->
|
||||
when (state) {
|
||||
DayCellState.SELECTED -> 1f
|
||||
else -> 0f
|
||||
},
|
||||
animationSpec = tween(150, easing = FastOutSlowInEasing),
|
||||
label = "selectedOutlineAlpha"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val selectedOutlineColor = MaterialTheme.colorScheme.primary
|
||||
|
||||
val lunarColor by animateColorAsState(
|
||||
targetValue = if (isAnnotationHighlight) {
|
||||
when (currentState) {
|
||||
val lunarColor by transition.animateColor(
|
||||
transitionSpec = { tween(150, easing = FastOutSlowInEasing) },
|
||||
label = "lunarColor"
|
||||
) { state ->
|
||||
if (isAnnotationHighlight) {
|
||||
when (state) {
|
||||
DayCellState.SELECTED_TODAY -> MaterialTheme.colorScheme.onPrimaryContainer.copy(
|
||||
alpha = 0.85f
|
||||
)
|
||||
@ -146,7 +156,7 @@ fun DayCell(
|
||||
DayCellState.NORMAL -> MaterialTheme.colorScheme.error.copy(alpha = 0.7f)
|
||||
}
|
||||
} else {
|
||||
when (currentState) {
|
||||
when (state) {
|
||||
DayCellState.SELECTED_TODAY -> MaterialTheme.colorScheme.onPrimaryContainer.copy(
|
||||
alpha = 0.7f
|
||||
)
|
||||
@ -155,10 +165,8 @@ fun DayCell(
|
||||
DayCellState.OTHER_MONTH -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.26f)
|
||||
DayCellState.NORMAL -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f)
|
||||
}
|
||||
},
|
||||
animationSpec = tween(150, easing = FastOutSlowInEasing),
|
||||
label = "lunarColor"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val holidayBgColor = when (holidayBadge) {
|
||||
"休" -> MaterialTheme.colorScheme.error.copy(alpha = 0.10f)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user