产品新闻

Jetpack Compose 2025 年 12 月版的最新动态

阅读用时:6 分钟
Nick Butcher
产品经理

今天,Jetpack Compose 2025 年 12 月版已发布稳定版。此版本包含核心 Compose 模块的 1.10 版和 Material 3 的 1.4 版(请参阅完整的 BOM 映射),并添加了新功能和重大性能改进。

如需使用今天的版本,请将 Compose BOM 版本升级到 2025.12.00

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

性能改进

我们知道,应用的运行时性能对您和您的用户来说非常重要,因此性能一直是 Compose 团队的首要任务。此版本带来了一些改进,您只需升级到最新版本即可获得所有改进。我们的内部滚动基准测试表明,Compose 现在与使用 View 时看到的性能相匹配:

janky.png

滚动性能基准测试,比较了不同 Compose 版本中 View 和 Jetpack Compose 的性能

延迟预提取中的可暂停组合

延迟预提取中的可暂停组合现在默认处于启用状态。这是对 Compose 运行时调度工作方式的根本性更改,旨在显著减少繁重的界面工作负载期间的卡顿。

以前,组合一旦开始,就必须运行到完成。如果组合很复杂,则可能会阻塞主线程的时间超过一帧,导致界面冻结。借助可暂停组合,运行时现在可以在时间不足时“暂停”其工作,并在下一帧中恢复工作。与延迟布局预提取结合使用时,提前准备帧的效果尤其明显。Compose 1.9 中引入的 Lazy 布局 CacheWindow API 是预提取更多内容并受益于可暂停组合以实现更流畅的界面性能的绝佳方式。

pausable.gif

可暂停组合与延迟预提取相结合,有助于减少卡顿

我们还在其他方面优化了性能,改进了 Modifier.onPlacedModifier.onVisibilityChanged 和其他修饰符实现。我们将继续投入资金来提升 Compose 的性能。

新功能

保留

Compose 提供了许多 API,用于在不同的生命周期中保留和管理状态;例如,remember 会在组合之间保留状态,而 rememberSavable/rememberSerializable 会在 activity 或进程重新创建之间保留状态。retain 是一个介于这些 API 之间的新 API,可让您在配置更改之间保留值,而无需序列化,但不会在进程终止时保留值。由于 retain 不会对您的状态进行序列化,因此您可以保留 lambda 表达式、流和大型对象(如位图)等无法轻松序列化的对象。例如,您可以使用 retain 来管理媒体播放器(例如 ExoPlayer),以确保媒体播放不会因配置更改而中断。

  @Composable

fun MediaPlayer() {

    val applicationContext = LocalContext.current.applicationContext

    val exoPlayer = retain { ExoPlayer.Builder(applicationContext).apply { ... }.build() }

    ...

}

我们要感谢 AndroidDev 社区(尤其是 Circuit 团队),他们影响并贡献了此功能的设计。

Material 1.4

material3 库的 1.4.0 版添加了许多新组件和增强功能:

centered-hero-carousel.webp

水平居中 Hero 轮播界面

请注意,Material 3 Expressive API 将继续在 material3 库的 Alpha 版中开发。如需了解详情,请观看最近的这次演讲:

新的动画功能

我们将继续扩展动画 API,包括用于自定义共享元素动画的更新。

动态共享元素

默认情况下,sharedElement()sharedBounds() 动画会在目标状态中找到匹配的键时尝试为

布局更改添加动画效果。不过,您可能希望根据某些条件(例如导航方向或当前界面状态)动态停用此动画。

如需控制是否发生共享元素转换,您现在可以自定义传递给 rememberSharedContentState()SharedContentConfigisEnabled 属性用于确定共享元素是否处于活跃状态。

  SharedTransitionLayout {

        val transition = updateTransition(currentState)

        transition.AnimatedContent { targetState ->

            // Create the configuration that depends on state changing.

            fun animationConfig() : SharedTransitionScope.SharedContentConfig {

                return object : SharedTransitionScope.SharedContentConfig {

                    override val SharedTransitionScope.SharedContentState.isEnabled: Boolean

                        get() =

                            // determine whether to perform a shared element transition

                }

            }

}

如需了解详情,请参阅文档

Modifier.skipToLookaheadPosition()

此版本中添加了一个新的修饰符 Modifier.skipToLookaheadPosition(),该修饰符会在执行共享元素动画时保留可组合项的最终位置。这样就可以执行“显示”类型动画等转换,如 Androidify 示例中逐步显示相机所示。如需了解详情,请观看此处的视频提示:

共享元素转换中的初始速度

此版本添加了一个新的共享元素转换 API prepareTransitionWithInitialVelocity,可让您将初始速度(例如来自手势)传递给共享元素转换:

  Modifier.fillMaxSize()

    .draggable2D(

        rememberDraggable2DState { offset += it },

        onDragStopped = { velocity ->

            // Set up the initial velocity for the upcoming shared element

            // transition.

            sharedContentStateForDraggableCat

                ?.prepareTransitionWithInitialVelocity(velocity)

            showDetails = false

        },

    )
fling-shared.gif

以手势的初始速度开始的共享元素转换

遮盖转换

EnterTransitionExitTransition 用于定义 AnimatedVisibility/AnimatedContent 可组合项的显示或消失方式。借助新的实验性遮盖选项,您可以指定颜色来遮盖或遮挡内容;例如,在内容上淡入/淡出一个半透明的黑色图层:

veil_2.gif

遮盖动画内容 - 请注意动画期间网格内容上的半透明遮盖(或遮挡)

  AnimatedContent(

    targetState = page,

    modifier = Modifier.fillMaxSize().weight(1f),

    transitionSpec = {

        if (targetState > initialState) {

            (slideInHorizontally { it } togetherWith

                    slideOutHorizontally { -it / 2 } + veilOut(targetColor = veilColor))

        } else {

            slideInHorizontally { -it / 2 } +

                    unveilIn(initialColor = veilColor) togetherWith slideOutHorizontally { it }

        }

    },

) { targetPage ->

    ...

}

即将发生的更改

废弃 Modifier.onFirstVisible

Compose 1.9 引入了 Modifier.onVisibilityChanged 和 Modifier.onFirstVisible。在查看您的反馈后,我们发现无法确定性地遵守 Modifier.onFirstVisible 的合约;具体来说,当某个项首次 变为可见时。例如,Lazy 布局可能会处置滚动出视口的项,然后在这些项滚动回视图时再次组合它们。在这种情况下,onFirstVisible 回调将再次触发,因为它是一个新组合的项。当导航回包含 onFirstVisible 的先前访问过的界面时,也会发生类似的行为。因此,我们决定在下一个 Compose 版本 (1.11) 中废弃此修饰符,并建议迁移到 onVisibilityChanged。如需了解详情,请参阅文档

测试中的协程调度

我们计划更改测试中的协程调度,以减少测试的不稳定性并捕获更多问题。目前,测试使用 UnconfinedTestDispatcher,这与生产行为不同;例如,效果可能会立即运行,而不是排队。在未来的版本中,我们计划引入一个默认使用 StandardTestDispatcher 的新 API,以匹配生产行为。您现在 可以在 1.10 中试用新行为:

  @get:Rule // also createAndroidComposeRule, createEmptyComposeRule

val rule = createComposeRule(effectContext = StandardTestDispatcher())

使用 StandardTestDispatcher 会将任务排队,因此您必须使用同步机制,例如 composeTestRule.waitForIdle()composeTestRule.runOnIdle()。如果您的测试使用 runTest,则必须确保 runTest 和您的 Compose 规则共享同一个 StandardTestDispatcher 实例以进行同步。

  // 1. Create a SINGLE dispatcher instance

val testDispatcher = StandardTestDispatcher()



// 2. Pass it to your Compose rule

@get:Rule

val composeRule = createComposeRule(effectContext = testDispatcher)



@Test

// 3. Pass the *SAME INSTANCE* to runTest

fun myTest() = runTest(testDispatcher) {

    composeRule.setContent { /* ... */ }

}

工具

出色的 API 值得拥有出色的工具,Android Studio 最近为 Compose 开发者添加了一些工具:

  • 转换界面:右键点击 @Preview,选择“转换界面”,然后用自然语言描述更改,即可迭代设计。
  • 生成 @Preview:右键点击可组合项,然后选择 Gemini > 生成 [可组合项名称] 预览
  • 自定义 Material Symbols:在 Vector Asset 向导中新增了对图标变体的支持。
  • 从屏幕截图生成代码,或问问 Gemini 将现有界面与目标图片进行匹配。这可以与远程 MCP 支持 相结合,例如连接到 Figma 文件并从设计生成 Compose 界面。
  • 修复界面质量问题 :审核界面是否存在常见问题(例如无障碍功能问题),然后提出修复建议。

如需了解这些工具的实际应用,请观看最近的演示:

祝您编写愉快

我们将继续投资 Jetpack Compose,为您提供创建精美、丰富的界面所需的 API 和工具。我们重视您的意见,因此请在我们的问题跟踪器中分享您对这些更改的反馈,或您希望接下来看到的内容。

继续阅读