Jetpack Compose 1.9: 核心新特性简介

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


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

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

less 复制代码
implementation(platform("androidx.compose:compose-bom:2025.08.00"))

阴影

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

Modifier.dropShadow()

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

less 复制代码
@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)
                )
        )
    }
}

图1. Shape周围绘制的阴影

Modifier.innerShadow()

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

less 复制代码
@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
                    )
                )
        )
    }
}

图2. 应用于ShapeModifier.innerShadow()

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

less 复制代码
@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)
                    )
            )
        }
    }
}

图3. Image上的内阴影

新增可见性相关的 Modifier

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

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

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

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

scss 复制代码
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 内调用.

scss 复制代码
// 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中, 它接受缓存窗口大小:

kotlin 复制代码
@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) }
    }
}

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

滚动

2D 滚动 API

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

ini 复制代码
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),
    )
}

滚动互操作性改进

为提升与 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之前进行此配置, 以确保堆栈栈迹信息被收集:

kotlin 复制代码
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!

相关推荐
TeleostNaCl16 小时前
如何安装 Google 通用的驱动以便使用 ADB 和 Fastboot 调试(Bootloader)设备
android·经验分享·adb·android studio·android-studio·android runtime
fatiaozhang952717 小时前
中国移动浪潮云电脑CD1000-系统全分区备份包-可瑞芯微工具刷机-可救砖
android·网络·电脑·电视盒子·刷机固件·机顶盒刷机
2501_9159184118 小时前
iOS 开发全流程实战 基于 uni-app 的 iOS 应用开发、打包、测试与上架流程详解
android·ios·小程序·https·uni-app·iphone·webview
lichong95118 小时前
【混合开发】vue+Android、iPhone、鸿蒙、win、macOS、Linux之dist打包发布在Android工程asserts里
android·vue.js·iphone
Android出海18 小时前
Android 15重磅升级:16KB内存页机制详解与适配指南
android·人工智能·新媒体运营·产品运营·内容运营
一只修仙的猿18 小时前
毕业三年后,我离职了
android·面试
编程乐学19 小时前
安卓非原创--基于Android Studio 实现的新闻App
android·ide·android studio·移动端开发·安卓大作业·新闻app
雅雅姐20 小时前
Android14 init.rc中on boot阶段操作4
android
fatiaozhang952720 小时前
中国移动中兴云电脑W132D-RK3528-2+32G-刷机固件包(非原机制作)
android·xml·电脑·电视盒子·刷机固件·机顶盒刷机
Android出海1 天前
Google Play账户与App突遭封禁?紧急应对与快速重构上架策略
android·网络·重构·新媒体运营·产品运营·内容运营