Jetpack Compose 自适应开发:Media Query、Grid 与 Nav3 实战

Android 进入 Compose First 时代,用 Media Query、Grid 与 Nav3 构建自适应界面。
Google 在技术演讲中宣布 Android 开发正式进入 Compose First 时代。平台层面,Android 16 起大屏忽略方向与宽高比锁定,Android 17 移除临时退出标志,自适应从最佳实践变为基线要求,且大屏设备潜在用户超过 5.8 亿。Compose 相比传统 View 体系的核心优势在于配置变化只是一次普通重组,而非销毁重建视图层级。新工具链包括:借鉴 CSS 的 mediaQuery API,用窗口尺寸、折叠姿态、指针精度、键盘类型等 UI 能力替代设备类型判断;已稳定的二维 Grid 与一维 Flexbox 布局,可将布局配置参数化;以及基于 back stack 与 scene 策略的 Navigation 3。此外,迁移到 Compose 的应用 token 用量减少超 30%,Compose Adaptive Skill 能自动完成自适应改造。
Android 生态早已不是手机一统天下。从折叠屏、平板、Chromebook 到电视、车载信息娱乐系统乃至 XR 设备,同一个 APK 需要面对五花八门的窗口尺寸与输入方式。Google 在技术演讲中系统梳理了 Jetpack Compose 为自适应开发准备的新工具链,并给出了清晰结论:Android 开发已正式进入 Compose First 时代。
为什么自适应不再是可选项
平台层面的信号非常明确。从 Android 16(SDK 36)开始,系统在大屏上直接忽略方向锁定、宽高比锁定和无障碍标志,只给一个临时 manifest 标志用于退出方向覆盖;到 Android 17,这个退出开关也会消失。换句话说,构建自适应界面已经从最佳实践变成基线平台要求。
工程收益同样具体。据演讲披露,Android 应用潜在可运行在超过 5.8 亿台活跃大屏设备上,多设备与折叠屏用户是 Android 上参与度最高的群体之一。两个案例被拿来佐证:自我关怀日记应用 Gratitude 在大屏适配后,来自平板和折叠屏的日安装量增长 100%,大屏安装占比从 7% 提升到 10%;F1 赛事伴侣应用 BoxBoxClub 则录得整体活跃用户增长 7.6%、参与时长增长 54%。
传统 View 体系的问题在于状态与 UI 的耦合。XML 布局没有条件逻辑,只能在 layout、layout-sw600dp、layout-land 等资源目录里复制整棵层级树,同一个输入框散落在多个文件中,属性漂移几乎不可避免。窗口跨过阈值时系统销毁并重建视图层级,靠 onSaveInstanceState 按 View ID 恢复状态,ID 写错就静默丢失用户输入,恢复时机还容易与 ViewModel 观察者形成回调循环。
Compose 的模型完全不同:配置变化只是一次普通的重组。窗口变宽、设备展开时,composable 读到新的尺寸,依赖它的部分重新执行即可。

Window Size Classes(窗口尺寸类别) 是 Material Design 提出的视口断点标准,把窗口宽度划分为 compact(<600dp)、medium(600–839dp)、expanded(840dp 及以上)等桶,高度也有对应划分。它的意义不在于精确测量,而在于把连续变化的窗口尺寸离散成有限几档,使开发者只在跨越桶边界时做结构性布局切换,桶内保持响应式微调。这正是文中建议的应用级架构基础:单屏用 Grid/Flexbox 做桶内自适应,跨桶时才改变导航形态或窗格数量。
derivedMediaQuery 是相对于普通 media query 的派生查询:普通 media query 在依赖值每次变化时都会触发重组,而窗口尺寸在桌面拖拽缩放时可能逐帧改变;derivedMediaQuery 只在计算结果真正改变时才通知下游,因此适合承载尺寸类别这类“分档”信号,避免应用级 scaffold 被高频重组淹没。
Media Query:用能力而不是设备类型做决策
演讲反复强调一个反直觉的观点:不要问"我在手机上还是平板上"。同一台 Pixel 手机插上外接显示器后,同一个应用进程就从 16 英寸竖屏跑到了 32 英寸宽屏,还带着物理键盘和鼠标;多窗口下应用可能只占 400dp 宽。静态设备类型和物理显示指标都无法说明当前真实环境。
正确的抽象是 UI 能力(UI capabilities):窗口宽高、折叠姿态、指针精度、键盘类型、摄像头与麦克风可用性、观看距离。过去获取这些信号要拼接 Configuration、WindowMetricsCalculator、WindowInfoTracker、InputManager、PackageManager 五六个 API,再手动转成 Compose 状态。
新的 mediaQuery API 借鉴 CSS 媒体查询,在 AndroidX Compose UI 中提供单一声明式入口。UIMediaScope 暴露的属性包括:windowWidth/windowHeight(dp)、windowPosture(flat、tabletop、book)、pointerPrecision(fine 对应鼠标、触控笔、触控板,coarse 对应触摸,blunt 对应摇杆或手柄)、keyboardKind、hasCamera、hasMicrophone、viewingDistance。
以前检测桌面模式需要拿到 Context、实例化 WindowInfoTracker、收集 WindowLayoutInfo 这个 Flow、转成生命周期感知的 Compose 状态,再遍历 display features 判断是否存在半开且水平的折叠特征。现在只需在 mediaQuery 的 lambda 接收者作用域里检查 windowPosture 一个表达式。
实际用法也很直观:指针精度为 fine 时把内边距从 8dp 收紧到 2dp,因为用户不用手指,触控目标可以更小;聊天输入栏根据 hasCamera 和 hasMicrophone 动态显隐视频通话按钮;keyboardKind == Physical 时提示 Control-Enter 发送。测试同样受益——media query 解析到 LocalUIMediaScope 这个 CompositionLocal,预览和 UI 测试里直接 provide 一个伪造的 scope 即可,不必真的把折叠屏掰到 90 度或插上鼠标。

CompositionLocal 是 Compose 中沿组合树隐式向下传递数据的机制,类似一种作用域受限的依赖注入。LocalUIMediaScope 就是承载当前 UI 能力的 CompositionLocal:框架在真实运行时用窗口与输入设备信息填充它,而在预览和测试中,可以直接在组合树外层 provide 一个手工构造的 UIMediaScope 实例,从而模拟折叠屏半开、外接鼠标、物理键盘等任意环境。这种可替换性是把“环境探测”从命令式 API 调用转为声明式组合的关键,也让 UI 测试不再依赖真实硬件姿态。
Grid 与 Flexbox:把布局配置参数化
查询能力解决的是"知道环境",Grid 解决的是"如何排布"。这个在 AndroidX Compose Foundation Layout 中已稳定的二维布局原语,允许在 config 块里通过 column、row 累加轨道,用标准 Kotlin 循环如 repeat 批量生成,设置 gutter、通过 gridFlow 控制自动放置方向。
关键架构细节:config 块在 measure 阶段执行,GridConfigurationScope 实现 Density 并暴露 constraints,因此可以直接读取 constraints.maxWidth 在 config 内计算轨道,零额外重组。子项默认自动流入下一个单元格;需要显式控制时用 Modifier.gridItem 配合从 1 开始、支持负数索引的坐标(row(-1) 表示最后一行),还有 rowSpan 跨格和重叠堆叠——重叠时 Z 序由组合顺序决定,后声明的绘制在上层,行为类似 Box。
演讲中的视频播放器示例最能说明价值。传统写法要定义 defaultLayout 和 tabletopLayout 两个 composable 再分支,但两者内容完全相同,播放器和控制器的区别只在排列方式。Grid 允许把布局配置本身作为参数:用枚举定义命名区域,通过 Modifier.gridItem 把 player 和 controller 关联到区域 ID,默认布局配一个 1 行 1 列、两个区域名指向同一单元格(控制器覆盖在播放器上),桌面模式配两行一列、上下分置。最终只保留一个 VideoPlayerLayout composable,在 Grid 配置里直接使用 media query 动态切换。
Flexbox 也已稳定,补足一维场景:需要智能空间分配、收缩、多行换行时,它是 flowRow 加 flowColumn 两个容器的替代品,方向可在 config 块内动态改变而不重建子 composable,实现了完整 CSS flexbox 算法,含 wrap、justifyContent、alignItems、gap,子项通过 Modifier.flex 获得 basis、grow、shrink、alignSelf。

measure 阶段与重组(recomposition)的区别 是理解 Grid 性能优势的前提。Compose 的布局过程分为组合(决定有哪些节点)、测量(决定每个节点多大)、放置(决定节点在哪)三步。Grid 的 config 块在测量阶段执行,此时已经拿到父级传入的 constraints,因此可以基于可用宽度计算轨道数量而不触发新一轮组合。相反,如果在组合阶段读取窗口宽度来决定列数,尺寸一变就要重新执行 composable 函数、重建节点树,开销显著更高。这也是为什么文中强调“零额外重组”。
从单屏到全局:Window Size Classes 与 Navigation 3
单屏布局之上还有应用级架构。演讲建议用标准化的视口断点——即 Window Size Classes——让每个屏幕视口归入同一尺寸桶,桶内保持响应式,桶之间才做结构性变化,比如 compact 只显示列表、expanded 同时显示列表和详情。可以直接在 UIMediaScope 上定义扩展属性实现,但要用 derivedMediaQuery 而非普通 media query:窗口宽高在桌面拖拽时可能每帧变化,而应用级 scaffold 只应在真正跨越断点时重组。
多窗格切换交给 Jetpack Navigation 3。Nav3 从设计之初就围绕 Compose 状态和 scene 构建:back stack 就是一个 nav key 列表,灵活且易于保存为状态;每个 key 解析为包含 composable 和元数据的 nav entry;NavDisplay 不硬编码绘制方式,而是评估一系列 scene strategy。策略内部可以检查 UI 能力(如窗口尺寸类别)和 back stack,再决定单窗格渲染一个 entry、同时渲染多个,还是层叠显示。尺寸类别还决定导航组件选型:compact 用底部导航栏,medium 用导航轨道,expanded 用完整导航抽屉。

Scene 策略(scene strategy) 是 Navigation 3 的核心抽象。传统导航库把“一个目的地对应一个全屏界面”写死,多窗格只能靠嵌套导航图或自定义容器硬凑。Nav3 把渲染决策从 NavDisplay 中剥离出来:back stack 只描述“用户处在哪些目的地”,而 scene strategy 负责把这些 key 映射成具体的可视布局——可以是单窗格、并列双窗格,也可以是列表叠加详情的层叠结构。由于策略是普通函数,它可以读取窗口尺寸类别、折叠姿态等 UI 能力,也可以在平板与手机间复用同一份 back stack,这正是 Navigation 3 与 Compose 状态模型天然契合的原因。
Agent 时代的 Compose:更少 token,更强技能
演讲给出的一个数据值得注意:迁移到 Compose 的应用,token 用量减少超过 30%。代码库更小、更易理解,agent 处理 Compose 代码的效率和成本都更优。
配套的 Compose Adaptive Skill 在早期版本已能把手机布局迁移到大窗口尺寸,如今能力进一步扩展:显式处理端到端状态管理(针对真实状态丢失缺陷测试过导航、滚动位置、媒体播放、半输入文本的保留)、折叠姿态优化(自动把媒体与相机界面重构为桌面模式)、以及输入支持审计——发现并实现键盘快捷键、自定义指针光标、悬停状态和工具提示。
现场演示中,一个布局过宽、调整窗口就丢失输入文本的应用,只需一句"make the app adaptive",Gemini 3.0 Flash 结合该 skill 自动完成改造,修改了 11 个文件,导航移到侧边、Grid 生效、过滤器恢复工作。这次运行耗时约 20 分钟、消耗约 20 万 token。
三条结论可以收尾:自适应让应用服务更多设备与用户,并为扩张中的生态做长期准备;Android 开发是 Compose First,新工具、库和技能都为 Compose 构建;Compose 既适合自适应布局,也适合 agent 开发。
相关推荐

私钥设为4?CA机构低级错误致TLS体系裸奔
CA机构eMudra将私钥设为数字4等极小值,导致TLS证书可被暴力破解。Computerphile视频演示了从提取公钥到伪造证书的完整过程,并探讨了PKI信任链的脆弱性。

Gen如何用ChatGPT Work把数据变决策
OpenAI用户案例中,企业领导者Gen分享用ChatGPT Work将数据转化为决策的实践:产品上线几小时内即可判断集成与引导流程是否有效,决策迭代从上百次试错变为快速反馈闭环。

ChatGPT与Claude深度对比:编程强但研究弱,如何弥补?
用户考虑从ChatGPT转向Claude,发现Claude编程更强但研究较浅。本文分析两者差异,并提供让Claude研究更深入的实用技巧,帮助您做出明智选择。