关于 styles
面向 Jetpack Compose 自定义组件和主题的 Styles API 迁移技能,涵盖样式定义、状态处理与视觉验证。该技能属于实验性功能,要求使用 alpha 版 Compose API,不支持 Material Design 组件样式。
主要功能
- 01分析主题结构与设计令牌
- 02建立 ComponentStyles 并接入自定义主题
- 03将组件迁移至 Styles API
- 04处理交互状态与 Modifier.styleable()
- 05通过截图和 UI 测试验证视觉效果
适用场景
- 01将自定义组件的样式参数迁移到 Style
- 02把设计令牌集中到组件样式定义
- 03验证样式迁移是否造成视觉回归
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ android/ skills --skill styles安装后在 Claude Code 中输入/styles确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/android/develop/ui/compose/designsystems/custom.mdreferences/android/develop/ui/compose/styles/fundamentals.mdstate-animations.mdstyles-vs-modifiers.mdtheming.md详细介绍
限制
- 告知用户,此技能为 EXPERIMENTAL,需要升级到 Compose 的 alpha 版本,并选择启用 Experimental API。
- 此技能仅支持自定义 UI 组件和自定义主题。
- 此技能不支持 Material Design 组件样式。
前提条件
1. 升级依赖项
- 项目必须使用
compileSdk版本 37 或更高版本。 - 项目必须使用
androidx.compose.foundation:foundation版本1.12.0-alpha01或更高版本。 - 或者,项目必须使用 Compose BOM 版本
2026.04.01或更高版本。 - 此 API 要求使用以下确切的软件包:
import androidx.compose.foundation.style.Style
2. 配置编译器选项以启用实验性 API
必须在项目级别选择启用实验性 API。将以下代码块添加到模块的 build.gradle.kts 中:
kotlin {
compilerOptions {
jvmTarget = JvmTarget.fromTarget("17")
freeCompilerArgs.add("-opt-in=androidx.compose.foundation.style.ExperimentalFoundationStyleApi")
}
}
核心工作流和指南
请参阅官方文档以完成具体的开发任务:
- 基本样式用法:如需为组件设置背景、尺寸和对齐方式,请参阅 Compose 样式基础指南 。
- 状态与过渡:如需配置状态变化(例如按下或悬停)时的属性更改,请参阅 动画与基于状态的样式指南 。
- 架构权衡:如需决定何时使用 Style 而不是标准 Modifier,请参阅 Styles 与 Modifiers 对比 。
- 主题级集成:如需将样式定义与自定义主题关联起来,请参阅 使用 Style 设置主题 和 Compose 中的自定义主题。
分步迁移工作流
第 1 步:分析主题结构
- 找到中央主题文件(例如
Theme.kt)。 - 找出设计令牌。记录颜色、排版和形状的引用(例如
LocalColorScheme、LocalTypography或LocalShapes)。 - 如果项目缺少 Jetpack Compose 依赖项,请停止。指示用户先迁移到 Jetpack Compose。
- 如果项目导入了
androidx.compose.material.MaterialTheme,请建议先迁移到 Material 3,再继续操作。
第 2 步:建立 ComponentStyles
-
在主题目录中创建名为
ComponentStyles.kt的新文件。 -
定义一个顶层数据类来保存组件样式,例如,Jetsnack 中的类名为
JetsnackStyles:object ExampleComponentStyles { val customButtonStyle: Style = { } val customTextFieldStyle: Style = { } }
-
通过静态引用在自定义主题中公开此类,此处无需使用
CompositionLocals。@Immutable class JetsnackTheme( // other Design system properties ) { companion object { val colors: CustomThemingWithStyles.JetsnackColors @Composable @ReadOnlyComposable get() = LocalJetsnackTheme.current.colors // ... // add helper static reference val styles: ComponentStyles = ComponentStyles } }
-
如果主题令牌通过
CompositionLocals暴露,请在StyleScope上提供扩展,以便直接引用这些令牌。例如:val StyleScope.colors: JetsnackColors get() = LocalJetsnackTheme.currentValue.colors val StyleScope.typography: androidx.compose.material3.Typography get() = LocalJetsnackTheme.currentValue.typography val StyleScope.shapes: Shapes get() = LocalJetsnackTheme.currentValue.shapes
步骤 3:将组件迁移到 Styles API
对于每个自定义组件(例如 CustomButton),请完成以下步骤:
- 建立视觉基线(如果有模拟器):
- 如果无法运行 Android 模拟器: 完全跳过此步骤,继续执行步骤 2。
- 如果可以运行 Android 模拟器: 按照以下方式截取基线屏幕截图:
- 选项 A: 查找并运行组件现有的屏幕截图测试。
- 选项 B(如果没有测试): 使用项目现有的测试框架创建测试,然后运行。
- 选项 C(如果没有框架): 使用 UI Automator 或 Espresso 创建最简屏幕截图测试,然后运行。
- 移除单独的样式参数:从签名中移除
backgroundColor、shape、textStyle和contentPadding等样式参数,即所有StyleScope支持的参数。 - 添加 style 参数:在函数签名中添加
style: Style = Style。务必确保默认值恰好是Style(例如style: Style = Style),而不是ChipStyleDefault等特定样式默认值。 - 声明状态跟踪:如果组件可交互,请使用交互源创建
MutableStyleState。在 Composable 中更新状态字段(例如isEnabled),以正确跟踪状态。 - 应用可设置样式的修饰符:将根元素上的特定布局修饰符替换为
Modifier.styleable()。 - 将默认值移至 ComponentStyles:将组件定义中的硬编码值移至
ComponentStyles.kt中专用的Style实例。 - 验证组件: 将开始时截取的基线屏幕截图与新 Composable 渲染的 Compose Preview 进行比较。忽略字符串内容,重点关注布局和样式。反复调整 Compose 代码,直到实现视觉一致。验证完成后,为新的 Composable 编写 Compose UI 测试。
迁移示例
迁移前:
@Composable
fun CustomButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
backgroundColor: Color = JetsnackTheme.colors.brandLight,
disabledBackgroundColor: Color = JetsnackTheme.colors.brandSecondary,
shape: Shape = JetsnackTheme.shapes.extraLarge,
textStyle: TextStyle = JetsnackTheme.typography.labelLarge,
enabled: Boolean = true,
content: @Composable RowScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
Row(
modifier
.clickable(onClick = onClick, indication = null, interactionSource = interactionSource)
.background(if (enabled) backgroundColor else disabledBackgroundColor, shape)
.defaultMinSize(58.dp, 40.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
content = content,
)
}
迁移后:
// Exposed via ComponentStyles.kt
object ComponentStyles {
val buttonStyle = Style {
background(colors.brandLight)
shape(shapes.extraLarge)
minWidth(58.dp)
minHeight(40.dp)
textStyle(typography.labelLarge)
disabled {
background(colors.brandSecondary)
}
}
}
@Composable
fun CustomButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
style: Style = Style,
enabled: Boolean = true,
content: @Composable RowScope.() -> Unit,
) {
val interactionSource = remember { MutableInteractionSource() }
val styleState = rememberUpdatedStyleState(interactionSource) {
it.isEnabled = enabled
}
Row(
modifier
.clickable(onClick = onClick, indication = null, interactionSource = interactionSource)
.styleable(styleState, JetsnackTheme.styles.buttonStyle, style),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
content = content,
)
}
步骤 4:验证更改
- 构建项目。确认没有编译错误。
- 运行模块的屏幕截图测试。
- 比较更新前后整个应用的视觉输出。确认没有出现视觉布局回归。
评论
支持 Markdown