05 · 样式与主题¶
Style 是什么¶
style.Style 是不可变的外观与盒模型声明。从零值起头,再链式方法叠加:
下文示例统一假定已通过 activeTheme := core.ActiveTheme(ctx) 取得当前主题值,
颜色直接引用主题的 Colors 字段(主题是值,不是查询——见 architecture):
primary := style.New().Background(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Fill)).
TextColor(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].OnFill)).
Radius(8).
Padding(6, 12).
Cursor(style.CursorPointer)
挂到控件:
button.New("确定").Style(primary)
// 或
layout.Box(child).Style(style.New().Padding(24))
// 需要拆开写时,一次调用接收多条声明(按顺序合并,后者覆盖前者)
layout.Box(child).Style(style.New().Padding(24), style.New().MaxWidth(760))
不必为每个属性写一次 .Style(...):链式声明能放进同一次调用,多条声明也能一次传入。重复调用 .Style(...) 同样累积(layout.Box 走 style.Join)。
没有单独的 Builder / Build 步骤;每次方法返回新的 Style 值。
级联顺序(固定)¶
每一层先应用自身匹配的状态规则,再参与层间合并;后面的层覆盖前面的层。
同一层的规则按 Rank、When 所需状态位数量、声明顺序依次应用。
On 要求掩码内所有状态成立,OnAny 表示任一状态成立,OnNot 额外排除状态。
On:状态样式¶
primary := style.New().Background(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Fill)).
TextColor(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].OnFill)).
Radius(8).
Cursor(style.CursorPointer).
On(style.Hovered, style.New().Background(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Hover))).
On(style.Pressed, style.New().Background(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Pressed)).Scale(0.96)).
On(style.Disabled, style.New().Opacity(0.5))
常见状态位:style.Hovered、Pressed、FocusVisible、Disabled、Checked、Selected、Invalid、Loading 等,可用 | 组合(如 OnAny(style.Hovered|style.Focused, ...))。
把 Model 布尔值 接进同一条路径——声明每帧由 View 重建,条件直接折叠进构造:
if model.Highlighted {
primary = primary.Background(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Hover))
}
按钮变体¶
按钮的语义颜色、Hover/Pressed/Disabled 反馈和焦点环由内置变体统一处理,
通过 Variant 选择操作层级;几何参数仍由按钮组件负责:
button.New("保存").Variant(button.Primary)
button.New("取消").Variant(button.Secondary)
button.New("删除").Variant(button.Danger)
button.New("更多").Variant(button.Ghost)
需要额外的局部样式时,在变体之上追加实例声明:
resolve.Scope:子树默认¶
作用域入口在 style/resolve 子包(style 本身是纯声明机制,不依赖框架,需要上下文的入口
都在它旁边),下文按 resolve 引用:
resolve.Scope(
style.New().FontSize(14).TextColor(style.Solid(activeTheme.Colors.Foreground)),
layout.Column(
text.New("继承字号与颜色"),
button.New("保存").Style(primary),
).Gap(8),
)
Scope 向下传递;实例 Style 仍优先。Scope 不会改全局 Theme。
Part:复合控件零件¶
根 Style 作用在外层盒子;内部用 Part:
barStyle := style.New().Background(style.RGBA(0x111827cc)).
Part(style.PartTrack, style.New().Height(6).Background(style.Solid(activeTheme.Colors.SurfaceRaised))).
Part(style.PartFill, style.New().Background(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Fill))).
Part(style.PartLabel, style.New().TextColor(style.Solid(activeTheme.Colors.MutedForeground)))
progress.Bar("upload", 42).Label("上传").Style(barStyle)
常见 Part:PartContent、PartLabel、PartIcon、PartTrack、PartFill、PartThumb、PartPanel、PartItem、PartBackdrop、PartPlaceholder、PartPrefix、PartSuffix 等。
字段类控件(Select、Input 组)常用 PartContent 表示字段表面。
颜色与主题值¶
颜色直接引用主题的 Colors 字段,便于明暗主题切换:
style.New().Background(style.Solid(activeTheme.Colors.Surface))
style.New().TextColor(style.Solid(activeTheme.Colors.Foreground))
style.New().Background(style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Fill))
字面色与透明度:
style.RGB(0x0078d4) // 0xRRGGBB
style.RGBA(0x00000030) // 0xRRGGBBAA
style.WithAlpha(activeTheme.Colors.Ring, 0.5) // 复用主题色改 alpha
⚠️ 主题值在声明处求值:不要把带主题色的 Style 缓存进 Model 字段——主题切换后 缓存的值不会更新。声明应在渲染路径上每次构造。
渐变与自定义绘制¶
自定义组件需要绘制渐变时,可以先把颜色源转成画刷,再交给公共绘制函数:
brush, ok := resolve.Brush(style.LinearGradient(
style.ColorStop(0, style.Solid(activeTheme.Colors.Tones[theme.TonePrimary].Fill)),
style.ColorStop(1, style.Solid(activeTheme.Colors.Tones[theme.ToneDanger].Fill)),
))
if ok {
draw.Fill(gtx, image.Rect(0, 0, 160, 32), 6, brush)
}
resolve.Color 用于取得单个具体色;draw.FillRRect 用于已有
clip.RRect 的形状。不要把渐变绘制扩展成浮层系统。
主题¶
启动时¶
默认主题入口来自 github.com/qianniancn/flowui/theme:
直接使用 theme.Light() / theme.Dark(),或调整种子后构造主题。
默认风格采用 Slate 灰阶、Indigo 强调色、1dp 边框和 32dp 常规控件;
明暗截图与尺寸规范见 默认主题与外部风格。
Light / Dark 提供默认靛蓝;New / NewDark 使用传入的种子颜色。
seed := theme.DefaultSeed()
seed.Primary = color.NRGBA{R: 0x00, G: 0x78, B: 0xd4, A: 0xff}
ui.Run(ui.NewProgram(Model{}, Update, View),
window.Theme(theme.NewDark(seed)), // 浅色使用 theme.New(seed)
window.Locale(window.LanguageChinese),
)
组件的局部尺寸和外观通过实例 .Style(...) 调整;全局主题只承载共享语义色、字体、阴影和动效。
运行时(多窗口 Application)¶
替换会在目标窗口的事件循环中生效;接口用法见 11-多窗口。
字体¶
默认主题使用 sans-serif 和 monospace 字体族,并允许 Gio 使用系统字体
回退。这样不需要额外资源就能显示不同语言;但不同系统的字形可能略有差异。
系统字体回退会建立完整的系统字体索引,启动和内存开销都可能明显增加;只需要
固定字体时,建议关闭它并提供完整的字体回退集合。
需要固定字体时,可以把字体文件随程序打包。opentype.ParseCollection 支持 TTF、OTF
和 TTC,直接使用 Gio 的字体解析入口:
package main
import (
_ "embed"
"gioui.org/font/opentype"
"github.com/qianniancn/flowui/theme"
)
//go:embed fonts/Inter-Regular.ttf
var interRegular []byte
func appTheme() theme.Theme {
faces, err := opentype.ParseCollection(interRegular)
if err != nil {
panic(err)
}
t := theme.Light()
t.Typography.Typeface = "Inter, sans-serif"
t.Fonts.Collection = faces
t.Fonts.SystemFonts = false
return t
}
Theme.Fonts.Collection 应在应用启动前准备好,多个窗口可以共享解析后的字体
数据;每个窗口仍会创建自己的 Shaper。关闭系统字体后,请确保集合包含应用需要
的字符范围,必要时将多个字体集合并到同一个 Collection。
将上述字体配置放入自己的应用,并提供 go:embed 引用的字体文件。
需要分析启动与绘制开销时,可在应用中使用标准库 runtime/pprof 或系统进程分析器。
几何常用项¶
style.New().Width(200).Height(40)
style.New().MinWidth(100).MaxWidth(400)
style.New().Padding(12) // 全边;也可用 Padding(纵向, 横向)
style.New().Margin(8)
style.New().Radius(8) // 或四角分别设置
style.New().BorderWidth(1).BorderColor(style.Solid(activeTheme.Colors.Border))
style.New().BorderBottom(1, style.Solid(activeTheme.Colors.Border))
style.New().BoxShadow(0, 6, 18, 0, style.RGBA(0x00000030))
style.New().Opacity(0.9)
style.New().Cursor(style.CursorPointer)
style.New().Overflow(style.OverflowHidden) // 裁剪溢出
Transition(声明式属性动画)¶
style.New().Background(style.Solid(activeTheme.Colors.Surface)).
Transition(style.PropBackgroundColor, 120*time.Millisecond).
On(style.Hovered, style.New().Background(style.Solid(activeTheme.Colors.SurfaceRaised)))
需要稳定 Key;详见 12-动画。
原则小结¶
- 外观与盒模型 → Style
- 排列策略 → 布局容器
- 状态外观 →
On/Rules,不要为 hover 另建一棵 UI 树 - 主题色 → 直接引用
Colors值,少写死 RGB
下一步¶
实例样式与主题默认外观¶
默认外观由各 widget 组件实现,使用 theme 提供的明暗主题 token。
.Style(...) 仅覆盖当前实例;调整全局语义颜色会影响使用这些角色的组件。