如何让Jetpack Compose ViewPager页面仅在可见时调用一次?
Jetpack Compose HorizontalPager 引导页组件重复加载及OOM问题解决
问题说明
使用Jetpack Compose的HorizontalPager开发引导页时,出现TutorialPager和TutorialPage频繁重组、重复执行的问题,导致页面内的Lottie动画重复加载,最终引发OutOfMemory错误。从日志可见,页面初始化和切换时组件会多次触发,日志重复输出,期望实现页面仅在可见状态下加载一次。
核心原因
- HorizontalPager预加载机制:默认会预加载相邻页面,导致非可见页面的Composable提前创建。
- 重组触发频繁:
TutorialPage内每次重组都会创建新的MutableTransitionState,导致AnimatedVisibility反复执行动画逻辑。 - Lottie动画逻辑冗余:同时使用
LottieAnimatable和animateLottieCompositionAsState,导致动画重复启动、资源重复加载。
解决方案
1. 修改HorizontalPager配置,关闭预加载
@OptIn(ExperimentalPagerApi::class) @Composable fun TutorialPager( tutorials: List<TutorialViewModel.TutorialUiResources>, state: PagerState, modifier: Modifier = Modifier ) { HorizontalPager( count = tutorials.size, state = state, modifier = modifier.fillMaxSize(), userScrollEnabled = false, // 关闭预加载,仅加载当前可见页面 beyondBoundsPageCount = 0, pageSpacing = 0.dp ) { page -> // 直接使用Pager传入的page索引,避免state.currentPage引发的重组不一致 TutorialPage(page = page, tutorial = tutorials[page], pagerState = state) } }
2. 优化TutorialPage,减少不必要重组
@OptIn(ExperimentalPagerApi::class) @Composable fun TutorialPage( page: Int, tutorial: TutorialViewModel.TutorialUiResources, pagerState: PagerState, modifier: Modifier = Modifier ) { val isVisible = pagerState.currentPage == page val pxValue = LocalDensity.current.run { 60.dp.toPx() } // 用remember保存过渡状态,避免每次重组创建新实例 val lottieVisibleState = remember { MutableTransitionState(false) } val titleVisibleState = remember { MutableTransitionState(false) } val spacerVisibleState = remember { MutableTransitionState(false) } val subTitleVisibleState = remember { MutableTransitionState(false) } // 监听页面可见状态,批量更新过渡目标状态 LaunchedEffect(isVisible) { lottieVisibleState.targetState = isVisible titleVisibleState.targetState = isVisible spacerVisibleState.targetState = isVisible subTitleVisibleState.targetState = isVisible } if (isVisible) { Box(modifier = modifier.fillMaxSize()) { AnimatedVisibility( visibleState = lottieVisibleState, enter = pageItemSlideInTransition(pxValue.toInt(), 100) ) { Log.d("aos", "World: page:$page, ${tutorial.lottie}") Lottie( play = isVisible, lottie = tutorial.lottie, modifier = Modifier .align(Alignment.TopStart) .size(220.dp), speed = 0.3f ) } Column( modifier = Modifier .align(Alignment.BottomStart) ) { AnimatedVisibility( visibleState = titleVisibleState, enter = pageItemSlideInTransition(pxValue.toInt(), 250) ) { Text( text = stringResource(id = tutorial.titleRes), fontFamily = HanSansFamily, style = MaterialTheme.typography.headlineLarge, color = Color.Black ) } AnimatedVisibility( visibleState = spacerVisibleState, enter = pageItemSlideInTransition(pxValue.toInt(), 400) ) { Spacer( modifier = Modifier .padding(vertical = 20.dp) .size(width = 28.dp, height = 4.dp) .background(MaterialTheme.colorScheme.primary, shape = RoundedCornerShape(100)) ) } AnimatedVisibility( visibleState = subTitleVisibleState, enter = pageItemSlideInTransition(pxValue.toInt(), 550) ) { Text( text = stringResource(id = tutorial.subTitleRes), style = MaterialTheme.typography.bodyLarge, lineHeight = 22.4.sp, color = Color.Gray05 ) } } } } }
3. 简化Lottie组件,避免资源重复加载
@Composable fun Lottie(play: Boolean, @RawRes lottie: Int, modifier: Modifier = Modifier, speed: Float = 1f, iterations: Int = LottieConstants.IterateForever) { // rememberLottieComposition确保资源仅加载一次 val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(lottie)) // 用animateLottieCompositionAsState统一控制播放状态,避免冗余逻辑 val progress by animateLottieCompositionAsState( composition = composition, isPlaying = play, speed = speed, iterations = iterations, restartOnPlay = true ) LottieAnimation( composition = composition, progress = { progress }, modifier = modifier ) }
修改说明
- 关闭预加载:通过
beyondBoundsPageCount = 0禁止Pager提前创建非可见页面的Composable,从源头减少组件实例。 - 稳定过渡状态:用
remember保存MutableTransitionState,避免每次重组都触发动画重新执行。 - 简化Lottie逻辑:移除冗余的
LottieAnimatable,仅通过animateLottieCompositionAsState控制播放,确保资源只加载一次,动画仅在页面可见时运行。
内容的提问来源于stack exchange,提问作者c-an
相关产品推荐
相关产品推荐

