You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 04:52:03