perf: 聚合 CalendarUiState 减少 Compose 重组
- ViewModel 新增 CalendarUiState 数据类,通过 combine + stateIn 将 6 个独立 StateFlow 合并为单一 uiState 流 - CalendarMonthView 从 5 个分散 collectAsState 改为单个 uiState.collectAsState,减少重组次数 - CalendarPagerArea 改为显式传参,解耦对 ViewModel 的直接引用 - CalendarMonthPage 中 DayCell 外层包裹 key(dayData.date), 稳定跨帧重组 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
baedf878b4
commit
7f9db1dc1d
@ -4,8 +4,11 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.datetime.DatePeriod
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.TimeZone
|
||||
@ -34,6 +37,21 @@ data class CalendarDay(
|
||||
val isSelected: Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
* 日历 UI 状态聚合,用于减少 Compose 重组次数。
|
||||
*
|
||||
* 将多个独立的 StateFlow 合并为单一状态流,
|
||||
* 避免 `collectAsState()` 分散订阅导致的重复重组。
|
||||
*/
|
||||
data class CalendarUiState(
|
||||
val selectedDate: LocalDate,
|
||||
val isCollapsed: Boolean,
|
||||
val isYearView: Boolean,
|
||||
val yearViewYear: Int,
|
||||
val collapseProgress: Float,
|
||||
val showLegalHoliday: Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
* 日历状态管理,持有选中日期、折叠状态和 ISO 周号计算逻辑。
|
||||
*
|
||||
@ -118,6 +136,26 @@ class CalendarViewModel(
|
||||
private val _showLegalHoliday = MutableStateFlow(false)
|
||||
val showLegalHoliday: StateFlow<Boolean> = _showLegalHoliday.asStateFlow()
|
||||
|
||||
/** 聚合 UI 状态,减少 Compose 层分散订阅导致的重组。 */
|
||||
val uiState: StateFlow<CalendarUiState> = combine(
|
||||
combine(_selectedDate, _isCollapsed, ::Pair),
|
||||
combine(_isYearView, _yearViewYear, ::Pair),
|
||||
combine(_collapseProgress, _showLegalHoliday, ::Pair)
|
||||
) { dateCollapsed, yearViewYearPair, progressHoliday ->
|
||||
CalendarUiState(
|
||||
selectedDate = dateCollapsed.first,
|
||||
isCollapsed = dateCollapsed.second,
|
||||
isYearView = yearViewYearPair.first,
|
||||
yearViewYear = yearViewYearPair.second,
|
||||
collapseProgress = progressHoliday.first,
|
||||
showLegalHoliday = progressHoliday.second
|
||||
)
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5000),
|
||||
CalendarUiState(today, false, false, today.year, 0f, false)
|
||||
)
|
||||
|
||||
/**
|
||||
* 选中指定日期。
|
||||
*
|
||||
|
||||
@ -189,17 +189,19 @@ private fun WeekRow(
|
||||
.padding(vertical = ROW_PADDING_DP.dp)
|
||||
) {
|
||||
week.forEach { dayData ->
|
||||
DayCell(
|
||||
date = dayData.date,
|
||||
isCurrentMonth = dayData.isCurrentMonth,
|
||||
isSelected = dayData.date == selectedDate,
|
||||
isToday = dayData.date == today,
|
||||
shiftKind = shiftKindAt(dayData.date),
|
||||
showLegalHoliday = showLegalHoliday,
|
||||
onClick = { onDateClick(dayData.date) },
|
||||
modifier = Modifier.weight(1f),
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
key(dayData.date) {
|
||||
DayCell(
|
||||
date = dayData.date,
|
||||
isCurrentMonth = dayData.isCurrentMonth,
|
||||
isSelected = dayData.date == selectedDate,
|
||||
isToday = dayData.date == today,
|
||||
shiftKind = shiftKindAt(dayData.date),
|
||||
showLegalHoliday = showLegalHoliday,
|
||||
onClick = { onDateClick(dayData.date) },
|
||||
modifier = Modifier.weight(1f),
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,6 +72,7 @@ import kotlinx.datetime.number
|
||||
import kotlinx.datetime.plus
|
||||
import kotlinx.datetime.todayIn
|
||||
import plus.rua.project.CalendarViewModel
|
||||
import plus.rua.project.ShiftKind
|
||||
import plus.rua.project.composeTraceBeginSection
|
||||
import plus.rua.project.composeTraceEndSection
|
||||
import kotlin.math.abs
|
||||
@ -95,15 +96,16 @@ fun CalendarMonthView(
|
||||
val viewModel = viewModel<CalendarViewModel>()
|
||||
val today = remember { Clock.System.todayIn(TimeZone.currentSystemDefault()) }
|
||||
|
||||
val selectedDate by viewModel.selectedDate.collectAsState()
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
val selectedDate = uiState.selectedDate
|
||||
val currentYear = selectedDate.year
|
||||
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-datetime 尚未提供新接口
|
||||
val currentMonth = selectedDate.month.number
|
||||
val isCollapsed by viewModel.isCollapsed.collectAsState()
|
||||
val isYearView by viewModel.isYearView.collectAsState()
|
||||
val yearViewYear by viewModel.yearViewYear.collectAsState()
|
||||
val collapseProgress by viewModel.collapseProgress.collectAsState()
|
||||
val showLegalHoliday by viewModel.showLegalHoliday.collectAsState()
|
||||
val isCollapsed = uiState.isCollapsed
|
||||
val isYearView = uiState.isYearView
|
||||
val yearViewYear = uiState.yearViewYear
|
||||
val collapseProgress = uiState.collapseProgress
|
||||
val showLegalHoliday = uiState.showLegalHoliday
|
||||
|
||||
val density = LocalDensity.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
@ -210,10 +212,21 @@ fun CalendarMonthView(
|
||||
)
|
||||
with(sharedScope) {
|
||||
CalendarPagerArea(
|
||||
viewModel = viewModel,
|
||||
selectedDate = selectedDate,
|
||||
today = today,
|
||||
isCollapsed = isCollapsed,
|
||||
collapseProgress = collapseProgress,
|
||||
showLegalHoliday = showLegalHoliday,
|
||||
rowHeightPx = rowHeightPx,
|
||||
screenWidthPx = screenWidthPx,
|
||||
onDateClick = { date -> viewModel.selectDate(date) },
|
||||
onMonthChanged = { year, month ->
|
||||
@Suppress("DEPRECATION")
|
||||
val date = if (year == today.year && today.month.number == month) today
|
||||
else LocalDate(year, month, 1)
|
||||
viewModel.selectDate(date)
|
||||
},
|
||||
shiftKindAt = { date -> viewModel.shiftKindAt(date) },
|
||||
onRowHeightMeasured = { h ->
|
||||
if (h > 0) rowHeightPx = h
|
||||
},
|
||||
@ -408,19 +421,21 @@ private fun MenuIcon(color: Color, modifier: Modifier = Modifier) {
|
||||
|
||||
@Composable
|
||||
private fun CalendarPagerArea(
|
||||
viewModel: CalendarViewModel,
|
||||
selectedDate: LocalDate,
|
||||
today: LocalDate,
|
||||
isCollapsed: Boolean,
|
||||
collapseProgress: Float,
|
||||
showLegalHoliday: Boolean,
|
||||
rowHeightPx: Int,
|
||||
screenWidthPx: Int,
|
||||
onDateClick: (LocalDate) -> Unit,
|
||||
onMonthChanged: (year: Int, month: Int) -> Unit,
|
||||
shiftKindAt: (LocalDate) -> ShiftKind?,
|
||||
onRowHeightMeasured: ((Int) -> Unit)?,
|
||||
pagerState: PagerState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val density = LocalDensity.current
|
||||
val collapseProgress by viewModel.collapseProgress.collectAsState()
|
||||
val selectedDate by viewModel.selectedDate.collectAsState()
|
||||
val isCollapsed by viewModel.isCollapsed.collectAsState()
|
||||
val showLegalHoliday by viewModel.showLegalHoliday.collectAsState()
|
||||
|
||||
val interpolatedWeeks by remember {
|
||||
derivedStateOf {
|
||||
@ -469,7 +484,7 @@ private fun CalendarPagerArea(
|
||||
WeekPager(
|
||||
selectedDate = selectedDate,
|
||||
today = today,
|
||||
onDateClick = { date -> viewModel.selectDate(date) },
|
||||
onDateClick = onDateClick,
|
||||
onWeekChanged = { weekMonday ->
|
||||
val weekSunday = weekMonday.plus(DatePeriod(days = 6))
|
||||
val date = when {
|
||||
@ -485,9 +500,9 @@ private fun CalendarPagerArea(
|
||||
|
||||
else -> weekMonday
|
||||
}
|
||||
viewModel.selectDate(date)
|
||||
onDateClick(date)
|
||||
},
|
||||
shiftKindAt = { date -> viewModel.shiftKindAt(date) },
|
||||
shiftKindAt = shiftKindAt,
|
||||
showLegalHoliday = showLegalHoliday,
|
||||
modifier = pagerModifier
|
||||
)
|
||||
@ -495,18 +510,12 @@ private fun CalendarPagerArea(
|
||||
CalendarPager(
|
||||
selectedDate = selectedDate,
|
||||
today = today,
|
||||
onDateClick = { date -> viewModel.selectDate(date) },
|
||||
onMonthChanged = { year, month ->
|
||||
@Suppress("DEPRECATION") // monthNumber 无替代 API
|
||||
val date =
|
||||
if (year == today.year && today.month.number == month) today
|
||||
else LocalDate(year, month, 1)
|
||||
viewModel.selectDate(date)
|
||||
},
|
||||
onDateClick = onDateClick,
|
||||
onMonthChanged = onMonthChanged,
|
||||
collapseProgress = collapseProgress,
|
||||
rowHeightPx = rowHeightPx,
|
||||
effectiveWeeks = effectiveWeeks,
|
||||
shiftKindAt = { date -> viewModel.shiftKindAt(date) },
|
||||
shiftKindAt = shiftKindAt,
|
||||
showLegalHoliday = showLegalHoliday,
|
||||
onRowHeightMeasured = onRowHeightMeasured,
|
||||
pagerState = pagerState,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user