Jetpack Compose 1.9: 核心新特性简介

2,247 阅读6分钟

Jetpack Compose 2025 年 8 月版本的新功能


Jetpack Compose

近期Jetpack Compose 2025 年 8 月版本 已正式发布. 本次发布包含 Compose 1.9 版本的核心模块(详见完整的BOM), 引入了用于渲染阴影, 2D 滚动, 文本变换相关的富样式, 改进的列表性能等新 API!

要想使用本次发布的版本的话, 你可以将自己项目的Compose BOM版本升级至2025.08.00:

implementation(platform("androidx.compose:compose-bom:2025.08.00"))

阴影

很高兴 Google 官方推出两个备受期待的Modifier: Modifier.dropShadow()Modifier.innerShadow(), 允许你渲染和阴影效果(与现有的 Modifier.shadow() 不同, 后者基于照明模型且渲染基于高度的阴影).

Modifier.dropShadow()

Modifier.dropShadow() 在内容后面绘制阴影. 你可以将其添加到@Composable链中, 并指定半径, 颜色和扩散范围. 请注意, 应显示在阴影上方的内容(如背景)应在 Modifier.dropShadow() 之后渲染.

@Composable
@Preview(showBackground = true)
fun SimpleDropShadowUsage() {
    val pinkColor = Color(0xFFe91e63)
    val purpleColor = Color(0xFF9c27b0)
    Box(Modifier.fillMaxSize()) {
        Box(
            Modifier
                .size(200.dp)
                .align(Alignment.Center)
                .dropShadow(
                    RoundedCornerShape(20.dp),
                    dropShadow = DropShadow(
                        15.dp,
                        color = pinkColor,
                        spread = 10.dp,
                        alpha = 0.5f
                    )
                )
                .background(
                    purpleColor,
                    shape = RoundedCornerShape(20.dp)
                )
        )
    }
}

drop shadow drawn all around shape

图1. Shape周围绘制的阴影

Modifier.innerShadow()

Modifier.innerShadow() 在提供的Shape的内侧绘制阴影:

@Composable
@Preview(showBackground = true)
fun SimpleInnerShadowUsage() {
    val pinkColor = Color(0xFFe91e63)
    val purpleColor = Color(0xFF9c27b0)
    Box(Modifier.fillMaxSize()) {
        Box(
            Modifier
                .size(200.dp)
                .align(Alignment.Center)
                .background(
                    purpleColor,
                    shape = RoundedCornerShape(20.dp)
                )
                .innerShadow(
                    RoundedCornerShape(20.dp),
                    innerShadow = InnerShadow(
                        15.dp,
                        color = Color.Black,
                        spread = 10.dp,
                        alpha = 0.5f
                    )
                )
        )
    }
}

modifier.innerShadow applied to a shape

图2. 应用于ShapeModifier.innerShadow()

内阴影的顺序非常重要. 内阴影绘制在内容之上, 因此在上面的示例中, Google 官方需要将Modifier.innerShadow()Modifier.background()之后. 当将其应用于Image等元素时, Google 官方需要采取类似的操作. 在此示例中, Google 官方放置了一个单独的 Box 来在Image上方渲染阴影:

@Composable
@Preview(showBackground = true)
fun PhotoInnerShadowExample() {
    Box(Modifier.fillMaxSize()) {
        val shape = RoundedCornerShape(20.dp)
        Box(
            Modifier
                .size(200.dp)
                .align(Alignment.Center)
        ) {
            Image(
                painter = painterResource(id = R.drawable.cape_town),
                contentDescription = "Image with Inner Shadow",
                contentScale = ContentScale.Crop,
                modifier = Modifier.fillMaxSize()
                    .clip(shape)
            )
            Box(
                modifier = Modifier.fillMaxSize()
                    .innerShadow(
                        shape,
                        innerShadow = InnerShadow(15.dp,
                            spread = 15.dp)
                    )
            )
        }
    }
}

Inner shadow on top of an image

图3. Image上的内阴影

新增可见性相关的 Modifier

Compose UI 1.8 引入了 onLayoutRectChanged, 这是一种高效的追踪屏幕上元素位置的新方式. Google 官方基于此 API 引入 onVisibilityChangedonFirstVisible 以支持常见用例. 这些 API 接受可选参数, 用于指定在触发操作前元素可见的最小比例或时间长度.

使用 onVisibilityChanged 处理基于可见性触发的 UI 变化或副作用, 例如自动播放/暂停视频或启动动画:

LazyColumn {
  items(feedData) { video ->
    VideoRow(
        video,
        Modifier.onVisibilityChanged(minDurationMs = 500, minFractionVisible = 1f) {
          visible ->
            if (visible) video.play() else video.pause()
          },
    )
  }
}

使用 onFirstVisible 处理元素首次在屏幕上可见时的场景, 例如记录展示次数:

LazyColumn {
    items(100) {
        Box(
            Modifier
                // Log impressions when item has been visible for 500ms
                .onFirstVisible(minDurationMs = 500) { /* log impression */ }
                .clip(RoundedCornerShape(16.dp))
                .drawBehind { drawRect(backgroundColor) }
                .fillMaxWidth()
                .height(100.dp)
        )
    }
}

OutputTransformation 富样式支持

BasicTextField 现在支持在 OutputTransformation 内部添加样式, 如颜色和字体粗细.

新的 TextFieldBuffer.addStyle() 方法允许你应用 SpanStyleParagraphStyle 来更改文本的外观, 而不会更改底层的 TextFieldState. 这对于视觉上格式化输入(如电话号码或信用卡号码)非常有用. 此方法只能在 OutputTransformation 内调用.

// Format a phone number and color the punctuation
val phoneTransformation = OutputTransformation {
    // 1234567890 -> (123) 456-7890
    if (length == 10) {
        insert(0, "(")
        insert(4, ") ")
        insert(9, "-")

        // Color the added punctuation
        val gray = Color(0xFF666666)
        addStyle(SpanStyle(color = gray), 0, 1)
        addStyle(SpanStyle(color = gray), 4, 5)
        addStyle(SpanStyle(color = gray), 9, 10)
    }
}

BasicTextField(
    state = myTextFieldState,
    outputTransformation = phoneTransformation
)

LazyLayout

LazyLayout 的所有构建块现已稳定! 查看 LazyLayoutMeasurePolicy, LazyLayoutItemProviderLazyLayoutPrefetchState 以构建你自己的 Lazy 组件.

预加载改进

随着新预加载行为的引入, Lazy ListLazy Grid 的滚动性能有了显著提升. 你现在可以定义一个 LazyLayoutCacheWindow 来预加载更多内容. 默认情况下, 仅在滚动方向上提前渲染一个项目, 且当项目滚出屏幕后会被丢弃. 你现在可以通过viewport大小或dp大小的分数来自定义预加载的前向项目数量和保留的后向项目数量. 当你选择使用LazyLayoutCacheWindow时, 项目会立即开始在前向区域进行预加载.

此配置的入口点在LazyListState中, 它接受缓存窗口大小:

@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun LazyColumnCacheWindowDemo() {
    // Prefetch items 150.dp ahead and retain items 100.dp behind the visible viewport
    val dpCacheWindow = LazyLayoutCacheWindow(ahead = 150.dp, behind = 100.dp)
    // Alternatively, prefetch/retain items as a fraction of the list size
    // val fractionCacheWindow = LazyLayoutCacheWindow(aheadFraction = 1f, behindFraction = 0.5f)
    val state = rememberLazyListState(cacheWindow = dpCacheWindow)
    LazyColumn(state = state) {
        items(1000) { Text(text = "$it", fontSize = 80.sp) }
    }
}

lazylayout in Compose 1.9 release

注意: 预加载会渲染比当前可见更多项——新缓存窗口 API 可能增加预加载量. 这意味着项的 LaunchedEffectDisposableEffect 可能更早执行——请勿将其作为可见性信号(例如用于展示栈迹). 相反, Google 官方建议使用新的 onFirstVisibleonVisibilityChanged API. 即使你目前未手动自定义 LazyLayoutCacheWindow, 也应避免将@Composable效果作为内容可见性的信号, 因为此新预加载机制将在未来版本中默认启用.

滚动

2D 滚动 API

在发布 Draggable2D 之后, Scrollable2D 现已推出, 为 Compose 带来了二维滚动功能. 虽然现有的 Scrollable Modifier支持单向滚动, 但 Scrollable2D 同时支持二维滚动和滑动操作. 这使你能够创建在所有方向上移动的更复杂布局, 例如电子表格或图片查看器. 嵌套滚动也得到支持, 以适应 2D 场景.

val offset = remember { mutableStateOf(Offset.Zero) }
Box(
    Modifier.size(150.dp)
        .scrollable2D(
            state =
                rememberScrollable2DState { delta ->
                    offset.value = offset.value + delta // update the state
                    delta // indicate that we consumed all the pixels available
                }
        )
        .background(Color.LightGray),
    contentAlignment = Alignment.Center,
) {
    Text(
        "X=${offset.value.x.roundToInt()} Y=${offset.value.y.roundToInt()}",
        style = TextStyle(fontSize = 32.sp),
    )
}

moving image of 2D scroll API demo

滚动互操作性改进

为提升与 View 的滚动和嵌套滚动互操作性, 本次更新包含以下 bug 修复和新功能:

  • 现可使用 ViewTreeObserver#onScrollChangeListener 监听 Compose 滚动事件.
  • 修复了 Compose 和 View 之间在滑动动画过程中错误的速度分发的问题.
  • Compose 现在会以正确顺序调用视图的嵌套滚动回调.
  • AndroidView 中嵌套 NestedScrollView 时, 嵌套滚动功能得到正确支持.

通过在堆栈栈迹中添加源信息改进crash分析

Google 官方了解到, 当你的代码未出现在堆栈栈迹中时, 调试 Compose crash可能较为困难. 为解决此问题, Google 官方提供了一个新的可选 API, 用于提供更丰富的crash位置详细信息, 包括@Composable的名称和位置, 使你能够:

  • 高效地识别并解决crash源.
  • 更轻松地隔离crash以生成可重复的示例.
  • 调查之前仅显示内部堆栈帧的crash.

请注意, Google 官方不建议在发布构建中使用此 API, 因为收集这些额外信息会影响性能, 且它在压缩 APK 中也不起作用.

要启用此功能, 请在应用程序入口点添加以下代码行. 理想情况下, 应在创建任何@Composable之前进行此配置, 以确保堆栈栈迹信息被收集:

class App : Application() {
   override fun onCreate() {
        // Enable only for debug flavor to avoid perf regressions in release
        Composer.setDiagnosticStackTraceEnabled(BuildConfig.DEBUG)
   }
}

新注解和 Lint 检查

Google 官方引入了一个新的运行时注解库, 该库暴露了编译器和工具(如 Lint 检查)使用的注解. 这使得非 Compose 模块可以使用这些注解, 而无需依赖 Compose 运行时库. @Stable, @Immutable@StableMarker 注解已移至 runtime-annotation, 允许你对不依赖 Compose 的类和函数进行注解.

此外, Google 官方新增了两个注解及对应的代码检查:

  • @RememberInComposition: 用于标记构造函数, 函数和属性获取器的注解, 指示这些元素不得在@Composable内部直接调用, 否则将引发代码检查错误.
  • @FrequentlyChangingValue: 一个用于标记函数和属性获取器的注解, 用于指示它们不应在@Composable内部直接调用, 因为这可能导致频繁的重新组合(例如, 标记滚动位置值和动画值). 相应的 lint 检查将提供警告.

附加更新

  • 为简化兼容性并提升 lint 检查支持的稳定性, Compose 现要求使用 Android Gradle Plugin(AGP)和Lint 的 8.8.2 或更高版本. 请查阅新文档以获取更多信息.
  • 添加了两个用于ContextMenu的新 API:
  • Modifier.appendTextContextMenuComponents(): 向ContextMenu添加新项.
  • Modifier.filterTextContextMenuComponents(): 从ContextMenu中移除项.

好吧, 今天的内容就分享到这里吧!

一家之言, 欢迎拍砖!

Happy Coding! Stay GOLDEN!