產品新訊

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 現在的效能與使用 Views 時的效能相同:

janky.png

捲動效能基準,比較不同 Compose 版本中 Views 和 Jetpack Compose 的效能

延遲預先擷取時可暫停組合

系統現在預設啟用延遲預先擷取中的可暫停組合。這項重大變更會影響 Compose 執行階段排程的工作方式,目的是大幅減少高負載 UI 工作負載期間的卡頓。

先前,組合一旦開始,就必須執行完畢。如果組合複雜,可能會封鎖主執行緒的時間超過單一影格,導致 UI 凍結。有了可暫停的組合,如果執行階段時間不足,現在可以「暫停」工作,並在下一個影格繼續工作。如果搭配使用延遲版面配置預先擷取功能,提早準備影格,效果會特別顯著。Compose 1.9 推出的 Lazy 版面配置 CacheWindow API,可預先擷取更多內容,並利用可暫停的組合功能,大幅提升 UI 效能。

pausable.gif

可暫停的組合搭配 Lazy prefetch,有助於減少卡頓

我們也改善了 Modifier.onPlacedModifier.onVisibilityChanged 和其他修飾符的實作方式,進一步提升效能。我們會持續投入資源,提升 Compose 的效能。

新功能

保留

Compose 提供多種 API,可跨不同生命週期保留及管理狀態;舉例來說,remember 會在重組時保留狀態,而 rememberSavable/rememberSerializable 則會在重新建立活動或程序時保留狀態。retain 是介於這些 API 之間的新 API,可讓您在設定變更時保留值,不必序列化,但不會跨程序終止。由於 retain 不會序列化狀態,因此您可以保存 lambda 運算式、Flow 和點陣圖等大型物件,這些物件不容易序列化。舉例來說,您可以使用 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

水平置中主頁橫幅輪轉介面

請注意,Material 3 Expressive API 仍會在 material3 程式庫的 Alpha 版中開發。詳情請參閱這場最近的演講:

全新動畫功能

我們持續擴充動畫 API,包括更新共用元素動畫的自訂功能。

動態共用元素

根據預設,sharedElement()sharedBounds() 動畫會嘗試為

只要在目標狀態中找到相符的鍵,版面配置就會變更。不過,您可能會想根據特定條件 (例如導覽方向或目前的 UI 狀態),動態停用這項動畫。

如要控管是否發生共用元素轉場效果,現在可以自訂傳遞至 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.onVisibilityChangedModifier.onFirstVisible。審查您的意見回饋後,我們發現無法確定是否能遵守 Modifier.onFirstVisible 的合約,具體來說,就是無法確定項目首次顯示的時間。舉例來說,如果項目捲動到檢視區塊外,Lazy 版面配置可能會捨棄這些項目,然後在項目捲動回檢視畫面時再次組合。在這種情況下,由於這是新組成的項目,因此 onFirstVisible 回呼會再次觸發。返回含有 onFirstVisible 的先前瀏覽畫面時,也會發生類似行為。因此,我們決定在下一個 Compose 版本 (1.11) 中淘汰這個修飾符,並建議遷移至 onVisibilityChanged。詳情請參閱說明文件

測試中的協同程式調度

我們計畫變更測試中的協同程式調度,以減少測試不穩定性並找出更多問題。目前測試使用的是 UnconfinedTestDispatcher,這與實際運作行為不同,例如 effects 可能會立即執行,而不是加入佇列。我們計畫在日後發布新版 API,預設使用 StandardTestDispatcher 來比對正式環境的行為。您現在可以在 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 開發人員新增了多項功能:

如要查看這些工具的實際運作情形,請觀看這場近期的示範:

祝您編寫愉快!

我們將持續投入 Jetpack Compose,為您提供建立精美豐富 UI 所需的 API 和工具。我們很重視您的想法,歡迎在問題追蹤工具中分享對這些異動的意見,或提出您希望我們接下來推出的功能。

繼續閱讀