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:
parent
27ba4f9f54
commit
f6b5e62284
@ -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 无替代 API,kotlinx-datetime 尚未提供新接口
|
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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(
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 无替代 API,kotlinx-datetime 尚未提供新接口
|
@Suppress("DEPRECATION") // monthNumber 无替代 API,kotlinx-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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user