Android Compose自定义网格日历HorizontalPager分页性能问题咨询
自定义Compose日历分页性能优化问题
我正在使用Compose开发自定义日历,通过LazyVerticalGrid显示整月日历,并借助HorizontalPager实现分页切换前后日历。当前日历视图可正常展示,但分页切换时性能表现不佳。想咨询:该日历的实现结构是否存在问题?是否因绘制视图过多导致性能下降?
实现代码
@OptIn(ExperimentalFoundationApi::class) @Composable fun CalendarPager( todayClicked: (LocalDate) -> Unit, pagedMonth: (LocalDate) -> Unit, ){ val yearRange = IntRange(1970, 2100) val initialPage = (LocalDate.now().year - yearRange.first) * 12 + LocalDate.now().monthValue - 1 val pagerState = rememberPagerState(initialPage = initialPage) { (yearRange.last - yearRange.first) * 12 // 모든 년도의 월 수 만큼 pageCount } HorizontalPager(state = pagerState) { page -> val month = LocalDate.of( yearRange.first + pagerState.currentPage / 12, pagerState.currentPage % 12 + 1, 1 ) pagedMonth.invoke(month) val displayDate by remember { derivedStateOf { LocalDate.of( yearRange.first + page / 12, page % 12 + 1, 1 ) } } TestGrid( modifier = Modifier.padding(horizontal = 10.dp), calendarDate = displayDate, onSelectDate = todayClicked ) }.also { DlogUtil.d(MyTag, "Recomposition Check HorizontalPager") } } @Composable fun TestGrid( modifier: Modifier, calendarDate: LocalDate, onSelectDate: (LocalDate) -> Unit ) { val lastDay = calendarDate.lengthOfMonth() val days = IntRange(1, lastDay).toList() val firstDayOfWeek = calendarDate.dayOfWeek.value val lastDayColumn = (firstDayOfWeek + lastDay - 1) % 7 val afterBoxCount = 7 - lastDayColumn - 1 val totalGridCount = if (firstDayOfWeek == 7) 0 else firstDayOfWeek + days.size + afterBoxCount val boxHeight = if (totalGridCount > 35) { 45.dp } else { 54.dp } LazyVerticalGrid( modifier = modifier, columns = GridCells.Fixed(7) ) { if (firstDayOfWeek != 7) { repeat(firstDayOfWeek) { item { EmptyDay(boxHeight) } } } items(days) { day -> val date = calendarDate.withDayOfMonth(day) TestDay( height = boxHeight, displayDate = date, isToday = date == LocalDate.now(), isSelected = false, onSelectDate = onSelectDate ) } repeat(afterBoxCount) { item { EmptyDay(boxHeight) } } }.also { DlogUtil.d(MyTag, "Recomposition Check TestGrid") } } @Composable fun TestDay( height: Dp, displayDate: LocalDate, isToday: Boolean, isSelected: Boolean, onSelectDate: (LocalDate) -> Unit ) { val dayOfWeek = displayDate.dayOfWeek Box( modifier = Modifier .height(height) .clip(shape = RoundedCornerShape(10.dp)) .calendarBorder(isSelected) // 선택 Day Gray Border .noRippleClickable { onSelectDate(displayDate) }, contentAlignment = Alignment.TopCenter ) { Box( modifier = Modifier .width(18.dp) .wrapContentHeight() .padding(top = 2.dp) .clip(shape = RoundedCornerShape(4.dp)) .calendarBackground(isToday, getTodayBgColor(dayOfWeek)), contentAlignment = Alignment.Center ) { Text( text = displayDate.dayOfMonth.toString(), color = getTextColor(isToday, dayOfWeek), fontSize = 12.sp, fontWeight = FontWeight.Bold ) } }.also { DlogUtil.d(MyTag, "Recomposition Check TestDay Box") } }
问题分析与优化方案
一、实现结构的核心问题
HorizontalPager日期计算错误
在HorizontalPager的content lambda中,你用pagerState.currentPage计算month,但正确的应该用传入的page参数。currentPage是当前选中的页面,而page是当前正在组合的页面,混用会触发不必要的重组。同时displayDate的计算不需要derivedStateOf,直接用page计算即可。不稳定变量触发频繁重组
TestGrid中days = IntRange(1, lastDay).toList()每次重组都会生成新列表,导致子组件重复重组,需要用remember包裹稳定化。TestDay中直接调用LocalDate.now()判断isToday,每次重组都会重新计算,应该把当前日期提升到上层缓存后传递。
冗余布局处理
LazyVerticalGrid中的空项EmptyDay用多次repeat生成独立item,可以简化逻辑;且EmptyDay如果仅做占位,应尽量减少布局层级。
二、具体优化步骤
修正HorizontalPager日期逻辑
HorizontalPager(state = pagerState) { page -> val displayDate = LocalDate.of( yearRange.first + page / 12, page % 12 + 1, 1 ) pagedMonth.invoke(displayDate) TestGrid( modifier = Modifier.padding(horizontal = 10.dp), calendarDate = displayDate, onSelectDate = todayClicked ) }稳定化TestGrid内的变量
// 在TestGrid中用remember缓存日期列表 val days = remember(calendarDate) { IntRange(1, calendarDate.lengthOfMonth()).toList() }缓存当前日期避免重复计算
在CalendarPager中缓存当前日期,传递给子组件:val today = rememberSaveable { LocalDate.now() }然后在
TestGrid的items中传递isToday:TestDay( height = boxHeight, displayDate = date, isToday = date == today, isSelected = false, onSelectDate = onSelectDate )简化EmptyDay组件
如果仅做占位,直接用Spacer替代:// 在LazyVerticalGrid的空项中 repeat(firstDayOfWeek) { item { Spacer(modifier = Modifier.height(boxHeight)) } }调整HorizontalPager预加载策略
默认HorizontalPager会预加载前后各一页,若不需要可减少预加载数量:HorizontalPager( state = pagerState, offscreenLimit = 0 // 仅加载当前页,按需调整 ) { page -> // ... }
三、性能下降的根本原因
不是因为绘制视图过多,而是不必要的重组导致的:
- 不稳定的参数(如每次生成的新列表、频繁计算的
LocalDate.now())触发大量组件重复重组,增加Compose计算开销。 - 混用
currentPage与page参数导致页面切换时错误的状态更新,触发额外重组。
内容的提问来源于stack exchange,提问作者JaeBumLee
相关产品推荐
相关产品推荐

