**我覺得這是 Jetpack Compose 今年更新最有意義的一個版本了,沒有之一**,因為它提供了一套全新的路線方向,同時也補齊了 Compose 三個長期短板:
雖然都是實驗性質,但是也明確了 Jetpack Compose 接下來的方向,這也是為什麼今年還沒過,我就確定它是最有意義的版本,因為這些 API 就算走到穩定版,今年也夠嗆了:
Styles、MediaQuery、Grid、FlexBox 和新的 SlotTable 實作,都能看出 Google 正在把 Compose 從目前的寫法推向下一代寫法。
首先就是 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>

在這段程式碼裡用的是 `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 抽離出來,以前的 background、externalPadding、width 之類都是 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」:

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>
也就是它會自動處理畫面排列,也可以顯式地處理跨越多行和多列,實現更精確的版面,最重要的是,它具有高度自適應,可以動態重新配置網格軌道和跨度,從而讓 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>

> 所以,這是一個天然適配摺疊螢幕、大螢幕和小螢幕切換場景的元件。
### FlexBox
`FlexBox` 這個應該更不陌生,作為自適應 UI 的版面容器,它支援根據容器的可用尺寸管理元素的大小和空間分配。
所以它能夠處理類似元素換行( `wrap` )和多軸對齊( `justifyContent`、`alignItems`、`alignContent` )等場景,同時支援元素增大( `grow` )或縮小( `shrink` )來達到填滿容器的效果。
> **這是一個很明顯的 CSS Flexbox 規範,所以它不是 FlowRow 的簡單升級版,而是更標準、更完整的彈性版面模型**。
甚至按照官方的說法,**如果你需要 wrap,那就用 `FlexBox`,而不是 `FlowRow` / `FlowColumn`**:

所以,從程式碼上看,`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* 家族,它們是另外一層抽象。它們其實是把 Compose 的版面體系補齊,核心是改善了 Compose 在多視窗、大螢幕、桌面化、摺疊態下的效能和能力:
Row / Column / Box:基礎骨架LazyColumn / LazyRow / LazyGrid:大資料容器FlowRow / FlowColumn:輕量流式FlexBox:標準一維彈性版面Grid:標準二維結構版面也是實驗性 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 是呼應的:一個負責「怎麼長得像不同裝置該有的樣子」,一個負責「怎麼排成不同裝置該有的結構」。
SlotTable 是 Compose runtime 用來追蹤 composition hierarchy、invalidations/recompositions、remembered values 和執行階段中繼資料的內部資料結構,而這個新的 SlotTable,目標是提升效能,其中重點是 random edits:
主要優化的是 deleting、moving、reordering composable content,而具體原因是可以減少內部 array copy operations。
簡單說,Compose 以前這套內部結構更適合「局部追加、常規更新」這類模式,但當 UI 結構頻繁發生 刪除、移動、重排、隨機插入 這類編輯時,gap buffer 路徑的內部搬運成本明顯提升了不少,而新實作想優化的正是這類「結構性變更」的成本。
個人感覺,這個優化和 Styles/Grid/FlexBox 一起出來並不算是巧合,它像是在為未來更複雜的 UI 編輯模式做 runtime 基礎建設,像是摺疊螢幕、Android PC 場景。
如果從層級看,大概類似:

到這裡,其他更新就反而有點無關緊要了。
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` 包起來就行了:

### Trackpad event
觸控板支援也得到了改進,包括筆記型電腦內建觸控板、平板電腦外接觸控板以及外部/虛擬觸控板。
> 常威,你還說你不是 Android PC?
現在,基本的觸控板事件會是 `PointerType.Mouse` 事件,在此之前這些觸控板事件都算是 `PointerType.Touch` 類型的虛擬觸控螢幕手指。
另外,本次還增加了對平台自 API 34 起可識別的更複雜觸控板手勢的支援,包括雙指滑動和捏合,這些手勢會被 `Modifier.scrollable` 和 `Modifier.transformable` 自動識別。
> 這些 event 也可以改善內建元件裡的觸控板行為,優化觸控延遲,支援在文字欄位中雙擊和三擊選取。

### 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…