widget
Python 编写 WidgetKit 小组件:参数、状态、布局与发布到桌面。
用 Python 描述主屏、锁屏与 StandBy 小组件;由 PythonIDE + WidgetKit 负责渲染、刷新与桌面交互。
边界:widget是 WidgetKit 时间线模型,不是普通 App 页面。不要用 appui 做小组件,不要用 scene 做 60fps 游戏画布。脚本只描述布局、参数、状态与时间线,结尾必须w.render()。
#模块概览
| 项 | 说明 |
|---|---|
| 导入 | import widget |
| 适合做什么 | 计数器、进度、图表卡片、锁屏 accessory、可点按钮/开关 |
| 入口 | w = widget.Widget(...) → 组合节点 → w.render()(只创建一个 Widget) |
| 可调参数 | widget.param.text/color/number/slider/bool/choice/file — 预览面板可编辑 |
| 桌面交互 | widget.state.int/bool/... → count.increment()、done.toggle() 等 action |
| 尺寸适配 | widget.context、widget.family_value(...) 按 small/medium/large 分支 |
| 发布 | Widget Studio 运行后发布;文档 previewWidget 仅预览 → 从脚本到桌面 |
#快速开始
运行下面脚本会打开小组件预览(非 AppUI 全屏)。用环形图展示饮水进度,+ / − 在桌面改写杯数。
预期效果:小组件预览面板显示与脚本一致的布局与交互。
已复制
import widget
goal = widget.param.number("每日杯数", 8, min=1, max=12)
glasses = widget.state.int("glasses", 0)
accent = widget.param.color("主色", "#38BDF8")
w = widget.Widget(background=("#F0F9FF", "#0C4A6E"), padding=14)
w.text("饮水打卡", size=16, weight="semibold").line_limit(1).min_scale(0.72)
w.ring_chart(
min(int(glasses), int(goal)),
total=int(goal),
label=f"{int(glasses)}/{int(goal)} 杯",
color=accent,
)
with w.row(spacing=10):
(
w.button("−", action=glasses.decrement(), style="plain")
.line_limit(1)
.min_scale(0.72)
)
(
w.button("+", action=glasses.increment(), background=accent, color="#FFFFFF")
.line_limit(1)
.min_scale(0.72)
)
w.render()
要点:
widget.param.number(...):预览里可调每日目标杯数。widget.state.int(...)+ring_chart(...):桌面点击后环与标签同步刷新。with w.row(...):横排多个按钮,比纵向堆叠更省高度。.line_limit(1).min_scale(...):防止 small/medium 文字被裁切。
#Widget 预览示例
#每日一言(参数 + 尺寸分支)
纯展示卡片:无 widget.state,演示 param.text、symbol 与 family_value;small 隐藏出处行。
预期效果:小组件预览面板显示与脚本一致的布局与交互。
已复制
import widget
quote = widget.param.text("名言", "专注一件事,做到极致。")
author = widget.param.text("出处", "— 佚名")
ink = widget.param.color("文字色", "#E2E8F0")
title_size = widget.family_value(17, small=15, large=20)
body_size = widget.family_value(15, small=13, large=17)
w = widget.Widget(background=("#0F172A", "#020617"), padding=16)
with w.row(spacing=8):
w.symbol("quote.bubble.fill").color("#60A5FA").font_size(18)
w.text("每日一言", size=14, weight="semibold", color="secondary").line_limit(1)
w.text(quote, size=body_size, color=ink).line_limit(3).min_scale(0.7)
if not widget.context.is_family("small"):
w.text(author, size=12, color="secondary").line_limit(1).min_scale(0.72)
w.render()
#本周专注(折线图)
静态数据折线 + 可调曲线色;适合仪表盘类小组件,不涉及桌面点击。
预期效果:小组件预览面板显示与脚本一致的布局与交互。
已复制
import widget
title = widget.param.text("标题", "本周专注")
accent = widget.param.color("曲线色", "#6366F1")
minutes = [25, 40, 15, 50, 30, 45, 20]
chart_h = widget.family_value(48, small=36, large=56)
w = widget.Widget(background=("#F5F3FF", "#1E1B4B"), padding=14)
with w.row(spacing=8):
w.symbol("brain.head.profile").color(accent).font_size(18)
w.text(title, size=16, weight="semibold").line_limit(1).min_scale(0.72)
w.line_chart(minutes, color=accent, height=chart_h, fill=True)
if not widget.context.is_family("small"):
w.text("分钟 / 天(示例数据)", size=11, color="secondary").line_limit(1)
w.render()
#心智模型
- 根容器:
w = widget.Widget(background=..., padding=..., style="clean")— 只管整体背景与边距。 - 内容节点:
text、value、symbol、image、progress、line_chart等。 - 布局:多数卡片用
row()/column()/grid();精确定位才用canvas()/table()。 - 参数:
widget.param.*在构建 UI 之前声明,供预览面板与 Studio 持久化。 - 状态:
widget.state.*生成桌面可执行的increment/toggle/setaction。 - 上下文:
widget.context.family、content_width、content_height做尺寸分支。 - 收尾:脚本末尾有且仅有一次
w.render()。
#与 appui / scene 怎么选
| 需求 | 首选 | 原因 |
|---|---|---|
| 主屏/锁屏/StandBy 卡片 | widget | WidgetKit 时间线与系统刷新预算 |
| 设置页、表单、导航 App | appui | 完整交互与滚动列表 |
| 触摸游戏、逐帧动画 | scene | 实时帧循环,不是 Widget 模型 |
| 教材海龟绘图 | turtle | 单次画布,非桌面组件 |
#WidgetKit 能做什么 / 不能做什么
| 适合 | 不适合 |
|---|---|
| 静态/准静态展示、数字过渡动画 | 文本输入框、长列表滚动 |
| 按钮、开关、链接(AppIntent) | WebView、视频、复杂手势 |
| 时间线刷新、深色/透明/渐变背景 | 60fps 实时动画、任意 Python 回调在扩展内执行 |
#API 参考
#速查
| API | 作用 |
|---|---|
Widget(...) | 根容器;唯一入口 |
w.render() | 提交 IR,生成预览/桌面时间线 |
widget.param.text/color/number/slider/bool/choice/file | 用户可调参数 |
widget.state.int/float/bool/str/list | 持久状态 + action 工厂 |
count.increment() / done.toggle() | 按钮/开关的 action= |
widget.context | 当前 family 与内容区尺寸 |
widget.family_value(d, small=..., large=...) | 按尺寸返回值 |
w.timeline(...) | 多条时间线条目(见专题文档) |
widget.SMALL / MEDIUM / LARGE | 尺寸常量 |
#widget.param 声明
| 方法 | 用途 |
|---|---|
text(name, default) | 标题、文案 |
color(name, default) | 十六进制或语义色 |
number(name, default, min=, max=) | 整数/小数参数 |
slider(name, default, min=, max=, step=) | 滑块 |
bool(name, default) | 开关类参数(预览面板,不是桌面 state) |
choice(name, options, default=) | 下拉选项 |
file(name, default, extensions=) | 图片等资源路径 |
第一个参数 name 是参数 ID,同时作为预览面板默认标题。
#widget.state 与交互
已复制
count = widget.state.int("count", 0)
done = widget.state.bool("done", False)
w.button("加 1", action=count.increment())
w.button("减 1", action=count.decrement(by=2))
w.toggle("完成", state=done) # 用 state=,不要手写 toggle action
状态 key 变更或删除后,需重新发布小组件,否则桌面可能仍读写旧 key。
#尺寸与布局
已复制
ctx = widget.context
if ctx.is_family("small"):
w.text("简版", size=14)
elif ctx.is_family("large"):
w.text("完整版", size=18)
size = widget.family_value(14, small=12, medium=14, large=18)
#常见错误
| 错误写法 | 后果 | 修正 |
|---|---|---|
忘记 w.render() | 预览空白 | 脚本末尾调用一次 |
创建多个 Widget() | 行为未定义 | 只保留一个根 Widget |
SpriteNode / appui.Button 混入脚本 | 构建失败 | 只用 widget.Widget 节点 API |
按钮 action 写死字符串 | 点击无反应 | 用 widget.state 的 .increment() 等 |
widget.param 写在 render() 之后 | 参数面板缺失 | 先声明参数再构建 UI |
| small 塞满 large 文案 | 文字裁切 | family_value / is_family 分支 + line_limit |
| 改 state key 不重新发布 | 桌面计数错乱 | 重新运行、发布,必要时删桌面组件重装 |
传 Python 函数给 action= | 需拉起 App 执行 | 交互优先 widget.state;见 交互 |
w.symbol(..., color=, size=) | TypeError | w.symbol("name").color(c).font_size(n) |
w.toggle(..., tint=) | TypeError | 用 color= |
w.link(..., icon=).line_limit() | AttributeError | 带 icon 的 link 不可链修饰符 |
#失败路径
| 情况 | 处理 |
|---|---|
| 预览空白 | 检查 w.render()、是否只创建一个 Widget、脚本是否混入 appui/scene |
| 参数不出现 | 确认 widget.param.* 在 UI 构建前,且 name 非空 |
| 点击没反应 | action 必须来自 widget.state;开关用 w.toggle(..., state=done) |
| 桌面仍旧内容 | 重新运行并发布;删除主屏小组件后重新添加 |
| 文本被裁切 | .line_limit(1).min_scale(0.65);small 隐藏次要行 |
| 预览与桌面不一致 | 查 排错;确认已发布最新构建 |
#发布到桌面(简表)
文档里的 previewWidget 不能直接上主屏。完整步骤见 从脚本到桌面。
- 把脚本复制到 Widget Studio 项目
- Studio 运行预览 → 调
widget.param→ 切换 family 检查裁切 - 构建成功且提示「已发布」类消息
- 在 iOS 主屏幕 + 添加 Python IDE 小组件
- 在桌面点按钮/开关验证
widget.state
#专题文档
| 文档 | 用途 |
|---|---|
| 从脚本到桌面 | 发布流程与检查清单 |
| 布局与尺寸 | row/column/grid、family 预算 |
| 参数面板 | widget.param 详解 |
| 状态和交互 | state、按钮、开关、链接 |
| 时间线和动画 | timeline、content_transition |
| 资源与外观 | 颜色、渐变、图片、SVG |
| 排错 | 预览/桌面不一致 |
| API 索引 | 按任务选 API |
| API 参考 | 全部节点签名 |
#相关模块
#相关文档
#预期效果
运行示例后,界面应出现文档描述的目标结果;若与预期不符,先看「失败路径」并按返回值或日志排查。