widget 资源与外观
渐变/双色背景、accentable 染色、图片/SVG/Symbol、透明与隐私占位。
WidgetKit 负责浅色/深色、透明、系统染色(Tinted)等外观策略。脚本只声明意图——颜色、背景、图标、图片路径——不要用手写遮罩去模拟系统行为。
边界:本篇讲背景、图标、图片、SVG 与 .accentable() 等外观 API。可调默认配色用 widget.param;布局防裁切见 布局与尺寸。
#本篇目标
| 项 | 说明 |
|---|---|
| 背景 | 纯色、(浅色, 深色)、渐变 dict、background_image |
| 系统表面 | container_background、transparent_background、content_margins |
| 图标 | w.symbol(SF Symbol)、w.svg(矢量文件) |
| 位图 | w.image、widget.param.file、widget.save_image |
| 染色 | .accentable(True/False) 控制是否参与系统 Tinted |
| 隐私 | .privacy_sensitive()、.redacted() 锁屏占位 |
#快速开始
渐变根背景 + 浅色文字,无需外部图片即可预览。
预期效果:小组件预览面板显示与脚本一致的布局与交互。
import widget
w = widget.Widget(
background={
"gradient": ["#4F46E5", "#7C3AED", "#DB2777"],
"direction": "vertical",
},
padding=16,
)
with w.row(spacing=10):
w.symbol("sparkles", scale="medium").color("#FFFFFF")
w.text("今日灵感", size=16, weight="semibold", color="#FFFFFF").line_limit(1)
w.text("写一段只给自己的话", size=13, color="#E0E7FF").line_limit(2).min_scale(0.72)
w.render()
要点:
Widget(background=...)支持单色、("#F8FAFC", "#0F172A")双色、或{"gradient": [...], "direction": "vertical"}。- 文字颜色可写十六进制或语义名
"secondary"(随系统外观变化)。 - 渐变方向常用
"vertical"/"horizontal"。
#心智模型
脚本声明外观意图(颜色 / 背景 / 资源路径 / accentable)
↓
PythonIDE 生成 Widget IR + 资源清单
↓
WidgetKit 按系统外观(浅色 / 深色 / Tinted / 透明)渲染
#背景 API 怎么选
| API | 作用 |
|---|---|
Widget(background=...) | 卡片内容区底色(纯色 / 双色 / 渐变) |
w.container_background(..., removable=) | 系统 Widget 容器背景(可请求移除) |
w.background_image(...) | 铺满容器的背景图 + 遮罩 |
w.transparent_background(True) | 请求透明(宿主是否允许由系统决定) |
w.content_margins(False) | 边到边布局,去掉系统内容边距 |
普通卡片用 Widget(background=...);需要照片墙时用 background_image;需要融入主屏壁纸时组合 transparent_background + container_background(..., removable=True)。
#资源路径约定
| 来源 | 写法 |
|---|---|
| 脚本同目录 | "cover.jpg" |
assets/ 子目录 | "assets/icon.svg" |
| 用户文件参数 | widget.param.file("封面", "assets/cover.jpg", extensions=[...]) |
| 运行时注册 | widget.save_image(url_or_bytes, "avatar") → w.image("avatar") |
| 远程 URL | w.image("https://example.com/pic.png")(需网络,注意缓存) |
深色模式应为图片提供 dark 变体,不要只靠压暗浅色图。
#外观示例
#系统染色(accentable)
Tinted / 强调色模式下,只有 .accentable(True) 的节点会跟随系统染色;标题等需保持自定义色时显式 .accentable(False)。
预期效果:小组件预览面板显示与脚本一致的布局与交互。
import widget
show = widget.param.text("标题", "专注电台")
subtitle = widget.param.text("副标题", "深度工作 · 25 分钟")
w = widget.Widget(background=("#F8FAFC", "#0F172A"), padding=14)
w.container_background(("#F8FAFC", "#0F172A"), removable=True)
w.content_margins(False)
(
w.text(show, size=16, weight="semibold")
.line_limit(1)
.min_scale(0.72)
.accentable(False)
)
(
w.text(subtitle, size=12, color="secondary")
.line_limit(1)
.min_scale(0.72)
.accentable(False)
)
(
w.symbol(
"waveform.circle.fill",
rendering="palette",
palette=["#F59E0B", "#2563EB"],
variant="fill",
scale="large",
)
.accentable(True)
)
w.render()
w.container_background(..., removable=True):允许系统在支持场景移除容器底。w.symbol(..., rendering="palette", palette=[...]):SF Symbol 多色渲染。- 图标参与染色 →
.accentable(True);文案保持设计色 →.accentable(False)。
#分层卡片(layer 背景)
用 layer 叠一块圆角面板,不必整张 Widget 都上色。
预期效果:小组件预览面板显示与脚本一致的布局与交互。
import widget
accent = widget.param.color("面板强调", "#10B981")
task = widget.param.text("待办", "整理发票拍照")
w = widget.Widget(background=("#F1F5F9", "#020617"), padding=14)
w.text("桌面便签", size=14, color="secondary").line_limit(1)
with w.layer(background=("#FFFFFF", "#1E293B"), corner_radius=12, padding=12):
with w.row(spacing=8):
w.symbol("checkmark.circle").color(accent).font_size(18)
w.text(task, size=15, weight="semibold").line_limit(1).min_scale(0.72)
w.text("下班前完成", size=12, color="secondary").line_limit(1)
w.render()
#图片背景(background_image + 文件参数)
浅色/深色各选一张图;scrim="bottom" 在底部加渐变遮罩,保证白字可读。
预期效果:小组件预览面板显示与脚本一致的布局与交互。
import widget
light_bg = widget.param.file("浅色背景", "", extensions=["jpg", "jpeg", "png"])
dark_bg = widget.param.file("深色背景", "", extensions=["jpg", "jpeg", "png"])
title = widget.param.text("标题", "周末登山")
w = widget.Widget(padding=14)
w.background_image(
(light_bg, dark_bg),
content_mode="fill",
focal="center",
scrim="bottom",
scrim_opacity=0.48,
)
w.text(title, size=18, weight="bold", color="#FFFFFF").line_limit(1).min_scale(0.72)
w.text("海拔 1280m", size=12, color="#E2E8F0").line_limit(1)
w.render()
- 预览侧栏用文件参数选图;路径相对于 widget 脚本或项目
assets/。 focal="topTrailing"等控制fill裁剪焦点;content_mode="fit"可整张展示不裁剪。
#头像位图(w.image)
角标/封面用 w.image;支持圆角与浅色/深色双资源。
预期效果:小组件预览面板显示与脚本一致的布局与交互。
import widget
avatar = widget.param.file("头像", "", extensions=["png", "jpg", "jpeg"])
frame = widget.family_value(44, small=36, large=52)
w = widget.Widget(background=("#FFFFFF", "#111827"), padding=14)
with w.row(spacing=12):
w.image(avatar, width=frame, height=frame, corner_radius=frame / 2, content_mode="fill")
with w.column(spacing=4):
w.text("Py 开发者", size=15, weight="semibold").line_limit(1).min_scale(0.72).line_limit(1)
w.text("本周 commit +12", size=12, color="secondary").line_limit(1)
w.render()
若有两张图,可写 w.image(light="avatar-light.png", dark="avatar-dark.png", ...) 或元组 w.image(("light.png", "dark.png"), ...)。
#SVG 图标(w.svg)
适合单色可着色的矢量角标;文件放 assets/ 或通过 param.file 选择。
预期效果:小组件预览面板显示与脚本一致的布局与交互。
import widget
icon = widget.param.file("图标", "", extensions=["svg"])
accent = widget.param.color("图标色", "#22C55E")
size = widget.family_value(28, small=24, large=32)
w = widget.Widget(background=("#FAFAFA", "#1C1C1E"), padding=14)
with w.row(spacing=10):
w.svg(icon, width=size, height=size, color=accent, content_mode="fit")
with w.column(spacing=2):
w.text("自定义 SVG", size=15, weight="semibold").line_limit(1).min_scale(0.72).line_limit(1)
w.text("path + viewBox 图标", size=12, color="secondary").line_limit(1)
w.render()
预览侧栏选本地 .svg 文件;简单场景优先 w.symbol(...),免维护文件。
#隐私占位(锁屏敏感信息)
步数等敏感数字在锁屏可模糊;与 时间线和动画 的 numericText 可叠加。
预期效果:小组件预览面板显示与脚本一致的布局与交互。
import widget
steps = widget.state.int("steps", 8420)
w = widget.Widget(background=("#F0FDF4", "#14532D"), padding=14)
(
w.text("今日步数", size=14, color="secondary")
.line_limit(1)
.privacy_sensitive()
)
(
w.value(steps, unit="步")
.monospaced_digit()
.line_limit(1)
.min_scale(0.72)
.privacy_sensitive()
.redacted("placeholder")
)
w.text("锁屏显示占位,解锁后见完整数字", size=11, color="secondary").line_limit(1)
w.render()
.privacy_sensitive():标记敏感内容。.redacted("placeholder"):锁屏显示系统占位样式。
#透明背景
请求透明需同时声明容器可移除与透明开关;桌面是否真透明取决于宿主与系统。
w = widget.Widget(padding=12)
w.transparent_background(True)
w.container_background("clear", removable=True)
w.content_margins(False)
w.text("透明卡片", size=15, weight="semibold").accentable(True)
w.render()
不要在透明卡片上再叠不透明全屏 Widget(background=...),否则透明意图会被盖住。
#API 参考
#背景与容器
# 纯色 / 双色 / 渐变
w = widget.Widget(background="#FFFFFF")
w = widget.Widget(background=("#F8FAFC", "#0F172A"))
w = widget.Widget(background={"gradient": ["#3B82F6", "#8B5CF6"], "direction": "horizontal"})
w.background(("#FAFAFA", "#18181B")) # 运行时改根背景
w.container_background(bg, removable=True) # 系统容器底
w.transparent_background(True) # 请求透明
w.content_margins(False) # 边到边
w.background_image(
("day.jpg", "night.jpg"),
content_mode="fill",
scrim="bottom",
scrim_opacity=0.45,
focal="center",
)
#图标与图片
w.symbol("star.fill", scale="large").color("#F59E0B")
w.symbol("paintpalette.fill", rendering="palette", palette=["#EF4444", "#3B82F6"])
w.svg("assets/logo.svg", width=24, height=24, color="#111827")
w.image("cover.jpg", width=60, height=60, corner_radius=8)
w.image(light="avatar-light.png", dark="avatar-dark.png", rendering_mode="accented")
#外观修饰符
w.text("标题").accentable(False)
w.symbol("bell.fill").accentable(True)
w.text("余额").privacy_sensitive().redacted("placeholder")
#运行时保存图片
# 脚本中下载或生成后注册,供 w.image / background_image 按名称引用
path = widget.save_image("https://example.com/pic.png", "cover")
w.image("cover", width=80, height=80)
完整签名见 API 参考:container_background、background_image、transparent_background、symbol、svg、image、.accentable、.privacy_sensitive、.redacted。
#常见错误
| 错误写法 | 后果 | 修正 |
|---|---|---|
| 染色模式下标题变色 | 未关闭 accentable | 设计色文案 .accentable(False) |
| 图标不跟系统色 | 写了 .accentable(False) | 应用染色的图标 .accentable(True) |
| 深色模式图片发灰 | 只有浅色图 | background_image((light, dark)) 或 image(light=, dark=) |
| 透明不生效 | 只设了 Widget 底色 | transparent_background + container_background(removable=True) |
| SVG 空白 | 路径错或预览无文件 | 放 assets/ 或用 param.file 重选 |
| 背景图字看不清 | 无遮罩 | scrim="bottom" + scrim_opacity |
| 用手写 rect 模拟系统底 | 与 WidgetKit 策略冲突 | 用 container_background / transparent_background |
w.symbol(..., color=, size=) | TypeError | w.symbol("name").color(c).font_size(n) |
#失败路径
| 现象 | 处理 |
|---|---|
| 预览浅色正常、深色异常 | 检查 (light, dark) 是否成对提供 |
| Tinted 模式颜色全乱 | 梳理每个节点的 accentable 默认值与显式设置 |
| 图片不显示 | 路径、extensions、是否已 save_image 注册 |
| SVG 构建报错 | 确认是图标级 SVG(viewBox + path);复杂 SVG 可能需简化 |
| 锁屏数字太清晰 | 加 .privacy_sensitive() + .redacted("placeholder") |
| 透明在桌面无效 | 系统/宿主限制;回退半透明双色背景 |
| 预览与桌面不一致 | 重新发布;查 排错 |
#相关文档
| 文档 | 用途 |
|---|---|
| widget 概览 | 总览 |
| 参数面板 | param.color / param.file |
| 布局与尺寸 | 图标尺寸、family_value |
| 时间线和动画 | numericText 与隐私数字叠加 |
| 排错 | 预览/桌面外观不一致 |
| API 参考 | 完整签名 |
相关 API:widget-api-reference.md