perf: 引入 logd 条件日志工具,增强动画调试并优化 sharedElement 缓存

- 新增 AnimLog.kt,提供 BuildConfig.DEBUG 条件控制的 logd 日志工具,
  支持 lambda 延迟求值以避免 release 模式下的字符串拼接开销
- 全模块替换 android.util.Log.d 为 logd,并将日志重构为 lambda 形式
- CalendarViewModel: toggleYearView / selectMonthFromYearView 添加纳秒级
  耗时追踪日志
- CalendarMonthView / CalendarMonthPage / CalendarPager / YearGridView:
  增加重组/进入/离开/页面切换/动画状态变化的详细调试日志
- 折叠动画/滑动进度日志添加状态去重,避免频繁重复输出
- P0: 缓存 sharedElement tween 实例(CalendarMonthView + YearGridView),
  避免每次重组创建新实例导致动画重新计算
- P0: YearPager pageYear 使用 remember 稳定计算,避免 settledPage 与
  yearViewYear 不同步导致抖动
- 移除 YearPager crossFadeAlpha,改为无透明度渐变
- app/build.gradle.kts 添加 profileinstaller 依赖

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xfy 2026-05-26 18:27:07 +08:00
parent 20425e392c
commit 9a0222b4a2
7 changed files with 143 additions and 30 deletions

View File

@ -93,6 +93,7 @@ dependencies {
implementation(platform(libs.compose.bom)) implementation(platform(libs.compose.bom))
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.profileinstaller)
debugImplementation(libs.compose.uiToolingPreview) debugImplementation(libs.compose.uiToolingPreview)
debugImplementation(libs.compose.uiTooling) debugImplementation(libs.compose.uiTooling)
} }

View File

@ -20,7 +20,7 @@ import kotlinx.datetime.plus
import kotlinx.datetime.todayIn import kotlinx.datetime.todayIn
import plus.rua.project.ui.COLLAPSE_THRESHOLD import plus.rua.project.ui.COLLAPSE_THRESHOLD
import plus.rua.project.ui.getMonthGridInfo import plus.rua.project.ui.getMonthGridInfo
import android.util.Log import plus.rua.project.util.logd
import kotlin.time.Clock import kotlin.time.Clock
private const val TAG_VM = "CalendarExpand" private const val TAG_VM = "CalendarExpand"
@ -210,17 +210,27 @@ class CalendarViewModel(
* 当前视图被直接移除动画只作用在目标视图的 scale/alpha * 当前视图被直接移除动画只作用在目标视图的 scale/alpha
*/ */
fun toggleYearView() { fun toggleYearView() {
val t0 = System.nanoTime()
if (_isYearView.value) { if (_isYearView.value) {
logd(TAG_VM, "[toggleYearView] ===== START Year→Month t=$t0 =====")
composeTraceBeginSection("YearView→MonthView") composeTraceBeginSection("YearView→MonthView")
_yearViewProgress.value = 0f _yearViewProgress.value = 0f
logd(TAG_VM, "[toggleYearView] yearViewProgress=0 dt=${(System.nanoTime() - t0) / 1_000_000}ms")
_isYearView.value = false _isYearView.value = false
logd(TAG_VM, "[toggleYearView] isYearView=false dt=${(System.nanoTime() - t0) / 1_000_000}ms")
composeTraceEndSection() composeTraceEndSection()
logd(TAG_VM, "[toggleYearView] ===== END Year→Month total=${(System.nanoTime() - t0) / 1_000_000}ms =====")
} else { } else {
logd(TAG_VM, "[toggleYearView] ===== START Month→Year t=$t0 =====")
composeTraceBeginSection("MonthView→YearView") composeTraceBeginSection("MonthView→YearView")
_yearViewYear.value = _selectedDate.value.year _yearViewYear.value = _selectedDate.value.year
logd(TAG_VM, "[toggleYearView] yearViewYear=${_yearViewYear.value} dt=${(System.nanoTime() - t0) / 1_000_000}ms")
_yearViewProgress.value = 1f _yearViewProgress.value = 1f
logd(TAG_VM, "[toggleYearView] yearViewProgress=1 dt=${(System.nanoTime() - t0) / 1_000_000}ms")
_isYearView.value = true _isYearView.value = true
logd(TAG_VM, "[toggleYearView] isYearView=true dt=${(System.nanoTime() - t0) / 1_000_000}ms")
composeTraceEndSection() composeTraceEndSection()
logd(TAG_VM, "[toggleYearView] ===== END Month→Year total=${(System.nanoTime() - t0) / 1_000_000}ms =====")
} }
} }
@ -236,13 +246,20 @@ class CalendarViewModel(
*/ */
@Suppress("DEPRECATION") // monthNumber 无替代 API @Suppress("DEPRECATION") // monthNumber 无替代 API
fun selectMonthFromYearView(month: Int) { fun selectMonthFromYearView(month: Int) {
val t0 = System.nanoTime()
logd(TAG_VM, "[selectMonthFromYearView] ===== START month=$month t=$t0 =====")
composeTraceBeginSection("YearView:SelectMonth") composeTraceBeginSection("YearView:SelectMonth")
val date = if (_yearViewYear.value == today.year && today.month.number == month) today val date = if (_yearViewYear.value == today.year && today.month.number == month) today
else LocalDate(_yearViewYear.value, month, 1) else LocalDate(_yearViewYear.value, month, 1)
logd(TAG_VM, "[selectMonthFromYearView] targetDate=$date dt=${(System.nanoTime() - t0) / 1_000_000}ms")
_selectedDate.value = date _selectedDate.value = date
logd(TAG_VM, "[selectMonthFromYearView] selectedDate set dt=${(System.nanoTime() - t0) / 1_000_000}ms")
_isYearView.value = false _isYearView.value = false
logd(TAG_VM, "[selectMonthFromYearView] isYearView=false dt=${(System.nanoTime() - t0) / 1_000_000}ms")
_yearViewProgress.value = 0f _yearViewProgress.value = 0f
logd(TAG_VM, "[selectMonthFromYearView] yearViewProgress=0 dt=${(System.nanoTime() - t0) / 1_000_000}ms")
composeTraceEndSection() composeTraceEndSection()
logd(TAG_VM, "[selectMonthFromYearView] ===== END total=${(System.nanoTime() - t0) / 1_000_000}ms =====")
} }
fun incrementYear() { fun incrementYear() {
@ -290,7 +307,7 @@ class CalendarViewModel(
fun onExpandDrag(delta: Float) { fun onExpandDrag(delta: Float) {
val old = _collapseProgress.value val old = _collapseProgress.value
_collapseProgress.value = (_collapseProgress.value + delta).coerceIn(0f, 1f) _collapseProgress.value = (_collapseProgress.value + delta).coerceIn(0f, 1f)
Log.d(TAG_VM, "onExpandDrag: delta=$delta old=$old new=${_collapseProgress.value}") logd(TAG_VM, "onExpandDrag: delta=$delta old=$old new=${_collapseProgress.value}")
} }
/** /**
@ -309,7 +326,7 @@ class CalendarViewModel(
_collapseProgress.value = 1f _collapseProgress.value = 1f
"COLLAPSED (bounce back)" "COLLAPSED (bounce back)"
} }
Log.d(TAG_VM, "onExpandDragEnd: progress=$progress threshold=${1 - COLLAPSE_THRESHOLD} result=$result") logd(TAG_VM, "onExpandDragEnd: progress=$progress threshold=${1 - COLLAPSE_THRESHOLD} result=$result")
} }
/** /**

View File

@ -21,7 +21,7 @@ import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import android.util.Log import plus.rua.project.util.logd
import kotlinx.datetime.DatePeriod import kotlinx.datetime.DatePeriod
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.datetime.minus import kotlinx.datetime.minus
@ -109,13 +109,13 @@ fun CalendarMonthPage(
// 全局动画参数日志(每次重组) // 全局动画参数日志(每次重组)
val pageFrameNs = System.nanoTime() val pageFrameNs = System.nanoTime()
Log.d( val totalCells = weeks.size * 7
TAG_CMP, logd(TAG_CMP) {
"Page[$year-$month]: anchorIndex=$anchorIndex weeksSize=${weeks.size} " + "Page[$year-$month]: anchorIndex=$anchorIndex weeksSize=${weeks.size} totalCells=$totalCells " +
"phase1End=${if (anchorIndex > 0 && weeks.size > 1) anchorIndex.toFloat() / (weeks.size - 1) else 0f} " + "phase1End=${if (anchorIndex > 0 && weeks.size > 1) anchorIndex.toFloat() / (weeks.size - 1) else 0f} " +
"effectiveWeeks=$effectiveWeeks rowHeightPx=$rowHeightPx " + "effectiveWeeks=$effectiveWeeks rowHeightPx=$rowHeightPx " +
"collapseProgress=$collapseProgress frameNs=$pageFrameNs" "collapseProgress=$collapseProgress lunarMapSize=${lunarDataMap.size} frameNs=$pageFrameNs"
) }
val totalHeightDp = if (rowHeightPx > 0) { val totalHeightDp = if (rowHeightPx > 0) {
val h = rowHeightPx.toFloat() val h = rowHeightPx.toFloat()
@ -213,8 +213,7 @@ private fun WeekRow(
} }
val frameTimeNs = System.nanoTime() val frameTimeNs = System.nanoTime()
Log.d( logd(TAG_CMP) {
TAG_CMP,
"WeekRow[$weekIndex]: " + "WeekRow[$weekIndex]: " +
"isAnchor=$isAnchor isAbove=$isAbove isBelow=$isBelow " + "isAnchor=$isAnchor isAbove=$isAbove isBelow=$isBelow " +
"phase1=$phase1 phase2=$phase2 phase1End=$phase1End " + "phase1=$phase1 phase2=$phase2 phase1End=$phase1End " +
@ -222,7 +221,7 @@ private fun WeekRow(
"yOffsetPx=$yOffsetPx rowAlpha=$rowAlpha " + "yOffsetPx=$yOffsetPx rowAlpha=$rowAlpha " +
"collapseProgress=$collapseProgress " + "collapseProgress=$collapseProgress " +
"frameNs=$frameTimeNs" "frameNs=$frameTimeNs"
) }
if (rowAlpha > 0.01f) { if (rowAlpha > 0.01f) {
Row( Row(

View File

@ -82,7 +82,7 @@ import plus.rua.project.composeTraceEndSection
import kotlin.math.abs import kotlin.math.abs
import kotlin.time.Clock import kotlin.time.Clock
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import android.util.Log import plus.rua.project.util.logd
/** /**
* 日历主界面包含月/周视图切换折叠动画和年视图共享元素转场 * 日历主界面包含月/周视图切换折叠动画和年视图共享元素转场
@ -119,8 +119,13 @@ fun CalendarMonthView(
animationSpec = spring(stiffness = Spring.StiffnessMedium), animationSpec = spring(stiffness = Spring.StiffnessMedium),
label = "collapseProgress" label = "collapseProgress"
) )
var lastLoggedCollapse by remember { mutableStateOf(-1f) }
SideEffect { SideEffect {
Log.d("CalendarExpandAnim", "View: target=$collapseProgress animated=$animatedCollapseProgress isCollapsed=$isCollapsed") if (kotlin.math.abs(lastLoggedCollapse - collapseProgress) > 0.001f) {
lastLoggedCollapse = collapseProgress
logd("AnimLog", "[Collapse] target=$collapseProgress animated=$animatedCollapseProgress isCollapsed=$isCollapsed")
}
logd("AnimLog", "[MonthView] isYearView=$isYearView isCollapsed=$isCollapsed collapseProgress=$collapseProgress animated=$animatedCollapseProgress selectedDate=$selectedDate yearViewYear=$yearViewYear")
} }
val density = LocalDensity.current val density = LocalDensity.current
@ -178,6 +183,13 @@ fun CalendarMonthView(
) { ) {
SharedTransitionLayout { SharedTransitionLayout {
val sharedScope = this val sharedScope = this
var lastLoggedTargetState by remember { mutableStateOf(false) }
SideEffect {
if (lastLoggedTargetState != isYearView) {
lastLoggedTargetState = isYearView
logd("AnimLog", "[AnimatedContent] ★ targetState CHANGE isYearView=$isYearView t=${System.nanoTime()}")
}
}
AnimatedContent( AnimatedContent(
targetState = isYearView, targetState = isYearView,
label = "month_year_transition", label = "month_year_transition",
@ -191,6 +203,13 @@ fun CalendarMonthView(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { yearViewActive -> ) { yearViewActive ->
if (!yearViewActive) { if (!yearViewActive) {
androidx.compose.runtime.DisposableEffect(Unit) {
val t = System.nanoTime()
logd("AnimLog", "[MonthView] ★★★ ENTER composable t=$t")
onDispose {
logd("AnimLog", "[MonthView] ★★★ LEAVE composable alive=${(System.nanoTime() - t) / 1_000_000}ms")
}
}
composeTraceBeginSection("MonthView:Compose") composeTraceBeginSection("MonthView:Compose")
composeTraceBeginSection("CalendarPagerArea") composeTraceBeginSection("CalendarPagerArea")
val layoutReady = rowHeightPx > 0 val layoutReady = rowHeightPx > 0
@ -239,6 +258,8 @@ fun CalendarMonthView(
{ h: Int -> if (h > 0) rowHeightPx = h } { h: Int -> if (h > 0) rowHeightPx = h }
} }
with(sharedScope) { with(sharedScope) {
// P0: 缓存 sharedElement tween避免每次重组创建新实例导致动画重新计算
val sharedTween = remember { tween<androidx.compose.ui.geometry.Rect>(400, easing = FastOutSlowInEasing) }
CalendarPagerArea( CalendarPagerArea(
selectedDate = selectedDate, selectedDate = selectedDate,
today = today, today = today,
@ -257,9 +278,7 @@ fun CalendarMonthView(
key = "month_grid_${currentYear}_${currentMonth}" key = "month_grid_${currentYear}_${currentMonth}"
), ),
animatedVisibilityScope = this@AnimatedContent, animatedVisibilityScope = this@AnimatedContent,
boundsTransform = { _, _ -> boundsTransform = { _, _ -> sharedTween }
tween(400, easing = FastOutSlowInEasing)
}
) )
.clipToBounds() .clipToBounds()
) )
@ -276,6 +295,13 @@ fun CalendarMonthView(
composeTraceEndSection() composeTraceEndSection()
composeTraceEndSection() composeTraceEndSection()
} else { } else {
androidx.compose.runtime.DisposableEffect(Unit) {
val t = System.nanoTime()
logd("AnimLog", "[YearView] ★★★ ENTER composable t=$t")
onDispose {
logd("AnimLog", "[YearView] ★★★ LEAVE composable alive=${(System.nanoTime() - t) / 1_000_000}ms")
}
}
composeTraceBeginSection("YearView:Compose") composeTraceBeginSection("YearView:Compose")
Column( Column(
modifier = Modifier modifier = Modifier
@ -294,6 +320,13 @@ fun CalendarMonthView(
} }
} }
) )
var lastLoggedYearPage by remember { mutableIntStateOf(-1) }
SideEffect {
if (lastLoggedYearPage != yearPagerState.currentPage) {
lastLoggedYearPage = yearPagerState.currentPage
logd("AnimLog", "[YearPager] page=${yearPagerState.currentPage} settledPage=${yearPagerState.settledPage} offset=${yearPagerState.currentPageOffsetFraction}")
}
}
HorizontalPager( HorizontalPager(
state = yearPagerState, state = yearPagerState,
beyondViewportPageCount = 0, beyondViewportPageCount = 0,
@ -302,19 +335,21 @@ fun CalendarMonthView(
.fillMaxWidth() .fillMaxWidth()
.weight(1f) .weight(1f)
) { page -> ) { page ->
val pageOffset = abs(yearPagerState.currentPageOffsetFraction) // P0: 稳定 pageYear 计算,避免 settledPage/yearViewYear 不同步导致抖动
val isCurrentPage = page == yearPagerState.currentPage val pageYear = remember(page, yearViewYear, yearPagerState.settledPage) {
val crossFadeAlpha = if (isCurrentPage) { yearViewYear + (page - yearPagerState.settledPage)
1f - pageOffset }
} else { val isCurrentPage = page == yearPagerState.currentPage
pageOffset if (isCurrentPage) {
logd("AnimLog") { "[YearPager] Compose page=$page year=$pageYear" }
} }
val pageYear = yearViewYear + (page - yearPagerState.settledPage)
YearGridView( YearGridView(
year = pageYear, year = pageYear,
selectedMonth = if (pageYear == currentYear) currentMonth else 0, selectedMonth = if (pageYear == currentYear) currentMonth else 0,
today = today, today = today,
onMonthClick = { month -> onMonthClick = { month ->
val clickT = System.nanoTime()
logd("AnimLog") { "[YearGridView] MonthClick month=$month year=$pageYear t=$clickT" }
viewModel.selectMonthFromYearView(month) viewModel.selectMonthFromYearView(month)
@Suppress("DEPRECATION") // monthNumber 无替代 API @Suppress("DEPRECATION") // monthNumber 无替代 API
val targetPage = yearMonthToPage( val targetPage = yearMonthToPage(
@ -322,12 +357,13 @@ fun CalendarMonthView(
today.year, today.month.number today.year, today.month.number
) )
if (targetPage != pagerState.currentPage) { if (targetPage != pagerState.currentPage) {
logd("AnimLog") { "[YearPager] scrollToPage target=$targetPage" }
coroutineScope.launch { pagerState.scrollToPage(targetPage) } coroutineScope.launch { pagerState.scrollToPage(targetPage) }
} }
}, },
sharedTransitionScope = sharedScope, sharedTransitionScope = sharedScope,
animatedVisibilityScope = this@AnimatedContent, animatedVisibilityScope = this@AnimatedContent,
modifier = Modifier.alpha(crossFadeAlpha) modifier = Modifier
) )
} }
} }
@ -477,12 +513,13 @@ private fun CalendarPagerArea(
pagerState: PagerState, pagerState: PagerState,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val t0 = System.nanoTime()
val density = LocalDensity.current val density = LocalDensity.current
val interpolatedWeeks by remember { val interpolatedWeeks by remember {
derivedStateOf { derivedStateOf {
val fraction = pagerState.currentPageOffsetFraction val fraction = pagerState.currentPageOffsetFraction
if (abs(fraction) > OFFSET_FRACTION_THRESHOLD) { val result = if (abs(fraction) > OFFSET_FRACTION_THRESHOLD) {
val cp = pagerState.currentPage val cp = pagerState.currentPage
val baseWeeks = calculateWeeksCountForPage(cp, today) val baseWeeks = calculateWeeksCountForPage(cp, today)
val targetPage = cp + if (fraction > 0) 1 else -1 val targetPage = cp + if (fraction > 0) 1 else -1
@ -491,6 +528,8 @@ private fun CalendarPagerArea(
} else { } else {
calculateWeeksCountForPage(pagerState.currentPage, today).toFloat() calculateWeeksCountForPage(pagerState.currentPage, today).toFloat()
} }
logd("AnimLog", "[PagerArea] interpolatedWeeks=$result fraction=$fraction page=${pagerState.currentPage}")
result
} }
} }
@ -514,6 +553,8 @@ private fun CalendarPagerArea(
} }
} else 0 } else 0
logd("AnimLog", "[PagerArea] gridHeightPx=$gridHeightPx effectiveRowHeightPx=$effectiveRowHeightPx effectiveWeeks=$effectiveWeeks collapseProgress=$collapseProgress screenW=$screenWidthPx rowH=$rowHeightPx dt=${(System.nanoTime() - t0) / 1_000_000}ms")
val pagerModifier = if (rowHeightPx > 0 && gridHeightPx > 0) { val pagerModifier = if (rowHeightPx > 0 && gridHeightPx > 0) {
Modifier Modifier
.height(with(density) { gridHeightPx.toDp() }) .height(with(density) { gridHeightPx.toDp() })
@ -559,17 +600,27 @@ private fun BottomCardArea(
animationSpec = tween(350, delayMillis = 100, easing = FastOutSlowInEasing), animationSpec = tween(350, delayMillis = 100, easing = FastOutSlowInEasing),
label = "bottomCardSlide" label = "bottomCardSlide"
) )
var lastLoggedSlide by remember { mutableStateOf(-1f) }
SideEffect {
if (kotlin.math.abs(lastLoggedSlide - slideProgress) > 0.001f) {
lastLoggedSlide = slideProgress
logd("AnimLog", "[BottomCard] slideProgress=$slideProgress isYearView=$isYearView")
}
}
// 延迟一帧显示 BottomCard避免 AnimatedGif 和 lunar 计算阻塞首帧 // 延迟一帧显示 BottomCard避免 AnimatedGif 和 lunar 计算阻塞首帧
var hasLoaded by remember { mutableStateOf(false) } var hasLoaded by remember { mutableStateOf(false) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
delay(32) delay(32)
hasLoaded = true hasLoaded = true
logd("AnimLog", "[BottomCard] hasLoaded=true after delay")
} }
val shouldShow = hasLoaded val shouldShow = hasLoaded
val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsState()
val shiftKind = viewModel.shiftKindAt(uiState.selectedDate) val shiftKind = viewModel.shiftKindAt(uiState.selectedDate)
logd("AnimLog", "[BottomCard] shouldShow=$shouldShow isYearView=$isYearView slideProgress=$slideProgress dragRangePx=$dragRangePx rowHeightPx=$rowHeightPx")
if (shouldShow) { if (shouldShow) {
BottomCard( BottomCard(
isCollapsed = uiState.isCollapsed, isCollapsed = uiState.isCollapsed,

View File

@ -5,12 +5,16 @@ 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.SideEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
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.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 plus.rua.project.util.logd
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -75,6 +79,14 @@ fun CalendarPager(
derivedStateOf { pagerState.currentPage } derivedStateOf { pagerState.currentPage }
} }
var lastLoggedPage by remember { mutableIntStateOf(-1) }
SideEffect {
if (lastLoggedPage != pagerState.currentPage) {
lastLoggedPage = pagerState.currentPage
logd("AnimLog", "[CalendarPager] page=${pagerState.currentPage} settledPage=${pagerState.settledPage} offsetFraction=${pagerState.currentPageOffsetFraction}")
}
}
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
beyondViewportPageCount = 0, beyondViewportPageCount = 0,
@ -89,12 +101,16 @@ fun CalendarPager(
pageOffset pageOffset
} }
val (year, month) = pageToYearMonth(page, initialYear, initialMonth) val (year, month) = pageToYearMonth(page, initialYear, initialMonth)
if (isCurrentPage) {
logd("AnimLog", "[CalendarPager] Compose page=$page ($year-$month) alpha=$alpha pageOffset=$pageOffset")
}
CalendarMonthPage( CalendarMonthPage(
year = year, year = year,
month = month, month = month,
selectedDate = selectedDate, selectedDate = selectedDate,
today = today, today = today,
onDateClick = { date -> onDateClick = { date ->
val clickT = System.nanoTime()
onDateClick(date) onDateClick(date)
// 点击跨月日期时,滚动到该月对应的页 // 点击跨月日期时,滚动到该月对应的页
val clickedYear = date.year val clickedYear = date.year
@ -105,6 +121,7 @@ fun CalendarPager(
val targetPage = val targetPage =
yearMonthToPage(clickedYear, clickedMonth, initialYear, initialMonth) yearMonthToPage(clickedYear, clickedMonth, initialYear, initialMonth)
if (targetPage != pagerState.currentPage) { if (targetPage != pagerState.currentPage) {
logd("AnimLog", "[CalendarPager] Cross-month click date=$date targetPage=$targetPage t=$clickT")
coroutineScope.launch { coroutineScope.launch {
pagerState.animateScrollToPage(targetPage) pagerState.animateScrollToPage(targetPage)
} }

View File

@ -29,6 +29,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import plus.rua.project.util.logd
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
@ -87,6 +88,14 @@ fun YearGridView(
animatedVisibilityScope: AnimatedVisibilityScope, animatedVisibilityScope: AnimatedVisibilityScope,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val enterT = System.nanoTime()
logd("AnimLog", "[YearGridView] ★★★ ENTER year=$year selectedMonth=$selectedMonth t=$enterT")
androidx.compose.runtime.DisposableEffect(year) {
logd("AnimLog", "[YearGridView] DisposableEffect attached year=$year")
onDispose {
logd("AnimLog", "[YearGridView] ★★★ LEAVE year=$year alive=${(System.nanoTime() - enterT) / 1_000_000}ms")
}
}
composeTraceBeginSection("YearGridView:$year") composeTraceBeginSection("YearGridView:$year")
// P0-F: 主题色在 YearGridView 级别一次性读取并缓存 // P0-F: 主题色在 YearGridView 级别一次性读取并缓存
@ -165,6 +174,9 @@ fun YearGridView(
(0 until 3).forEach { col -> (0 until 3).forEach { col ->
val month = row * 3 + col + 1 val month = row * 3 + col + 1
with(sharedTransitionScope) { with(sharedTransitionScope) {
// P0: 缓存 sharedElement tween避免每次重组创建新实例
val miniMonthTween = remember { tween<androidx.compose.ui.geometry.Rect>(400, easing = FastOutSlowInEasing) }
val seKey = "month_grid_${year}_$month"
MiniMonth( MiniMonth(
year = year, year = year,
month = month, month = month,
@ -180,12 +192,10 @@ fun YearGridView(
.weight(1f) .weight(1f)
.sharedElement( .sharedElement(
sharedContentState = rememberSharedContentState( sharedContentState = rememberSharedContentState(
key = "month_grid_${year}_$month" key = seKey
), ),
animatedVisibilityScope = animatedVisibilityScope, animatedVisibilityScope = animatedVisibilityScope,
boundsTransform = { _, _ -> boundsTransform = { _, _ -> miniMonthTween }
tween(400, easing = FastOutSlowInEasing)
}
) )
) )
} }

View File

@ -0,0 +1,18 @@
package plus.rua.project.util
import android.util.Log
import plus.rua.project.shared.BuildConfig
@Suppress("NOTHING_TO_INLINE")
inline fun logd(tag: String, message: () -> String) {
if (BuildConfig.DEBUG) {
Log.d(tag, message())
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun logd(tag: String, message: String) {
if (BuildConfig.DEBUG) {
Log.d(tag, message)
}
}