Jetpack Compose 1.11 正式版發布,下一代的全新元件和樣式 API,你一定要知道

**我覺得這是 Jetpack Compose 今年更新最有意義的一個版本了,沒有之一**,因為它提供了一套全新的路線方向,同時也補齊了 Compose 三個長期短板:
  • 元件樣式能力太依賴 modifier 和參數堆疊
  • 複雜自適應版面缺少真正的二維 Grid 和標準 Flexbox
  • runtime 對高頻隨機結構編輯的底層支援不夠理想

雖然都是實驗性質,但是也明確了 Jetpack Compose 接下來的方向,這也是為什麼今年還沒過,我就確定它是最有意義的版本,因為這些 API 就算走到穩定版,今年也夠嗆了:

Styles、MediaQuery、Grid、FlexBox 和新的 SlotTable 實作,都能看出 Google 正在把 Compose 從目前的寫法推向下一代寫法

Styles

首先就是 Jetpack Compose 1.11 這次最有意思的更新,推出了全新的 experimental 基礎 Styles API,它主要代表了一種新的 styling 範式。以前很多自訂都靠 modifiers 來完成,而現在這部分能力被抽象成 Style API:

<div><div><div></div><span>scss</span></div><div><div> <span>體驗 AI 程式碼助理</span></div><div> <span>程式碼解讀</span></div><div>複製程式碼</div></div></div>```
<span><span>@Composable</span></span>
<span>fun LoginButton(<span>modifier</span>: Modifier = Modifier) {</span>
<span>    <span>Button</span>(</span>
<span>        onClick = {</span>
<span>            // Login logic</span>
<span>        },</span>
<span>        modifier = modifier,</span>
<span>        style = {</span>
<span>            background(</span>
<span>                Brush.linearGradient(</span>
<span>                    listOf(lightPurple, lightBlue)</span>
<span>                )</span>
<span>            )</span>
<span>            <span>width</span>(<span>75</span>.dp)</span>
<span>            <span>height</span>(<span>50</span>.dp)</span>
<span>            <span>textAlign</span>(TextAlign.Center)</span>
<span>            <span>externalPadding</span>(<span>16</span>.dp)</span>
<span>​</span>
<span>            pressed {</span>
<span>                <span>background</span>(</span>
<span>                    Brush.linearGradient(</span>
<span>                        listOf(Color.Magenta, Color.Red)</span>
<span>                    )</span>
<span>                )</span>
<span>            }</span>
<span>        }</span>
<span>    ){</span>
<span>        Text(</span>
<span>            text = "Login",</span>
<span>        )</span>
<span>    }</span>
<span>}</span>

![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/fa60537bcf91452ba01933057117f4dc~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&x-expires=1777596369&x-signature=i%2B60%2FGCf6X3btk2SsV%2FcdDSzmtc%3D)

在這段程式碼裡用的是 `style = {}` 而不是 modifier,**它代表了一個「樣式宣告 DSL」,專門描述元件的視覺規則和狀態規則**。如果在之前版本,你大概要:

<div><div><div></div><span>ini</span></div><div><div> <span>體驗 AI 程式碼助理</span></div><div> <span>程式碼解讀</span></div><div>複製程式碼</div></div></div>```
<span>val <span>interaction</span> = remember { MutableInteractionSource() }</span>
<span>val isPressed by interaction.collectIsPressedAsState()</span>
<span>​</span>
<span>val <span>backgroundBrush</span> = if (isPressed) {</span>
<span>    Brush.linearGradient(listOf(Color.Magenta, Color.Red))</span>
<span>} else {</span>
<span>    Brush.linearGradient(listOf(lightPurple, lightBlue))</span>
<span>}</span>
<span>​</span>
<span>Button(</span>
<span>    <span>onClick</span> = {},</span>
<span>    <span>interactionSource</span> = interaction,</span>
<span>    <span>modifier</span> = modifier</span>
<span>       .padding(16.dp)</span>
<span>       .width(75.dp)</span>
<span>       .height(50.dp)</span>
<span>       .background(backgroundBrush)</span>
<span>) {</span>
<span>    Text("Login", <span>textAlign</span> = TextAlign.Center)</span>
<span>}</span>

可以看到,Styles 簡潔很多,它把「視覺參數」從 modifier 抽離出來,以前的 backgroundexternalPaddingwidth 之類都是 modifier,現在全部變成元件內部的 style 語意

最重要的還有狀態邏輯被內建成 DSL。以前你要自己管理的一些 state、手動 collect interaction、手動寫 if 和手動處理動畫等場景,現在 pressed {} 是宣告式狀態樣式,可以讓 runtime 自己處理狀態變化,自動做 diff / animation:

<div><div><div></div><span>scss</span></div><div><div> <span>體驗 AI 程式碼助理</span></div><div> <span>程式碼解讀</span></div><div>複製程式碼</div></div></div>```
<span><span>///現在</span></span>
<span>pressed {</span>
<span>    <span>background</span>(...)</span>
<span>}</span>
<span>​</span>
<span><span>///以前</span></span>
<span>if (isPressed) {</span>
<span>    <span>background</span> = ...</span>
<span>}</span>

另外還有一點,官方說 Styles 在 Draw / Layout phase,跳過 Composition,也就是 `pressed { background(...) }` 不會像 `animateColorAsState` 那樣觸發重組,這對效能來說非常明顯。

> 雖然官方強調,**Styles 不是 modifier 的完全替代品**,但是看起來它就很吸引人。

可以看到,Styles 直接解決掉了 Compose 過去自訂裡的幾個問題:

- **參數爆炸**:元件需要堆疊很多情境 background、padding、shape、border、containerColor、contentColor ····
- **狀態樣式不和諧**:在 Compose 裡做 hover、focus、pressed、checked 這類狀態樣式,通常要自己拉 interaction/source、collect state、再配合 `animate*AsState`
- **動畫和重組耦合太深**:顏色、陰影、尺寸狀態切換很多時候依賴 `animateColorAsState` / `animateDpAsState`

**所以 Styles 這個方向等於讓 Compose 進行了一次全新的職責重新切分**,其實也可以猜測,官方說的,Styles 能讓「custom design system」更容易建構的意思,其實就是:

> 它在為 Material 之外的設計系統鋪路。

過去 Compose 其實一直缺少這一層,所以很多事情被迫塞進 modifier 或參數裡,現在 Styles 讓這層抽象真正出現了,官方甚至直接說它「類似 CSS styles」:

![](https://i.imgur.com/rxNWEjR.jpeg)

Grid/FlexBox
------------

### Grid

`Grid` 是 Jetpack Compose 這次提供,用來建構複雜二維版面的新 API,相比起 `Column` 和 `Row`,`Grid` 能提供更好的效能。

`Grid` 不是為了展示大量 homogeneous data 的 lazy list/grid,而是為了 **screen-level architecture 和 complex component 的 structural layout**。官方明確說了,`LazyVerticalGrid` 這類現有 API 主要用於大規模資料展示,不適合做畫面結構版面,但如果用 `Row` + `Column` 硬拼二維版面會導致層級深、適配困難。

所以這其實也是 Compose 過去的一個痛點,過去你要嘛嵌套很多 `Row/Column/Box`,要嘛自訂 layout 或者用 `LazyGrid`,但是現在你有了 Grid API,它支援:

- tracks
- gaps
- cells
- row/column span
- 自動放置與顯式 span
- `Dp`、百分比、intrinsic、`Fr` 單位等 sizing 模型

<div><div><div></div><span>scss</span></div><div><div> <span>體驗 AI 程式碼助理</span></div><div> <span>程式碼解讀</span></div><div>複製程式碼</div></div></div>```
span><span>@OptIn</span(<span>ExperimentalGridApi</span>::class)</span>
span><span>@Composable</span</span>
<span>fun GridExample() {</span>
<span>    <span>Grid</span>(</span>
<span>        config = {</span>
<span>            repeat(<span>4</span>) { <span>column</span>(<span>0.25</span>f) }</span>
<span>            repeat(<span>2</span>) { <span>row</span>(<span>0.5</span>f) }</span>
<span>            <span>gap</span>(<span>16</span>.dp)</span>
<span>       }</span>
<span>   ) {</span>
<span>        <span>Card1</span>(modifier = Modifier.gridItem(rowSpan = <span>2</span>))</span>
<span>        <span>Card2</span>(modifier = Modifier.gridItem(columnSpan = <span>3</span>))</span>
<span>        <span>Card3</span>(modifier = Modifier.gridItem(columnSpan = <span>2</span>))</span>
<span>        <span>Card4</span>()</span>
<span>   }</span>
<span>}</span>

Grid

也就是它會自動處理畫面排列,也可以顯式地處理跨越多行和多列,實現更精確的版面,最重要的是,它具有高度自適應,可以動態重新配置網格軌道和跨度,從而讓 UI 回應裝置狀態(桌面模式或方向變化)。

<div><div><div></div><span>scss</span></div><div><div> <span>體驗 AI 程式碼助理</span></div><div> <span>程式碼解讀</span></div><div>複製程式碼</div></div></div>```
<span><span>Grid</span>(</span>
<span>    config = {</span>
<span>        repeat(<span>3</span>) {</span>
<span>            <span>column</span>(<span>160</span>.dp)</span>
<span>        }</span>
<span>        repeat(<span>3</span>) {</span>
<span>            <span>row</span>(<span>90</span>.dp)</span>
<span>        }</span>
<span>        <span>rowGap</span>(<span>8</span>.dp)</span>
<span>        <span>columnGap</span>(<span>8</span>.dp)</span>
<span>    }</span>
<span>) {</span>
<span>    <span>Card1</span>(modifier = Modifier.gridItem(rowSpan = <span>2</span>, columnSpan = <span>2</span>))</span>
<span>    <span>Card2</span>()</span>
<span>    <span>Card3</span>()</span>
<span>    <span>Card4</span>(modifier = Modifier.gridItem(columnSpan = <span>3</span>))</span>
<span>}</span>
<span>​</span>

![](https://i.imgur.com/lxkvpPs.jpeg)

> 所以,這是一個天然適配摺疊螢幕、大螢幕和小螢幕切換場景的元件。

### FlexBox

`FlexBox` 這個應該更不陌生,作為自適應 UI 的版面容器,它支援根據容器的可用尺寸管理元素的大小和空間分配。

所以它能夠處理類似元素換行( `wrap` )和多軸對齊( `justifyContent`、`alignItems`、`alignContent` )等場景,同時支援元素增大( `grow` )或縮小( `shrink` )來達到填滿容器的效果。

> **這是一個很明顯的 CSS Flexbox 規範,所以它不是 FlowRow 的簡單升級版,而是更標準、更完整的彈性版面模型**。

甚至按照官方的說法,**如果你需要 wrap,那就用 `FlexBox`,而不是 `FlowRow` / `FlowColumn`**:

![](https://i.imgur.com/GFNSYLN.jpeg)

所以,從程式碼上看,`FlowRow`/`FlowColumn` 更像輕量流式版面,而 `FlexBox` 才是面向複雜空間分配、換行、主軸/交叉軸對齊、剩餘空間分配的最佳選擇:

<div><div><div></div><span>scss</span></div><div><div> <span>體驗 AI 程式碼助理</span></div><div> <span>程式碼解讀</span></div><div>複製程式碼</div></div></div>```
span><span>@OptIn</span(<span>ExperimentalFlexBoxApi</span>::class)</span>
<span>fun FlexBoxWrapping(){</span>
<span>    <span>FlexBox</span>(</span>
<span>        config = {</span>
<span>            wrap(FlexWrap.Wrap)</span>
<span>            <span>gap</span>(<span>8</span>.dp)</span>
<span>       }</span>
<span>   ) {</span>
<span>        <span>RedRoundedBox</span>()</span>
<span>        <span>BlueRoundedBox</span>()</span>
<span>        <span>GreenRoundedBox</span>(modifier = Modifier.width(<span>350</span>.dp)<span>.flex</span> { <span>grow</span>(<span>1.0</span>f) })</span>
<span>        <span>OrangeRoundedBox</span>(modifier = Modifier.width(<span>200</span>.dp)<span>.flex</span> { <span>grow</span>(<span>0.7</span>f) })</span>
<span>        <span>PinkRoundedBox</span>(modifier = Modifier.width(<span>200</span>.dp)<span>.flex</span> { <span>grow</span>(<span>0.3</span>f) })</span>
<span>   }</span>
<span>}</span>

那為什麼我要把這兩個東西放在一個章節裡?因為:

  • Grid:二維版面,更適合整體結構
  • FlexBox:一維版面,但具備更強彈性和換行能力
  • 兩者都不支援 lazy loading

所以別把它們當成 Lazy* 家族,它們是另外一層抽象。它們其實是把 Compose 的版面體系補齊,核心是改善了 Compose 在多視窗、大螢幕、桌面化、摺疊態下的效能和能力:

  • Row / Column / Box:基礎骨架
  • LazyColumn / LazyRow / LazyGrid:大資料容器
  • FlowRow / FlowColumn:輕量流式
  • FlexBox:標準一維彈性版面
  • Grid:標準二維結構版面

MediaQuery

也是實驗性 API,這個長得和 Flutter 一樣的 API,支援各種環境訊號(從鍵盤類型和指標精度等裝置功能,到視窗大小和姿勢等上下文狀態),派生媒體查詢 derivedMediaQuery 也內建了高效能功能,支援處理高頻更新。

比如以前要存取某些裝置屬性(例如裝置是否處於桌面模式),需要寫大量樣板程式:

<div><div><div></div><span>kotlin</span></div><div><div> <span>體驗 AI 程式碼助理</span></div><div> <span>程式碼解讀</span></div><div>複製程式碼</div></div></div>```
<span><span>@Composable</span></span>
<span><span><span>fun</span> <span>isTabletopPosture</span><span>(</span></span></span>
<span>    context: <span>Context</span> = LocalContext.current</span>
<span>): <span>Boolean</span> {</span>
<span>    <span>val</span> windowLayoutInfo <span>by</span></span>
<span>        WindowInfoTracker</span>
<span>            .getOrCreate(context)</span>
<span>            .windowLayoutInfo(context)</span>
<span>            .collectAsStateWithLifecycle(<span>null</span>)</span>
<span>​</span>
<span>    <span>return</span> windowLayoutInfo.displayFeatures.any { displayFeature -></span>
<span>        displayFeature <span>is</span> FoldingFeature &&</span>
<span>            displayFeature.state == FoldingFeature.State.HALF_OPENED &&</span>
<span>            displayFeature.orientation == FoldingFeature.Orientation.HORIZONTAL</span>
<span>    }</span>
<span>}</span>
<span>​</span>
<span><span>@Composable</span></span>
<span><span><span>fun</span> <span>VideoPlayer</span><span>()</span></span> {</span>
<span>    <span>if</span>(isTabletopPosture()) {</span>
<span>        TabletopLayout()</span>
<span>    } <span>else</span> {</span>
<span>        FlatLayout()</span>
<span>    }</span>
<span>}</span>

現在,借助 `UIMediaQuery` 可以加入 `mediaQuery` 語法來查詢裝置屬性,例如裝置是否處於桌面模式:

<div><div><div></div><span>less</span></div><div><div> <span>體驗 AI 程式碼助理</span></div><div> <span>程式碼解讀</span></div><div>複製程式碼</div></div></div>```
span><span>@OptIn</span(<span>ExperimentalMediaQueryApi</span>::class)</span>
span><span>@Composable</span</span>
<span>fun <span>VideoPlayer</span>() {</span>
<span>    <span>if</span> (mediaQuery { <span>windowPosture</span> == <span>UiMediaScope</span><span>.Posture</span><span>.Tabletop</span> }) {</span>
<span>        <span>TabletopLayout</span>()</span>
<span>   } <span>else</span> {</span>
<span>        <span>FlatLayout</span>()</span>
<span>   }</span>
<span>}</span>

對比前面幾個屬於 UI 表達,那麼 MediaQuery 就是輸入條件(環境),MediaQuery 解決的是 UI 應該「選哪種形態」的問題,解決的是過去 Compose 感知環境成本相對較高和碎片化的問題。

所以,它是一個統一環境入口的存在,同時也是適配高頻變化效能場景的存在。

Styles、MediaQuery 是呼應的:一個負責「怎麼長得像不同裝置該有的樣子」,一個負責「怎麼排成不同裝置該有的結構」。

New SlotTable

SlotTable 是 Compose runtime 用來追蹤 composition hierarchy、invalidations/recompositions、remembered values 和執行階段中繼資料的內部資料結構,而這個新的 SlotTable,目標是提升效能,其中重點是 random edits:

  • 舊實作是 gap buffer based slot table
  • 新實作是 link-list based slot table

主要優化的是 deleting、moving、reordering composable content,而具體原因是可以減少內部 array copy operations。

簡單說,Compose 以前這套內部結構更適合「局部追加、常規更新」這類模式,但當 UI 結構頻繁發生 刪除、移動、重排、隨機插入 這類編輯時,gap buffer 路徑的內部搬運成本明顯提升了不少,而新實作想優化的正是這類「結構性變更」的成本

個人感覺,這個優化和 Styles/Grid/FlexBox 一起出來並不算是巧合,它像是在為未來更複雜的 UI 編輯模式做 runtime 基礎建設,像是摺疊螢幕、Android PC 場景

如果從層級看,大概類似:

其他

到這裡,其他更新就反而有點無關緊要了。

Shared element Debug

shared elements 和 Modifier.animatedBounds 在這版新增了一些方便的視覺化除錯工具,現在開發者可以在 UI 看到底層發生的情況,例如目標邊界、動畫軌跡以及找到的匹配項數量。

<div><div><div></div><span>ini</span></div><div><div> <span>體驗 AI 程式碼助理</span></div><div> <span>程式碼解讀</span></div><div>複製程式碼</div></div></div>```
<span>LookaheadAnimationVisualDebugging(</span>
<span>    <span>overlayColor</span> = Color(<span>0</span>x4AE91E63),</span>
<span>    <span>isEnabled</span> = <span>true</span>,</span>
<span>    <span>multipleMatchesColor</span> = Color.Green,</span>
<span>    <span>isShowKeylabelEnabled</span> = <span>false</span>,</span>
<span>    <span>unmatchedElementColor</span> = Color.Red,</span>
<span>) {</span>
<span>    SharedTransitionLayout {</span>
<span>        CompositionLocalProvider(</span>
<span>            LocalSharedTransitionScope provides this,</span>
<span>        ) {</span>
<span>            // your content</span>
<span>        }</span>
<span>    }</span>

而你只需要將 `SharedTransitionLayout` 用 `LookaheadAnimationVisualDebugging` 包起來就行了:

![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/6bf2662d29fc45fc8a26b814a59f8e72~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&x-expires=1777596369&x-signature=u3MZKaDSjselWQDFo8WhyBDNUnU%3D)

### Trackpad event

觸控板支援也得到了改進,包括筆記型電腦內建觸控板、平板電腦外接觸控板以及外部/虛擬觸控板。

> 常威,你還說你不是 Android PC?

現在,基本的觸控板事件會是 `PointerType.Mouse` 事件,在此之前這些觸控板事件都算是 `PointerType.Touch` 類型的虛擬觸控螢幕手指。

另外,本次還增加了對平台自 API 34 起可識別的更複雜觸控板手勢的支援,包括雙指滑動和捏合,這些手勢會被 `Modifier.scrollable` 和 `Modifier.transformable` 自動識別。

> 這些 event 也可以改善內建元件裡的觸控板行為,優化觸控延遲,支援在文字欄位中雙擊和三擊選取。

![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/4b8defc452ca4a4fbfd8a3d05f8b2e7e~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oGL54yrZGXlsI_pg60=:q75.awebp?rk3s=f64ab15b&x-expires=1777596369&x-signature=ZC1q498ewiPQoZq9a4u2mv5b5OE%3D)

### Preview wrappers

這算是 Android Studio 自訂預覽的一項新功能,可以讓開發者精確定義 Compose 預覽內容的顯示方式,透過實作 `PreviewWrapperProvider` 介面和使用新的 `@PreviewWrapper` 註解,可以輕鬆注入自訂邏輯,例如套用特定主題。

該註解可以套用於同時使用 `@Composable` 和 `@Preview` 或 `@MultiPreview` 註解的函式:

<div><div><div></div><span>kotlin</span></div><div><div> <span>體驗 AI 程式碼助理</span></div><div> <span>程式碼解讀</span></div><div>複製程式碼</div></div></div>```
<span><span>class</span> <span>ThemeWrapper</span>: <span>PreviewWrapper</span> {</span>
<span>    span>@Composable</span</span>
<span>    <span>override</span> <span><span>fun</span> <span>Wrap</span><span>(content: @<span>Composable</span> (() -> <span>Unit</span>))</span></span> {</span>
<span>        JetsnackTheme {</span>
<span>            content()</span>
<span>       }</span>
<span>   }</span>
<span>}</span>
<span>​</span>
span><span>@PreviewWrapperProvider(ThemeWrapper::class)</span</span>
span><span>@Preview</span</span>
span><span>@Composable</span</span>
<span><span>private</span> <span><span>fun</span> <span>ButtonPreview</span><span>()</span></span> {</span>
<span>    <span>// JetsnackTheme in effect</span></span>
<span>    Button(onClick = {}) {</span>
<span>        Text(text = <span>"Demo"</span>)</span>
<span>   }</span>
<span>}</span>

棄用

該版本開始將棄用 Modifier.onFirstVisible(),因為它的名稱經常導致誤解,尤其是在 Lazy 版面,它會在捲動過程中多次觸發,所以官方建議遷移到 Modifier.onVisibilityChanged()

ComposeFoundationFlags.isTextFieldDpadNavigationEnabled 旗標移除,因為現在 TextFields 的方向鍵導航預設始終啟用。

最後

所以這次 Jetpack Compose 2026 年 4 月正式版本的更新,核心都在 Experimental API,同時也可以看出 Android PC 真的不遠了,因為這些 API 都在告訴你一件事:你需要適配更多大螢幕和滑鼠操作

連結

android-developers.googleblog.com/2026/04/jet…


原文出處:https://juejin.cn/post/7631745987393224745


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝3   💬3   ❤️1
199
🥈
我愛JS
💬2  
7
🥉
Gigi
2
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登