perf: 添加组件级性能监控日志

- CalendarViewModel: toggle/select 方法耗时打点
- CalendarMonthPage/CalendarPager/WeekPager/YearGridView: 重组次数与耗时日志
- CalendarMonthView: 使用 graphicsLayer 驱动 BottomCard 滑入动画

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xfy 2026-05-20 18:31:48 +08:00
parent 27ba4f9f54
commit f6b5e62284
6 changed files with 181 additions and 34 deletions

View File

@ -23,6 +23,28 @@ import plus.rua.project.ui.COLLAPSE_THRESHOLD
import plus.rua.project.ui.FLING_VELOCITY_THRESHOLD_DP import plus.rua.project.ui.FLING_VELOCITY_THRESHOLD_DP
import kotlin.time.Clock import kotlin.time.Clock
// region 性能监控工具
private fun perfLog(tag: String, msg: String) {
println("[PERF:${Thread.currentThread().name}] $tag | $msg")
}
private inline fun <T> perfMeasure(tag: String, block: () -> T): T {
val start = System.nanoTime()
val result = block()
val elapsedMs = (System.nanoTime() - start) / 1_000_000.0
perfLog(tag, "elapsed=${"%.3f".format(elapsedMs)}ms")
return result
}
private inline fun <T> perfMeasureResult(tag: String, block: () -> T): T {
val start = System.nanoTime()
val result = block()
val elapsedMs = (System.nanoTime() - start) / 1_000_000.0
perfLog(tag, "result=$result elapsed=${"%.3f".format(elapsedMs)}ms")
return result
}
// endregion
/** /**
* 日历日期数据用于网格单元格渲染 * 日历日期数据用于网格单元格渲染
* *
@ -113,36 +135,53 @@ class CalendarViewModel(
* 当前视图被直接移除动画只作用在目标视图的 scale/alpha * 当前视图被直接移除动画只作用在目标视图的 scale/alpha
*/ */
fun toggleYearView() { fun toggleYearView() {
val direction = if (isYearView) "Year→Month" else "Month→Year"
perfLog("VM.toggleYearView", "START direction=$direction isCollapsed=$isCollapsed yearViewYear=$yearViewYear selectedDate=$selectedDate")
val toggleStart = System.nanoTime()
yearViewJob?.cancel() yearViewJob?.cancel()
yearViewJob = coroutineScope.launch { yearViewJob = coroutineScope.launch {
if (isYearView) { if (isYearView) {
// 年 → 月:先启动动画(年视图开始淡出),等一帧后翻转 isYearView月视图开始组合
composeTraceBeginSection("YearView→MonthView") composeTraceBeginSection("YearView→MonthView")
val snapStart = System.nanoTime()
_yearViewAnimatable.snapTo(1f) _yearViewAnimatable.snapTo(1f)
perfLog("VM.toggleYearView", "snapTo(1f) elapsed=${(System.nanoTime() - snapStart) / 1_000_000.0}ms")
val animJob = launch { val animJob = launch {
val animStart = System.nanoTime()
_yearViewAnimatable.animateTo( _yearViewAnimatable.animateTo(
0f, tween(400, easing = FastOutSlowInEasing) 0f, tween(400, easing = FastOutSlowInEasing)
) )
perfLog("VM.toggleYearView", "animateTo(0f) elapsed=${(System.nanoTime() - animStart) / 1_000_000.0}ms")
} }
val frameStart = System.nanoTime()
withFrameNanos { } withFrameNanos { }
perfLog("VM.toggleYearView", "withFrameNanos elapsed=${(System.nanoTime() - frameStart) / 1_000_000.0}ms")
val flipStart = System.nanoTime()
isYearView = false isYearView = false
animJob.join() perfLog("VM.toggleYearView", "isYearView=false flip elapsed=${(System.nanoTime() - flipStart) / 1_000_000.0}ms")
composeTraceEndSection() composeTraceEndSection()
} else { } else {
// 月 → 年:直接切换,折叠态下周视图的 sharedElement 缩小到 MiniMonth 更自然
composeTraceBeginSection("MonthView→YearView") composeTraceBeginSection("MonthView→YearView")
val snapStart = System.nanoTime()
yearViewYear = selectedDate.year yearViewYear = selectedDate.year
_yearViewAnimatable.snapTo(0f) _yearViewAnimatable.snapTo(0f)
perfLog("VM.toggleYearView", "snapTo(0f) + set yearViewYear=$yearViewYear elapsed=${(System.nanoTime() - snapStart) / 1_000_000.0}ms")
val animJob = launch { val animJob = launch {
val animStart = System.nanoTime()
_yearViewAnimatable.animateTo( _yearViewAnimatable.animateTo(
1f, tween(400, easing = FastOutSlowInEasing) 1f, tween(400, easing = FastOutSlowInEasing)
) )
perfLog("VM.toggleYearView", "animateTo(1f) elapsed=${(System.nanoTime() - animStart) / 1_000_000.0}ms")
} }
val frameStart = System.nanoTime()
withFrameNanos { } withFrameNanos { }
perfLog("VM.toggleYearView", "withFrameNanos elapsed=${(System.nanoTime() - frameStart) / 1_000_000.0}ms")
val flipStart = System.nanoTime()
isYearView = true isYearView = true
animJob.join() perfLog("VM.toggleYearView", "isYearView=true flip elapsed=${(System.nanoTime() - flipStart) / 1_000_000.0}ms")
composeTraceEndSection() composeTraceEndSection()
} }
val totalMs = (System.nanoTime() - toggleStart) / 1_000_000.0
perfLog("VM.toggleYearView", "END direction=$direction total=${"%.3f".format(totalMs)}ms")
} }
} }
@ -151,18 +190,27 @@ class CalendarViewModel(
*/ */
@Suppress("DEPRECATION") // monthNumber 无替代 API @Suppress("DEPRECATION") // monthNumber 无替代 API
fun selectMonthFromYearView(month: Int) { fun selectMonthFromYearView(month: Int) {
val totalStart = System.nanoTime()
perfLog("VM.selectMonthFromYearView", "START month=$month yearViewYear=$yearViewYear")
composeTraceBeginSection("YearView:SelectMonth") composeTraceBeginSection("YearView:SelectMonth")
val date = if (yearViewYear == today.year && today.month.number == month) today val date = if (yearViewYear == today.year && today.month.number == month) today
else LocalDate(yearViewYear, month, 1) else LocalDate(yearViewYear, month, 1)
val dateSetStart = System.nanoTime()
selectedDate = date selectedDate = date
perfLog("VM.selectMonthFromYearView", "selectedDate=$selectedDate elapsed=${(System.nanoTime() - dateSetStart) / 1_000_000.0}ms")
val flipStart = System.nanoTime()
isYearView = false isYearView = false
perfLog("VM.selectMonthFromYearView", "isYearView=false elapsed=${(System.nanoTime() - flipStart) / 1_000_000.0}ms")
yearViewJob?.cancel() yearViewJob?.cancel()
yearViewJob = coroutineScope.launch { yearViewJob = coroutineScope.launch {
withFrameNanos { } val animStart = System.nanoTime()
_yearViewAnimatable.animateTo( _yearViewAnimatable.animateTo(
0f, tween(400, easing = FastOutSlowInEasing) 0f, tween(400, easing = FastOutSlowInEasing)
) )
perfLog("VM.selectMonthFromYearView", "animateTo(0f) elapsed=${(System.nanoTime() - animStart) / 1_000_000.0}ms")
composeTraceEndSection() composeTraceEndSection()
val totalMs = (System.nanoTime() - totalStart) / 1_000_000.0
perfLog("VM.selectMonthFromYearView", "END total=${"%.3f".format(totalMs)}ms")
} }
} }
@ -293,6 +341,7 @@ class CalendarViewModel(
*/ */
@Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口 @Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口
fun getMonthDays(year: Int, month: Int): List<CalendarDay> { fun getMonthDays(year: Int, month: Int): List<CalendarDay> {
val start = System.nanoTime()
composeTraceBeginSection("getMonthDays:$year-$month") composeTraceBeginSection("getMonthDays:$year-$month")
val firstOfMonth = LocalDate(year, month, 1) val firstOfMonth = LocalDate(year, month, 1)
val dayOfWeekOffset = firstOfMonth.dayOfWeek.ordinal val dayOfWeekOffset = firstOfMonth.dayOfWeek.ordinal
@ -313,6 +362,8 @@ class CalendarViewModel(
) )
} }
composeTraceEndSection() composeTraceEndSection()
val elapsedMs = (System.nanoTime() - start) / 1_000_000.0
perfLog("VM.getMonthDays", "$year-$month rows=$rows totalDays=$totalDays elapsed=${"%.3f".format(elapsedMs)}ms")
return result return result
} }
} }

View File

@ -8,7 +8,10 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
@ -23,6 +26,12 @@ import kotlinx.datetime.number
import kotlinx.datetime.plus import kotlinx.datetime.plus
import plus.rua.project.ShiftKind import plus.rua.project.ShiftKind
// region 性能监控工具
private fun monthPagePerfLog(tag: String, msg: String) {
println("[MONTH-PAGE-PERF:${Thread.currentThread().name}] $tag | $msg")
}
// endregion
/** /**
* 月度日历网格页面支持两阶段折叠动画 * 月度日历网格页面支持两阶段折叠动画
* *
@ -57,8 +66,15 @@ fun CalendarMonthPage(
onRowHeightMeasured: ((Int) -> Unit)? = null, onRowHeightMeasured: ((Int) -> Unit)? = null,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val pageStart = System.nanoTime()
var recomposeCount by remember { mutableIntStateOf(0) }
recomposeCount++
val days = remember(year, month) { val days = remember(year, month) {
generateMonthDays(year, month) val genStart = System.nanoTime()
val result = generateMonthDays(year, month)
val genMs = (System.nanoTime() - genStart) / 1_000_000.0
monthPagePerfLog("CalendarMonthPage", "generateMonthDays year=$year month=$month elapsed=${"%.3f".format(genMs)}ms")
result
} }
val density = LocalDensity.current val density = LocalDensity.current
@ -66,6 +82,8 @@ fun CalendarMonthPage(
val anchorIndex = remember(weeks, selectedDate) { val anchorIndex = remember(weeks, selectedDate) {
weeks.indexOfFirst { week -> week.any { it.date == selectedDate } } weeks.indexOfFirst { week -> week.any { it.date == selectedDate } }
} }
val weekCount = weeks.size
monthPagePerfLog("CalendarMonthPage", "composition #$recomposeCount year=$year month=$month weeks=$weekCount collapseProgress=$collapseProgress anchorRow=$anchorIndex")
val hasAnchor = anchorIndex >= 0 val hasAnchor = anchorIndex >= 0
val h = rowHeightPx.toFloat() val h = rowHeightPx.toFloat()
@ -164,6 +182,8 @@ fun CalendarMonthPage(
} }
} }
} }
val totalMs = (System.nanoTime() - pageStart) / 1_000_000.0
monthPagePerfLog("CalendarMonthPage", "END year=$year month=$month elapsed=${"%.3f".format(totalMs)}ms")
} }
private data class DayData( private data class DayData(

View File

@ -77,6 +77,12 @@ import plus.rua.project.composeTraceEndSection
import kotlin.math.abs import kotlin.math.abs
import kotlin.time.Clock import kotlin.time.Clock
// region 性能监控工具
private fun uiPerfLog(tag: String, msg: String) {
println("[UI-PERF:${Thread.currentThread().name}] $tag | $msg")
}
// endregion
/** /**
* 日历主界面包含月/周视图切换折叠动画和年视图共享元素转场 * 日历主界面包含月/周视图切换折叠动画和年视图共享元素转场
* *
@ -100,15 +106,6 @@ fun CalendarMonthView(
val currentMonth by remember { derivedStateOf { viewModel.selectedDate.month.number } } val currentMonth by remember { derivedStateOf { viewModel.selectedDate.month.number } }
val density = LocalDensity.current val density = LocalDensity.current
// BottomCard 滑入动画1f=完全隐藏在下方0f=完全显示
val bottomCardSlideProgress by animateFloatAsState(
targetValue = if (viewModel.isYearView) 1f else 0f,
animationSpec = tween(350, delayMillis = 100, easing = FastOutSlowInEasing),
label = "bottomCardSlide"
)
LocalDensity.current
var rowHeightPx by remember { mutableIntStateOf(0) } var rowHeightPx by remember { mutableIntStateOf(0) }
var screenWidthPx by remember { mutableIntStateOf(0) } var screenWidthPx by remember { mutableIntStateOf(0) }
var isMenuExpanded by remember { mutableStateOf(false) } var isMenuExpanded by remember { mutableStateOf(false) }
@ -117,6 +114,10 @@ fun CalendarMonthView(
isMenuExpanded = false isMenuExpanded = false
} }
// 重组计数器
var recomposeCount by remember { mutableIntStateOf(0) }
recomposeCount++
val pagerState = rememberPagerState(initialPage = START_PAGE, pageCount = { Int.MAX_VALUE }) val pagerState = rememberPagerState(initialPage = START_PAGE, pageCount = { Int.MAX_VALUE })
// 年视图分页器 // 年视图分页器
@ -157,21 +158,23 @@ fun CalendarMonthView(
} }
} }
SharedTransitionLayout { uiPerfLog("CalendarMonthView", "composition #$recomposeCount isYearView=${viewModel.isYearView}")
val sharedScope = this Box(
Box( modifier = modifier
modifier = modifier .fillMaxSize()
.fillMaxSize() .background(MaterialTheme.colorScheme.background)
.background(MaterialTheme.colorScheme.background) .statusBarsPadding()
.statusBarsPadding() .onSizeChanged { size ->
.onSizeChanged { size -> screenWidthPx = size.width
screenWidthPx = size.width }
} ) {
) { SharedTransitionLayout {
val sharedScope = this
AnimatedContent( AnimatedContent(
targetState = viewModel.isYearView, targetState = viewModel.isYearView,
label = "month_year_transition", label = "month_year_transition",
transitionSpec = { transitionSpec = {
uiPerfLog("CalendarMonthView", "AnimatedContent transitionSpec target=$targetState initial=$initialState")
val enter = fadeIn(tween(300, easing = FastOutSlowInEasing)) + val enter = fadeIn(tween(300, easing = FastOutSlowInEasing)) +
slideInVertically(tween(300, easing = FastOutSlowInEasing)) { it / 6 } slideInVertically(tween(300, easing = FastOutSlowInEasing)) { it / 6 }
val exit = fadeOut(tween(200, easing = FastOutSlowInEasing)) + val exit = fadeOut(tween(200, easing = FastOutSlowInEasing)) +
@ -180,6 +183,8 @@ fun CalendarMonthView(
}, },
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { isYearView -> ) { isYearView ->
val contentStart = System.nanoTime()
uiPerfLog("CalendarMonthView", "AnimatedContent content lambda START isYearView=$isYearView")
if (!isYearView) { if (!isYearView) {
composeTraceBeginSection("MonthView:Compose") composeTraceBeginSection("MonthView:Compose")
val layoutReady = rowHeightPx > 0 val layoutReady = rowHeightPx > 0
@ -232,16 +237,18 @@ fun CalendarMonthView(
viewModel = viewModel, viewModel = viewModel,
today = today, today = today,
rowHeightPx = rowHeightPx, rowHeightPx = rowHeightPx,
modifier = Modifier isYearView = viewModel.isYearView,
.fillMaxWidth() modifier = Modifier.fillMaxWidth()
.offset(y = with(density) { (bottomCardSlideProgress * 200).dp })
.alpha(1f - bottomCardSlideProgress)
) )
} }
} }
val monthViewMs = (System.nanoTime() - contentStart) / 1_000_000.0
uiPerfLog("CalendarMonthView", "MonthView content lambda END elapsed=${"%.3f".format(monthViewMs)}ms")
composeTraceEndSection() composeTraceEndSection()
} else { } else {
composeTraceBeginSection("YearView:Compose") composeTraceBeginSection("YearView:Compose")
val yearViewStart = System.nanoTime()
uiPerfLog("CalendarMonthView", "YearView:Compose START")
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@ -266,6 +273,7 @@ fun CalendarMonthView(
.fillMaxWidth() .fillMaxWidth()
.weight(1f) .weight(1f)
) { page -> ) { page ->
val pageStart = System.nanoTime()
val pageOffset = abs(yearPagerState.currentPageOffsetFraction) val pageOffset = abs(yearPagerState.currentPageOffsetFraction)
val isCurrentPage = page == yearPagerState.currentPage val isCurrentPage = page == yearPagerState.currentPage
val crossFadeAlpha = if (isCurrentPage) { val crossFadeAlpha = if (isCurrentPage) {
@ -274,6 +282,7 @@ fun CalendarMonthView(
pageOffset pageOffset
} }
val pageYear = viewModel.selectedDate.year + (page - START_PAGE) val pageYear = viewModel.selectedDate.year + (page - START_PAGE)
uiPerfLog("CalendarMonthView", "YearPager page=$page pageYear=$pageYear crossFadeAlpha=${"%.2f".format(crossFadeAlpha)} isCurrentPage=$isCurrentPage offset=${"%.3f".format(pageOffset)}")
YearGridView( YearGridView(
year = pageYear, year = pageYear,
selectedMonth = if (pageYear == currentYear) currentMonth else 0, selectedMonth = if (pageYear == currentYear) currentMonth else 0,
@ -295,12 +304,17 @@ fun CalendarMonthView(
) )
} }
} }
val yearViewMs = (System.nanoTime() - yearViewStart) / 1_000_000.0
uiPerfLog("CalendarMonthView", "YearView:Compose END elapsed=${"%.3f".format(yearViewMs)}ms")
composeTraceEndSection() composeTraceEndSection()
} }
val contentTotalMs = (System.nanoTime() - contentStart) / 1_000_000.0
uiPerfLog("CalendarMonthView", "AnimatedContent content lambda END total=${"%.3f".format(contentTotalMs)}ms")
} }
}
// FAB 浮动按钮 // FAB 浮动按钮
FloatingActionButton( FloatingActionButton(
onClick = { isMenuExpanded = !isMenuExpanded }, onClick = { isMenuExpanded = !isMenuExpanded },
modifier = Modifier modifier = Modifier
.align(Alignment.BottomStart) .align(Alignment.BottomStart)
@ -385,7 +399,6 @@ fun CalendarMonthView(
} }
} }
} }
}
@Composable @Composable
private fun MenuIcon(color: Color, modifier: Modifier = Modifier) { private fun MenuIcon(color: Color, modifier: Modifier = Modifier) {
@ -415,6 +428,10 @@ private fun CalendarPagerArea(
pagerState: PagerState, pagerState: PagerState,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val areaStart = System.nanoTime()
var areaRecomposeCount by remember { mutableIntStateOf(0) }
areaRecomposeCount++
uiPerfLog("CalendarPagerArea", "composition #$areaRecomposeCount collapseProgress=${viewModel.collapseProgress} page=${pagerState.currentPage} rowHeightPx=$rowHeightPx")
val density = LocalDensity.current val density = LocalDensity.current
val collapseProgress = viewModel.collapseProgress val collapseProgress = viewModel.collapseProgress
@ -509,6 +526,8 @@ private fun CalendarPagerArea(
modifier = pagerModifier modifier = pagerModifier
) )
} }
val areaTotalMs = (System.nanoTime() - areaStart) / 1_000_000.0
uiPerfLog("CalendarPagerArea", "END elapsed=${"%.3f".format(areaTotalMs)}ms")
} }
@Composable @Composable
@ -516,6 +535,7 @@ private fun BottomCardArea(
viewModel: CalendarViewModel, viewModel: CalendarViewModel,
today: LocalDate, today: LocalDate,
rowHeightPx: Int, rowHeightPx: Int,
isYearView: Boolean,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val density = LocalDensity.current val density = LocalDensity.current
@ -526,12 +546,21 @@ private fun BottomCardArea(
dragRangeMinPx dragRangeMinPx
} }
val slideProgress by animateFloatAsState(
targetValue = if (isYearView) 1f else 0f,
animationSpec = tween(350, delayMillis = 100, easing = FastOutSlowInEasing),
label = "bottomCardSlide"
)
BottomCard( BottomCard(
viewModel = viewModel, viewModel = viewModel,
selectedDate = viewModel.selectedDate, selectedDate = viewModel.selectedDate,
today = today, today = today,
dragRangePx = dragRangePx, dragRangePx = dragRangePx,
modifier = modifier modifier = modifier.graphicsLayer {
translationY = slideProgress * 200.dp.toPx()
alpha = 1f - slideProgress
}
) )
} }

View File

@ -5,8 +5,11 @@ import androidx.compose.foundation.pager.PagerDefaults
import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.PagerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
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.draw.alpha
@ -17,6 +20,12 @@ import kotlinx.datetime.number
import plus.rua.project.ShiftKind import plus.rua.project.ShiftKind
import kotlin.math.abs import kotlin.math.abs
// region 性能监控工具
private fun pagerPerfLog(tag: String, msg: String) {
println("[PAGER-PERF:${Thread.currentThread().name}] $tag | $msg")
}
// endregion
/** /**
* 月度日历分页器HorizontalPager 实现无限左右滑动切换月份 * 月度日历分页器HorizontalPager 实现无限左右滑动切换月份
* *
@ -51,6 +60,9 @@ fun CalendarPager(
pagerState: PagerState, pagerState: PagerState,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
var recomposeCount by remember { mutableIntStateOf(0) }
recomposeCount++
pagerPerfLog("CalendarPager", "composition #$recomposeCount selectedDate=$selectedDate collapseProgress=$collapseProgress")
val initialYear = remember { today.year } val initialYear = remember { today.year }
@Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口 @Suppress("DEPRECATION") // monthNumber 无替代 APIkotlinx-datetime 尚未提供新接口
@ -71,6 +83,7 @@ fun CalendarPager(
flingBehavior = PagerDefaults.flingBehavior(state = pagerState), flingBehavior = PagerDefaults.flingBehavior(state = pagerState),
modifier = modifier modifier = modifier
) { page -> ) { page ->
val pageComposeStart = System.nanoTime()
val pageOffset = abs(pagerState.currentPageOffsetFraction) val pageOffset = abs(pagerState.currentPageOffsetFraction)
val isCurrentPage = page == pagerState.currentPage val isCurrentPage = page == pagerState.currentPage
val alpha = if (isCurrentPage) { val alpha = if (isCurrentPage) {
@ -109,5 +122,7 @@ fun CalendarPager(
onRowHeightMeasured = onRowHeightMeasured, onRowHeightMeasured = onRowHeightMeasured,
modifier = Modifier.alpha(alpha) modifier = Modifier.alpha(alpha)
) )
val pageComposeMs = (System.nanoTime() - pageComposeStart) / 1_000_000.0
pagerPerfLog("CalendarPager", "page=$page year=$year month=$month alpha=${"%.2f".format(alpha)} elapsed=${"%.3f".format(pageComposeMs)}ms")
} }
} }

View File

@ -8,7 +8,10 @@ import androidx.compose.foundation.pager.PagerDefaults
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.draw.alpha
@ -21,6 +24,12 @@ import kotlinx.datetime.plus
import plus.rua.project.ShiftKind import plus.rua.project.ShiftKind
import kotlin.math.abs import kotlin.math.abs
// region 性能监控工具
private fun weekPerfLog(tag: String, msg: String) {
println("[WEEK-PERF:${Thread.currentThread().name}] $tag | $msg")
}
// endregion
/** /**
* 周视图分页器折叠状态下显示选中日期所在周支持左右滑动切换周 * 周视图分页器折叠状态下显示选中日期所在周支持左右滑动切换周
* *
@ -42,6 +51,9 @@ fun WeekPager(
showLegalHoliday: Boolean, showLegalHoliday: Boolean,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
var recomposeCount by remember { mutableIntStateOf(0) }
recomposeCount++
weekPerfLog("WeekPager", "composition #$recomposeCount selectedDate=$selectedDate")
val initialWeekMonday = remember { selectedDate.toWeekMonday() } val initialWeekMonday = remember { selectedDate.toWeekMonday() }
val pagerState = rememberPagerState( val pagerState = rememberPagerState(
initialPage = START_PAGE, initialPage = START_PAGE,
@ -70,6 +82,7 @@ fun WeekPager(
flingBehavior = PagerDefaults.flingBehavior(state = pagerState), flingBehavior = PagerDefaults.flingBehavior(state = pagerState),
modifier = modifier modifier = modifier
) { page -> ) { page ->
val pageStart = System.nanoTime()
val pageOffset = abs(pagerState.currentPageOffsetFraction) val pageOffset = abs(pagerState.currentPageOffsetFraction)
val isCurrentPage = page == pagerState.currentPage val isCurrentPage = page == pagerState.currentPage
val alpha = if (isCurrentPage) { val alpha = if (isCurrentPage) {
@ -99,5 +112,7 @@ fun WeekPager(
) )
} }
} }
val pageMs = (System.nanoTime() - pageStart) / 1_000_000.0
weekPerfLog("WeekPager", "page=$page weekMonday=$weekMonday elapsed=${"%.3f".format(pageMs)}ms")
} }
} }

View File

@ -25,7 +25,9 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -50,6 +52,12 @@ import plus.rua.project.composeTraceEndSection
private val WEEKDAY_LABELS = listOf("", "", "", "", "", "", "") private val WEEKDAY_LABELS = listOf("", "", "", "", "", "", "")
// region 性能监控工具
private fun yearPerfLog(tag: String, msg: String) {
println("[YEAR-PERF:${Thread.currentThread().name}] $tag | $msg")
}
// endregion
private data class MiniMonthColors( private data class MiniMonthColors(
val titleSelected: Color, val titleSelected: Color,
val titleNormal: Color, val titleNormal: Color,
@ -82,6 +90,10 @@ fun YearGridView(
animatedVisibilityScope: AnimatedVisibilityScope, animatedVisibilityScope: AnimatedVisibilityScope,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val yearGridStart = System.nanoTime()
var yearGridRecomposeCount by remember { mutableIntStateOf(0) }
yearGridRecomposeCount++
yearPerfLog("YearGridView", "composition #$yearGridRecomposeCount year=$year selectedMonth=$selectedMonth")
composeTraceBeginSection("YearGridView:$year") composeTraceBeginSection("YearGridView:$year")
// P0-F: 主题色在 YearGridView 级别一次性读取并缓存 // P0-F: 主题色在 YearGridView 级别一次性读取并缓存
@ -196,6 +208,8 @@ fun YearGridView(
} }
} }
composeTraceEndSection() composeTraceEndSection()
val yearGridTotalMs = (System.nanoTime() - yearGridStart) / 1_000_000.0
yearPerfLog("YearGridView", "END year=$year elapsed=${"%.3f".format(yearGridTotalMs)}ms")
} }
/** /**
@ -308,6 +322,7 @@ private data class MiniDayData(
@Suppress("DEPRECATION") // monthNumber 无替代 API @Suppress("DEPRECATION") // monthNumber 无替代 API
private fun generateMiniMonthDays(year: Int, month: Int): List<MiniDayData> { private fun generateMiniMonthDays(year: Int, month: Int): List<MiniDayData> {
val start = System.nanoTime()
composeTraceBeginSection("generateMiniMonthDays:$year-$month") composeTraceBeginSection("generateMiniMonthDays:$year-$month")
val firstOfMonth = LocalDate(year, month, 1) val firstOfMonth = LocalDate(year, month, 1)
val offset = firstOfMonth.dayOfWeek.ordinal val offset = firstOfMonth.dayOfWeek.ordinal
@ -325,6 +340,8 @@ private fun generateMiniMonthDays(year: Int, month: Int): List<MiniDayData> {
) )
} }
composeTraceEndSection() composeTraceEndSection()
val elapsedMs = (System.nanoTime() - start) / 1_000_000.0
yearPerfLog("generateMiniMonthDays", "$year-$month rows=$rows totalDays=$totalDays elapsed=${"%.3f".format(elapsedMs)}ms")
return result return result
} }