Jetpack Compose中LazyColumn在三星设备滚动卡顿的优化咨询
Jetpack Compose LazyColumn在Samsung设备滚动卡顿优化方案
问题说明
使用Jetpack Compose的LazyColumn组件时,在Pixel、OnePlus设备上滚动流畅,但在Samsung设备上出现明显卡顿,已通过示例项目复现该问题。
核心代码
SeeAllView.kt
@Composable fun SeeAllView( viewModel: MainActivityViewModel, handleItemSelection: (TestDataModel) -> Unit, ctaAction: (CurrentModel) -> Unit, ) { LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(20.dp), ) { items(viewModel.itemList, key = { it.index }) { item -> ListContent(false, item, ctaAction, handleItemSelection) } } } @Composable fun ListContent( useSpaceView: Boolean = true, myTestDataItem: TestDataModel, ctaAction: (CurrentModel) -> Unit, handleItemSelection: (TestDataModel) -> Unit, ) { val name = myTestDataItem.name val image = myTestDataItem.iconUrl val summary = myTestDataItem.summary val currentModel = myTestDataItem.eventModel.currentModel AnimatedVisibility(visible = useSpaceView) { Spacer(Modifier.height(20.dp)) } ListItem(name, image, summary, currentModel, ctaAction) { handleItemSelection(myTestDataItem) } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun ListItem( name: String, image: Int, summary: String, currentModel: CurrentModel, ctaAction: (CurrentModel) -> Unit, openBottomSheet: () -> Unit ) { Card( modifier = Modifier .fillMaxSize(), colors = CardDefaults.cardColors(containerColor = Color.White), shape = RoundedCornerShape(20.dp), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), onClick = { openBottomSheet() }, ) { Column( modifier = Modifier.padding(20.dp) ) { PanelHeaderView(name, image) SummaryView(summary) CtaView( modifier = Modifier .align(Alignment.End), currentModel, ctaAction, ) } } } @Composable private fun PanelHeaderView(testName: String, testImage: Int) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { Image( modifier = Modifier.weight(0.1f), painter = painterResource(testImage), contentDescription = null ) AnimatedVisibility( visible = testName.isNotEmpty(), modifier = Modifier .weight(0.8f) .padding(start = 20.dp), ) { Text( text = testName, maxLines = 1, overflow = TextOverflow.Ellipsis ) } Icon( imageVector = Icons.Filled.ArrowBack, contentDescription = null, tint = Color.Black ) } } @Composable private fun SummaryView(summary: String) { AnimatedVisibility(visible = summary.isNotEmpty()) { AndroidView( modifier = Modifier.padding(top = 20.dp), factory = { context -> TextView(context) }, update = { textView -> with(textView) { text = HtmlCompat.fromHtml(summary, HtmlCompat.FROM_HTML_MODE_COMPACT) setTextColor(ContextCompat.getColor(context, R.color.black)) setTextSize(TypedValue.COMPLEX_UNIT_PX, 60F) } } ) } } @Composable private fun CtaView( modifier: Modifier, currentModel: CurrentModel, ctaAction: (CurrentModel) -> Unit, ) { AnimatedVisibility( modifier = modifier, visible = currentModel.showCtaAction ) { ClickableText( modifier = Modifier.padding(top = 20.dp), text = AnnotatedString(currentModel.actionTitle), onClick = { ctaAction(currentModel) } ) } }
依赖配置(gradle.kt)
dependencies { implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.9.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.core:core-ktx:1.10.1' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' def composeBom = platform "androidx.compose:compose-bom:2023.05.01" implementation composeBom androidTestImplementation composeBom implementation "androidx.compose.ui:ui" implementation "androidx.compose.foundation:foundation" implementation "androidx.compose.foundation:foundation-layout" implementation "androidx.compose.material:material" implementation "androidx.compose.material3:material3" implementation "androidx.compose.runtime:runtime" implementation "androidx.compose.runtime:runtime-livedata" implementation "androidx.compose.ui:ui-tooling" implementation "androidx.compose.ui:ui-tooling-preview" implementation "androidx.lifecycle:lifecycle-viewmodel-compose" implementation "androidx.activity:activity-compose:1.7.2" androidTestImplementation "androidx.compose.ui:ui-test" androidTestImplementation "androidx.compose.ui:ui-test-junit4" debugImplementation "androidx.compose.ui:ui-test-manifest" implementation "io.insert-koin:koin-android:3.4.0" implementation "io.insert-koin:koin-androidx-workmanager:3.4.0" implementation "io.insert-koin:koin-androidx-compose:3.4.3" }
MainActivity代码
class MainActivity : AppCompatActivity() { private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) } private val viewModel by viewModel<MainActivityViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setupViewModel() setContentView(binding.root) } private fun setupViewModel() { viewModel.requestCompleteLiveData.observe(this) { dataReturned -> if (dataReturned) { setupView() } } viewModel.createData() } private fun setupView() { binding.composeView.setContent { AppBarScaffold(R.string.app_name, true) { SeeAllView(viewModel, {}, {}) } } } }
优化建议
1. 替换AndroidView为Compose原生Text处理HTML
SummaryView中使用AndroidView包裹TextView处理HTML文本,跨View层的交互会带来额外性能开销,尤其在LazyColumn滚动时频繁创建/更新会加剧卡顿。建议将HTML转成AnnotatedString后直接用Compose原生Text组件:
@Composable private fun SummaryView(summary: String) { AnimatedVisibility(visible = summary.isNotEmpty()) { val annotatedText = remember(summary) { HtmlCompat.fromHtml(summary, HtmlCompat.FROM_HTML_MODE_COMPACT) .let { SpannedString(it) } .toAnnotatedString() } Text( modifier = Modifier.padding(top = 20.dp), text = annotatedText, color = Color.Black, fontSize = 60.sp ) } }
2. 移除不必要的AnimatedVisibility
ListContent中useSpaceView固定传false,对应的AnimatedVisibility完全可以替换成条件判断的Spacer,避免动画组件带来的额外计算:
@Composable fun ListContent( useSpaceView: Boolean = true, myTestDataItem: TestDataModel, ctaAction: (CurrentModel) -> Unit, handleItemSelection: (TestDataModel) -> Unit, ) { val name = myTestDataItem.name val image = myTestDataItem.iconUrl val summary = myTestDataItem.summary val currentModel = myTestDataItem.eventModel.currentModel if (useSpaceView) { Spacer(Modifier.height(20.dp)) } ListItem(name, image, summary, currentModel, ctaAction) { handleItemSelection(myTestDataItem) } }
3. 优化布局稳定性
PanelHeaderView中的Icon未设置权重,会导致Row布局在不同item间重绘,添加Modifier.weight(0.1f)保证布局结构稳定:
Icon( modifier = Modifier.weight(0.1f), imageVector = Icons.Filled.ArrowBack, contentDescription = null, tint = Color.Black )
4. 升级Compose版本
当前使用的Compose BOM版本为2023.05.01,属于较旧版本,很多针对Samsung设备的兼容性和性能修复已在后续版本中推出,建议升级到最新稳定版:
def composeBom = platform "androidx.compose:compose-bom:2024.04.01"
5. 减少不必要的重组
- 给自定义数据类
TestDataModel、CurrentModel添加@Stable或@Immutable注解,帮助Compose识别稳定类型,减少无意义重组。 - 若暂时保留
AndroidView写法,将不变的配置(如文字大小、颜色)移出update回调,放到factory中,避免每次重组重复设置:
AndroidView( modifier = Modifier.padding(top = 20.dp), factory = { context -> TextView(context).apply { setTextColor(ContextCompat.getColor(context, R.color.black)) setTextSize(TypedValue.COMPLEX_UNIT_PX, 60F) } }, update = { textView -> textView.text = HtmlCompat.fromHtml(summary, HtmlCompat.FROM_HTML_MODE_COMPACT) } )
6. 启用性能检查
使用Android Studio的Layout Inspector开启「Show All Recompositions」功能,定位LazyColumn中频繁重组的item,针对性优化重组触发条件。
内容的提问来源于stack exchange,提问作者Compose Learner
相关产品推荐
相关产品推荐

