☰
Jetpack Compose 预览(@Preview)全面指南:从零到交互式 UI 调试
2026/10/2 10:32:38 网站建设 项目流程

1. 先说清楚:此 Compose 非彼 Compose

如果你是在搜索框里直接输入"Compose"三个字母,大概率会看到两种完全不同的东西:一种叫 Docker Compose,一种叫 Jetpack Compose。我见过不少人在群里问"Compose 怎么部署 nacos 3.x",也有人问"Compose 预览为什么白屏",两边聊了半天才发现根本不是同一个东西。Docker Compose 是容器编排工具,用 YAML 描述一组服务,docker compose up一拉全起,它所谓的"预览"通常指docker compose config这类解析检查;而 Jetpack Compose 是 Android 的新一代声明式 UI 工具包,用 Kotlin 写界面,它真正意义上的"预览"是 Android Studio 里的实时渲染功能。

这篇文章要讲的是后者,Jetpack Compose 的完整预览教程。它解决的是 Android 开发里最磨人的一个问题:改完 UI 代码,想看一眼效果,到底要不要启动模拟器、要不要打包装到真机上?传统 XML 时代,Layout Editor 勉强能看,但慢、笨、不够实时。Compose 的预览把这件事做到了极致——你在代码里写一个@Preview注解,右侧面板立刻渲染出组件效果,改参数、改间距、换主题,几乎零延迟。这个功能对开发效率的提升是肉眼可见的,尤其适合做多设备适配、深浅色模式、多语言切换这类需要反复比对效果的场景。不管你是刚接触 Compose 的新手,还是已经写了一阵子 UI 但没用透预览的老手,这篇内容都值得完整看一遍。

你可能还会看到一句眼熟的警告:"你尝试预览的文件可能对你的计算机有害。如果你信任此文件以及其来源,请打开此文件。"这是浏览器或系统对可疑附件的拦截提示。预览这个概念本身其实很安全,Compose 预览也同理——它只是在 IDE 里渲染 UI,不会执行业务逻辑,不会发网络请求,也不会写文件。所以放心大胆地多用预览,它不会"打开"一个炸弹,只会帮你提前排除 UI 问题。

2. 预览到底解决了什么问题

2.1 传统 UI 调试的痛点

写过 XML 布局的人都有体会,layout文件改完,切到 Design 页要看半天,有时还要Build一下才刷新。真要验证交互,就得跑模拟器,冷启动一次少说 20 秒,热启动也要好几秒,App 一大了更是折磨。而且 XML 加各种match_parent、wrap_content嵌套,嵌套多了布局层级深,性能问题也难查。写代码和看效果之间的反馈回路太长,人很容易烦躁,效率自然上不去。

Compose 预览的核心价值,就是把"改代码—看效果"的回路压缩到几乎为零。它是 IDE 层面的即时渲染,改动 Kotlin 代码后,Design 面板里对应组件立刻更新,不需要重新打包,不需要装模拟器。这种体验有点像在文档工具里编辑富文本,所见即所得。

2.2 预览的适用场景

日常开发里,预览最能发力的场景是这几个:

  • 纯静态 UI 的验证:卡片布局、按钮样式、列表项、空态、加载态,这些不需要真实数据的界面,预览直接渲染,一眼看出间距、字体、颜色对不对。
  • 多状态展示:同一个组件在普通态、深色态、大字体态、不同语言下的表现,用多个预览函数组合,一次全看。
  • 多设备适配:手机、折叠屏、平板不同尺寸,预览支持模拟不同设备,提前发现布局溢出或拉伸问题。
  • 组件库开发:如果你在做团队内部的 Compose 组件库,预览就是组件的"文档",配合group参数把组件分类,Design 面板里可以按分组筛选,效果比翻文档直观多了。

换句话说,真机调试不是不需要,而是应该留到做真实交互、数据链路、性能检测的时候再去跑。纯 UI 层面的事,预览全包了。

2.3 预览的边界

预览再强,也有它的边界。凡是涉及真实的生命周期、网络请求、传感器、复杂状态流转的逻辑,预览是模拟不了的。比如LaunchedEffect里的网络加载、ViewModel的数据驱动更新,预览环境不会执行这些逻辑。理解这条边界很重要,否则你会花很多时间在预览里折腾一个永远跑不起来的交互,最后发现方向就不对。预览负责"看长相",真机负责"验动作",两者分工不同。

3. 从零开始:@Preview 注解的完整参数拆解

3.1 最小可用的预览长什么样

先看最基础的写法。定义一个@Composable函数,给它加上@Preview注解,就能在 Design 面板里预览。

@Preview @Composable fun GreetingPreview() { MyAppTheme { Greeting("Android") } }

这里有一个很容易被忽略但很重要的点:预览函数内部最好用你的应用主题MyAppTheme包一层。如果不包,编译是能过的,预览也能显示,但组件会使用 Material 默认主题的配色和字体风格,和你 App 实际运行时的观感可能差很远。尤其是你用到了自定义ColorScheme、动态取色这类主题逻辑,不包 Theme 的预览基本等于白看。所以我的习惯是:所有预览函数都套一层应用主题,宁可多敲几个字,也不要看到假效果。

还有一个细节:@Preview注解标注的函数不能有参数,否则 IDE 没法决定传什么数据进去。如果组件本身需要参数怎么办?后面第 5 节会讲@PreviewParameter这个解法。

3.2 常用参数逐条解读

@Preview不是空注解,它自带十几个参数,每个参数对应一种预览行为。我把最常用的整理成一张表:

参数类型作用典型写法
nameString预览卡片显示的名称name = "深色模式卡片"
groupString预览分组,Design 面板可按组筛选group = "列表组件"
showBackgroundBoolean是否显示浅色/深色背景区showBackground = true
backgroundColorLong自定义背景色,ARGB 格式backgroundColor = 0xFF202124
showSystemUiBoolean是否显示状态栏、导航栏等系统窗口showSystemUi = true
widthDpFloat预览宽度,单位是 dp 的数值widthDp = 360f
heightDpFloat预览高度heightDp = 640f
localeString模拟地区语言locale = "zh-rCN"
fontScaleFloat字体缩放倍数fontScale = 1.5f
uiModeIntUI 模式,深色/浅色/夜间等uiMode = Configuration.UI_MODE_NIGHT_YES
themeKClass指定预览使用的主题类theme = MyAppTheme::class
deviceString模拟设备规格device = "id:pixel_8_pro"

我单说几个常见的坑。

widthDp和heightDp接收的是 Float,不是Dp类型。新手最容易在这里翻车,写了widthDp = 360.dp,编译直接报错。正确的写法是widthDp = 360f,因为注解参数必须用编译期常量,不能是运行时对象。

showSystemUi这是个好东西。默认情况下预览只画组件本身,不显示系统状态栏和导航栏,看起来有点像"悬浮的 UI 碎片"。当你想模拟一个 Activity 里完整页面的真实观感,把这个参数设成true,预览顶部会出现状态栏、底部出现手势导航条,整体效果更接近真实设备。

backgroundColor要注意格式。它接收的是 Long 类型的 ARGB 值,0xFF202124表示不透明的深色,前面两位FF是 Alpha 通道。很多人写0x202124,丢掉了透明度前缀,结果是展示成半透明甚至全透明,背景一片奇怪的颜色。

group参数是团队协作里的隐藏神器。一个项目里可能有几十个预览函数,全部堆在 Design 面板里乱成一团。给它们分好组,比如"登录组件""首页卡片""个人中心入口",Design 面板的筛选器就能快速定位到某一组,看代码 Review 的时候也非常清爽。

3.3 多预览组合的实战写法

实际开发里,一个组件往往需要覆盖多个状态。比如一张商品卡片,至少要看默认态、深色态、大字体态和英文态。你可以写四个独立的预览函数,也可以拆开组合:

@Preview( name = "商品卡片-默认", group = "商品", showBackground = true ) @Preview( name = "商品卡片-深色", group = "商品", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true ) @Preview( name = "商品卡片-大字体", group = "商品", fontScale = 1.5f, showBackground = true ) @Preview( name = "商品卡片-英文", group = "商品", locale = "en-rUS", showBackground = true ) @Composable fun ProductCardPreview() { MyAppTheme { ProductCard( title = "无线降噪耳机", price = "1999", image = painterResource(R.drawable.ic_product) ) } }

注意,这些注解是叠在一个@Composable函数上的。Android Studio 会为每个注解各生成一张预览卡片,所以你可以在 Design 面板里同时看到四个卡片。我最常用的组合是"默认 + 深色 + 大字体",基本上能覆盖 90% 的 UI 走查需求。这种堆叠注解的方式,代码看起来是有点冗余,但换来的是"一次写四张卡片",效率很值。

4. 多设备、多主题与无障碍预览

4.1 用 device 参数模拟不同设备

手机厂商的屏幕尺寸五花八门,折叠屏、平板也早已普及。靠真机一台台去适配不现实,预览的device参数就是为了解决这个场景。

@Preview( name = "Pixel 8 Pro", device = "id:pixel_8_pro", showBackground = true ) @Preview( name = "折叠屏展开态", device = "spec: width=673dp,height=841dp,orientation=landscape", showBackground = true ) @Composable fun HomePagePreview() { MyAppTheme { HomePageContent() } }

device参数支持两种写法。一种是id:xxx,直接引用 Android Studio 内置的设备定义,比如pixel_8_pro、pixel_5、nexus_5等。另一种是spec:xxx,自己定义设备规格,语法如下:

spec: width=360dp,height=640dp,orientation=portrait,round=true,chin_size=24dp

spec的可选键包括width、height、orientation、round、chin_size等。orientation可以写portrait或landscape,round表示是否模拟圆形屏幕,chin_size是屏幕下巴高度。这种方式非常灵活,比如你想模拟一块 1:1 的方形屏幕,直接写spec: width=400dp,height=400dp就行,比找对应设备 id 省事得多。

不过我得提醒一个实战里的教训:device预览不要贪多。以前我就一个列表组件挂了八个设备预览,结果每次改代码都要等十几秒编译刷新,反而拖慢了节奏。现在的做法是固定两个:一个主流手机(Pixel 8 Pro 这类尺寸),一个自己的目标设备。真要做全设备适配,不如用 Compose 的BoxWithConstraints或者 WindowSizeClass 之类的方案做响应式布局,再配合少量关键设备预览来验证。

4.2 深浅色一键双看:@PreviewLightDark 与 @PreviewDark

如果你的 App 做了深色模式适配,那么每个组件至少得看一遍深色效果。手动写uiMode = Configuration.UI_MODE_NIGHT_YES虽然能看,但每次只显示一张卡片,想对比深浅色差异还要来回切。Compose 提供的@PreviewLightDark注解可以直接解决这个问题。

@PreviewLightDark @Composable fun ColorfulCardPreview() { MyAppTheme { ColorfulCard() } }

这个注解会自动生成两张预览卡片:一张亮色模式,一张暗色模式。你不需要传任何参数,也不用手动指定uiMode。原理上它其实是一个组合注解,内部展开了两次预览配置,一次用UI_MODE_NIGHT_NO,一次用UI_MODE_NIGHT_YES。

类似的还有一个@PreviewDark,只生成暗色一张。如果你只关心深色是否正常,用@PreviewDark就够了,预览面板能少一张卡片,干净一些。

这里也回应一下热词里那句中英文对照的疑问。Compose 有一系列注解标签,中文社区里经常有人整理成"中文版注解标签":@Preview是"预览",@PreviewLightDark是"亮暗预览",@PreviewParameter是"预览参数注入",@PreviewWallpaper是"预览壁纸",@SampleData是"示例数据"。搞清楚这些注解的中文含义,记起来会顺手很多。

4.3 动态主题预览:theme 参数的正确打开方式

如果你的 App 支持动态取色(Dynamic Color),或者有多套主题可以在运行时切换,预览时最好显式指定主题类。

@Preview( name = "新版主题-亮色", theme = MyAppTheme::class, showBackground = true ) @Composable fun SettingsPagePreview() { SettingsContent() }

注意theme = MyAppTheme::class的写法,这里传的是 KClass 引用,不是实例。它的作用相当于在预览环境里设定一个CompositionLocalProvider级别的主题上下文,让组件渲染时优先使用你指定的主题。如果你的 App 只有一套主题,不写这个参数其实问题不大,因为你的预览函数内部一般也会包MyAppTheme。但如果有多套主题,或者你在做主题切换功能,建议把theme参数用起来,否则预览里的配色可能和你预期的主题不匹配,排查起来很费劲。

4.4 字体缩放与多语言预览

国际化 App 里,字体缩放和语言切换是两个高频验证场景。fontScale参数直接控制预览里的字体放大倍数,注意它是相对数值:1.0f是默认,1.3f模拟系统大字体,2.0f是极端放大。国内不少用户会把系统字体调到最大,如果你的布局用了固定高度或固定宽度,放大字体会出现文字截断。这类问题在真机上很难提前发现,因为很少有人随身带一台大字体设置好的测试机,但预览里一个参数就能看到。

语言切换用locale参数,值格式是"语言代码-地区代码",比如"zh-rCN"表示简体中文、"en-rUS"表示美式英文、"ja-rJP"表示日文。写完一段文案后,直接加上locale = "en-rUS"看一眼英文状态下的换行,比真机切系统语言快得多。

5. 可交互预览与动态数据注入

5.1 可交互预览:预览面板里的"真机"

Android Studio 的预览面板右上角有一个"可交互模式"按钮,图标是一个小手加一个圆圈,点了之后预览卡片就变成一个可以点击的界面。你可以在里面点按钮、切换 Switch、输入文字、滚动列表。这个模式对我这种日常做表单类页面的开发非常有用,以前验证一个按钮的点击态、一个输入框的焦点态,都得跑一次真机,现在直接在预览面板里完成。

@Preview( name = "登录表单-交互", showBackground = true ) @Composable fun LoginFormPreview() { MyAppTheme { LoginScreen() } }

需要注意的是,可交互预览只能识别组合函数内部的mutableStateOf状态变化。如果你在代码里直接写了网络请求、数据库访问,这些在预览环境不会真的执行。另外,可交互预览对动画的支持取决于 Android Studio 版本,我用过的 Electric Eel 之后的版本基本都能流畅预览AnimatedVisibility这类简单动画。如果你的预览面板里可交互按钮是灰色的,先检查 Android Studio 版本是不是太老。

5.2 @PreviewParameter:给预览批量喂数据

很多Composable函数是有参数的,比如一个用户信息卡片,接收User对象;一个新闻列表,接收List<News>。@Preview要求函数无参,那怎么预览带参组件?答案是@PreviewParameter。

class PreviewUsers : PreviewParameterProvider<User> { override val values = sequenceOf( User("张三", "在线", true), User("李四", "离线", false), User("王五", "勿扰", true) ) } @Preview @Composable fun UserCardPreview( @PreviewParameter(PreviewUsers::class) user: User ) { MyAppTheme { UserCard(user) } }

PreviewParameterProvider是接口,实现values属性,返回一个数据序列。预览时,IDE 会把你提供的每一个数据项都渲染成一张卡片。比如上面这个例子里有三个User对象,预览面板就会生成三张用户卡片,一眼看完正常态、离线态、勿扰态三个分支。

用这个 API 有两点要注意。第一,values是一个Sequence,惰性求值,数量控制好。我一般控制在 5 个以内,太多会拖慢预览构建。第二,PreviewParameterProvider最好不要在生产代码里直接引用真实业务数据类,更不要让 provider 里的数据来自网络。它的作用只是喂假数据给预览看,如果哪一天有人不小心在生产环境的调用链里初始化了这个 provider,容易出问题。所以我习惯把它定义在 preview 相关的包或者文件里,跟正式代码隔离。

5.3 @SampleData 与动画预览的进阶玩法

@SampleData是另一个预览数据注入手段,它配合 Android Studio 的 Sample Data 功能使用,可以从 JSON 文件或资源文件里读取示例数据作为预览输入。它的好处是数据不用写在 Kotlin 代码里,适合数据量比较大、字段比较多的场景。

动画预览方面,早先的预览确实只能看静态,后来 Android Studio 逐步增强了@Preview对动画的支持。rememberInfiniteTransition这类无限动画在可交互模式下已经可以播放,但性能一般,复杂动画还是建议真机看。我自己的经验是:动画预览最适合验证"动画是否存在""元素是否按照预期位移/淡入淡出",不适合做性能调优。真机上的帧率、卡顿,预览还是力不从心。

5.4 SVG 转 ImageVector 与预览图标不显示的坑

热词里有一条"svg → compose imagevector kotlin",这个话题和预览的关系非常密切。Compose 里不能用传统的Bitmap直接当图标,而是要用ImageVector这样的矢量图形对象。如果设计给的资源是 SVG,你需要转成 Compose 能用的格式。

常见的转换途径有两种。一种是 Android Studio 自带的 Vector Asset 工具,在 res 目录右键 New > Vector Asset,选择本地 SVG 文件,IDE 会自动生成对应的 drawable XML 资源,然后在 Compose 里用painterResource(R.drawable.ic_xxx)加载。这种方式生成的实际上是 Android 的 VectorDrawable 资源,预览一般能正常显示。另一种是使用三方工具把 SVG 直接转成 Kotlin 文件(ImageVector的构造代码),生成的是纯 Kotlin 对象,适合打包体积敏感的团队。

预览里图标不显示,大部分时候不是转换的问题,而是资源路径或构建缓存的问题。改完ImageVector或 SVG 后,预览面板里图片还是空的,我会先做一次 Rebuild,再看资源文件是否放在正确的目录。另外,如果你用的是动态生成的ImageVector代码,注意group和path的命名不能重复,否则合并时会被 IDE 合并导致显示异常。

6. Android Studio 预览面板的完整操作技巧

6.1 Design / Split / Code 三种模式

打开一个带@Preview的 Kotlin 文件,编辑器右上角有四个图标:Code、Split、Design、Preview。Code 是纯代码视图,Design 只显示预览面板,Split 是代码和预览上下分屏。我强烈建议日常开发用 Split 模式,左边码代码右边看效果,改一个参数就变一下。Preview 的意思是只显示预览面板而不进入设计画布,适合单独审查 UI 的时候用。

如果你在编辑器里找不到设计面板,大概率是当前文件没有任何@Preview注解,或者 Android Studio 正卡在构建。检查一下右下角的进度条,等构建完成再操作。

6.2 预览工具栏的隐藏功能

预览面板顶部有一条工具栏,第一个是刷新按钮(两个箭头绕圈的图标),点击会强制重新构建当前预览。旁边是设备下拉框,你可以在这里临时切换预览设备。再右边是缩放比例,可以从 25% 到 200%,放大镜看细节很有用。还有一个旋转按钮,点击可以把预览方向在横竖屏间切换。

这些操作最大的价值在于,它们不会污染你的代码。你不需要为了看一眼横屏效果去改某个device参数,然后再改回来,直接在工具栏里切就行。

6.3 手动触发预览更新

大多数时候 IDE 会自动增量编译并刷新预览,但总有抽风的时候。比如你改了一个资源 XML,或者改了一个自定义@Composable函数的文件名,预览会卡在旧版本上。这时候先点刷新按钮,不行就Build > Rebuild Project,再不行就File > Invalidate Caches and Restart。我遇到预览不更新的情况,90% 是增量编译缓存出问题,Rebuild 一下基本都能解决。

另外,预览构建也依赖 Gradle 同步。如果build.gradle.kts里新增了依赖(比如 Compose Material3 的某个新版本),最好先 Sync 一次,再回来看预览。@Preview背后不是魔法的根源就在这里,它本质上是 IDE 调用 Gradle 编译产物来渲染,所以构建链路任何一环出问题,预览都会挂。

7. 常见问题与避坑实录

预览用久了,多少会碰到一些让人觉得"莫名其妙"的问题。我把这些年踩过的坑整理成一个速查表,基本覆盖了 90% 的预览疑难杂症。

现象可能原因解决方案
预览面板空白函数没有@Preview注解检查注解是否添加
报错 Preview requires at least one函数不是@Composable给函数加@Composable修饰符
预览不刷新Gradle 增量构建缓存问题点刷新,或 Rebuild,或 Invalidate Caches
预览颜色和真机不一致预览函数没包 Theme用MyAppTheme包裹,或指定theme参数
widthDp编译报错传了Dp类型改成 Float 数值,如360f
深色模式不生效忘了uiMode或@PreviewLightDark补上对应注解或参数
大字体下文字截断固定高度容器导致真机或预览中查看布局约束,改用自适应高度
多 device 预览卡顿预览注解太多精简到 2-3 个关键设备
可交互模式按钮灰色IDE 版本过旧或当前组件不支持升级 Android Studio,检查组件是否使用真实状态
图标不显示ImageVector 资源路径或缓存问题检查资源文件,Rebuild 后再看
分组筛选找不到卡片group参数拼写或大小写不一致统一分组名规范,建议英文常量

这里重点讲一下"预览颜色和真机不一致"的问题。Color 在 Compose 中有很多来源:MaterialTheme.colorScheme、Color(0xFF....)、resource(R.color.xxx)。如果你在预览函数里直接写死Color(0xFF333333),那预览显示的就是这个颜色,和真机必然一致。但如果走的是主题色,比如MaterialTheme.colorScheme.primary,而你的主题里定义了动态取色或品牌色替换逻辑,预览和你当前系统的动态取色结果就可能不一致。解决办法就是前面反复强调的,预览函数外面包一层和 App 一致的MyAppTheme。

还有一个很容易忽略的:预览里用R.drawable.xxx加载的图片资源,如果是 WebP 或者较大尺寸的 PNG,预览构建会明显变慢。建议预览状态的图片占位图尽量用纯色或ImageVector,减少解码开销,真机跑的时候再替换成真实资源。

最后再分享一个我自己的习惯:所有预览相关的数据类、Provider 类,统一命名为PreviewXxx或XxxPreviewData,放在项目的preview包下。这样即使用户误引用了预览数据,代码审查的时候一眼就能看到。我还习惯在@Preview注解的name里写清楚"这是哪个页面、哪个状态",比如"登录页-密码错误提示",配合group分组,整个 Design 面板就好像一个可视化的组件文档,别人接手代码时能少问很多问题。

预览功能我从 Compose 还叫 Alpha 的时候就开始用了,这几年看着它从只能看静态快照,进化到可以交互、可以模拟多设备、可以喂假数据。平时真机跑得再多,也不如花十分钟把各种状态都堆到预览面板里一次看完来得高效。如果你现在还是"写完代码直接跑真机"的习惯,我建议从下一个组件开始,先写@Preview再看效果,慢慢你就会发现,真机调试的次数会断崖式下降,但代码质量和走查效率反而上来了。Compose 的版本更新很快,预览相关的注解和 IDE 功能也在持续迭代,遇到不认识的参数或按钮,先看一眼当前版本的官方更新说明,别在旧习惯里打转。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询