ui 概览
Pythonista 风格原生 UI 组件与交互。
Pythonista 风格原生 UI 组件与交互。
#预期效果
运行示例后会出现一个手动 frame 布局的原生 sheet,点击按钮会直接修改按钮标题。
#适用场景
Pythonista 风格命令式 UI,使用 frame/flex 手动布局;新 MiniApp 默认优先用 appui。
#先选写法
| 需求 | 首选 | 原因 |
|---|---|---|
迁移 Pythonista 风格的 ui 脚本 | ui | 代码通常已经按 View、frame、action(sender) 组织。 |
| 新建设置页、表单、列表或多页面工具 | appui | 状态、导航、输入和动态列表更容易维护。 |
| 需要 Sprite、触摸循环、物理或逐帧绘制 | scene | 场景生命周期和渲染循环更适合动画与小游戏。 |
| 只需要桌面小组件 | widget | 小组件有独立的 WidgetKit 约束和刷新模型。 |
| 只想生成一张图片或画布 | ui.ImageContext / CanvasView | 不需要完整页面时,离屏绘图更轻。 |
#标准示例
已复制
import ui
def tapped(sender):
sender.title = "Tapped"
view = ui.View(frame=(0, 0, 320, 220))
view.name = "ui demo"
button = ui.Button(frame=(40, 80, 240, 44), title="Tap")
button.action = tapped
view.add_subview(button)
view.present("sheet")
#写 ui 的心智模型
- 根视图:创建一个有明确尺寸的
ui.View(frame=...)。 - 子视图:按钮、标签、输入框和图片用
add_subview(...)挂到父视图。 - 布局:用
frame设定初始位置,用flex处理简单的横竖屏变化。 - 交互:控件回调接收
sender,在回调里修改标题、文本、颜色或子视图。 - 呈现:脚本末尾调用一次
present(...),不要在导入模块时弹出多个页面。 - 迁移:当状态、列表和导航开始变复杂,就把新页面迁到
appui。
#API 参考
| 类型 | API | 签名 | 说明 |
|---|---|---|---|
| class | Color | Color(r: float, g: float, b: float, a: float=1.0) -> None | 颜色类,兼容Pythonista的ui.Color |
| class | Point | Point(x: Union[float, Sequence[float]]=0.0, y: float=0.0) -> None | 二维点,兼容 Pythonista 的 ui.Point |
| class | Size | Size(w: Union[float, Sequence[float]]=0.0, h: float=0.0) -> None | 尺寸,兼容 Pythonista 的 ui.Size |
| class | Rect | Rect(x: Union[float, Sequence[float]]=0.0, y: float=0.0, w: float=0.0, h: float=0.0) -> None | 矩形,兼容 Pythonista 的 ui.Rect |
| class | Transform | Transform(a: float=1.0, b: float=0.0, c: float=0.0, d: float=1.0, tx: float=0.0, ty: float=0.0) -> None | 2D 仿射变换,兼容 Pythonista 的 ui.Transform |
| class | Touch | Touch(location: Sequence[float]=..., prev_location: Optional[Sequence[float]]=..., phase: str=..., touch_id: int=..., timestamp: Union[int, float]=...) -> None | 触摸事件,兼容 Pythonista 的 ui.Touch(简化实现) |
| class | GestureSender | GestureSender(view: Optional[View]=..., state: int=..., location: Sequence[float]=..., translation: Sequence[float]=..., scale: float=..., direction: int=...) -> None | 手势识别器回调的 sender 对象,兼容 Pythonista(state, location, translation, scale, direction) |
| class | GestureRecognizer | GestureRecognizer(gesture_id: str) -> None | 手势识别器基类,兼容 Pythonista(action(sender)) |
| class | TapGestureRecognizer | TapGestureRecognizer() -> None | 点击手势(Pythonista 兼容) |
| class | PanGestureRecognizer | PanGestureRecognizer() -> None | 拖动手势(Pythonista 兼容) |
| class | PinchGestureRecognizer | PinchGestureRecognizer() -> None | 捏合手势(Pythonista 兼容) |
| class | SwipeGestureRecognizer | SwipeGestureRecognizer() -> None | 滑动手势(Pythonista 兼容),direction: LEFT=1, RIGHT=2, UP=3, DOWN=4 |
| class | LongPressGestureRecognizer | LongPressGestureRecognizer() -> None | 长按手势(Pythonista 兼容) |
| function | parse_color | parse_color(color: Any) -> Tuple[float, float, float, float] | 解析颜色参数为RGBA元组,支持语义色(systemBackground 等)自适应深色/浅色模式 |
| class | Font | Font(name: str=..., size: float=...) -> None | 字体类,兼容Pythonista的ui.Font |
| function | parse_font | parse_font(font: Any) -> Tuple[str, float] | 解析字体参数为(name, size)元组 |
| class | View | View(frame: Optional[_Frame]=..., **kwargs: Any) -> None | 视图基类,所有UI组件的父类 |
| class | ButtonItem | ButtonItem(title: str=..., image: Any=..., action: Optional[Callable[..., Any]]=..., **kwargs: Any) -> None | 导航栏按钮项,用于 left_button_items / right_button_items |
| class | Button | Button(frame: Optional[_Frame]=..., title: str=..., **kwargs: Any) -> None | 按钮组件 |
| class | Label | Label(frame: Optional[_Frame]=..., text: str=..., **kwargs: Any) -> None | 文本标签组件 |
| class | TextField | TextField(frame: Optional[_Frame]=..., text: str=..., placeholder: str=..., **kwargs: Any) -> None | 单行文本输入组件 |
| class | TextView | TextView(frame: Optional[_Frame]=..., text: str=..., **kwargs: Any) -> None | 多行文本输入组件 |
| class | ImageView | ImageView(frame: Optional[_Frame]=..., **kwargs: Any) -> None | 图片显示组件 |
| class | WebView | WebView(frame: Optional[_Frame]=..., **kwargs: Any) -> None | 网页视图 |
| class | ActivityIndicator | ActivityIndicator(frame: Optional[_Frame]=..., **kwargs: Any) -> None | 加载指示器 |
| class | TableView | TableView(frame: Optional[_Frame]=..., **kwargs: Any) -> None | 表格视图 |
| class | ListDataSource | ListDataSource() -> None | TableView 数据源协议 子类实现 tableview_number_of_sections, tableview_number_of_rows, tableview_cell_for_row 等 |
| class | TableViewCell | TableViewCell(style: str=..., **kwargs: Any) -> None | 表格行单元,兼容 Pythonista |
| class | Switch | Switch(frame: Optional[_Frame]=..., value: bool=..., **kwargs: Any) -> None | 开关组件 |
| class | Slider | Slider(frame: Optional[_Frame]=..., **kwargs: Any) -> None | 滑块组件 |
| class | SegmentedControl | SegmentedControl(frame: Optional[_Frame]=..., **kwargs: Any) -> None | 分段控制器 |
| class | DatePicker | DatePicker(frame: Optional[_Frame]=..., **kwargs: Any) -> None | 日期时间选择器 |
| class | ProgressView | ProgressView(frame: Optional[_Frame]=..., **kwargs: Any) -> None | 进度条组件,兼容 Pythonista 的 ui.ProgressView |
| class | Stepper | Stepper(frame: Optional[_Frame]=..., **kwargs: Any) -> None | 步进器组件,兼容 Pythonista 的 ui.Stepper |
| class | NavigationView | NavigationView(view: Optional[View]=..., **kwargs: Any) -> None | 导航视图,支持 push_view / pop_view 兼容 Pythonista 的 ui.NavigationView |
| class | ScrollView | ScrollView(frame: Optional[_Frame]=..., **kwargs: Any) -> None | 滚动视图,兼容 Pythonista 的 ui.ScrollView 可容纳超出可见区域的子视图并支持滚动 |
| class | Path | Path() -> None | 路径类,兼容 Pythonista 的 ui.Path 在 ImageContext 内使用 |
| class | GState | GState() | with ui.GState(): 保存和恢复绘图状态 |
| class | ImageContext | ImageContext(width: float, height: float) -> None | 离屏绘图上下文,兼容 Pythonista 的 ui.ImageContext with ui.ImageContext(100, 100) as ctx: ui.set_color('red') ui.fill_rect(0, 0, 50, 50) img = ctx.get_image() # 可在 with 块内调用,会立即结束上下文并返回图像 |
| class | Image | Image(data: bytes=...) -> None | 图像类,兼容 Pythonista 的 ui.Image |
| function | begin_image_context | begin_image_context(width: float, height: float) -> None | Start an image drawing context (Pythonista-compatible standalone function). |
| function | end_image_context | end_image_context() -> None | End the current image drawing context. |
| function | get_image_from_current_context | get_image_from_current_context() -> Image | Capture the current drawing context as an Image. Call before end_image_context() to get the drawn image, or after end_image_context() to retrieve the last captured result. |
| function | set_color | set_color(color: _ColorLike) -> None | 设置当前绘图颜色 |
| function | fill_rect | fill_rect(x: float, y: float, w: float, h: float) -> None | 填充矩形 |
| function | stroke_rect | stroke_rect(x: float, y: float, w: float, h: float) -> None | 描边矩形 |
| function | draw_string | draw_string(text: str, rect: Any=..., font: _FontLike=..., color: _ColorLike=..., alignment: int=..., line_break_mode: int=...) -> None | 绘制字符串 rect: (x, y, w, h) 元组 |
| class | CanvasView | CanvasView(frame: Optional[_Frame]=..., **kwargs: Any) -> None | 画布视图,通过 render(draw_func) 自绘内容 兼容 Pythonista 的 ui 画布用法 |
| function | get_screen_size | get_screen_size() -> Tuple[float, float] | 获取屏幕尺寸 |
| function | get_window_size | get_window_size() -> Size | 获取窗口尺寸(同 get_screen_size) |
| function | get_keyboard_frame | get_keyboard_frame() -> Tuple[float, float, float, float] | 获取键盘在屏幕上的 frame (x, y, width, height);未显示时为 (0, 0, 0, 0)(Pythonista 兼容) |
| function | get_ui_style | get_ui_style() -> str | 获取当前 UI 风格:'light' 或 'dark'(Pythonista 兼容) |
| function | measure_string | measure_string(text: str, max_width: float=..., font: _FontLike=..., alignment: int=..., line_break_mode: int=...) -> Tuple[float, float] | 测量字符串尺寸,返回 (width, height) |
| function | delay | delay(seconds: float, func: Callable[..., Any]) -> None | 延迟 seconds 秒后执行 func(Pythonista 兼容)。可用 cancel_delays() 取消未执行的 delay。 |
| function | cancel_delays | cancel_delays() -> None | 取消所有通过 delay() 调度且尚未执行的回调(Pythonista 兼容) |
| function | in_background | in_background(fn: Callable[..., Any]) -> Callable[..., None] | 装饰器:在后台线程执行函数 |
| function | animate | animate(animation: Callable[[], Any], duration: float=..., delay_sec: float=..., completion: Optional[Callable[[], Any]]=...) -> None | 执行动画。按指定延迟执行动画回调,并在 duration 秒后执行 completion。注意:回调异步执行,适合轻量属性更新。 |
| function | convert_point | convert_point(point: Any=..., from_view: Optional[View]=..., to_view: Optional[View]=...) -> Point | 坐标转换。from_view/to_view 为 None 表示窗口坐标系 |
| function | convert_rect | convert_rect(rect: Any=..., from_view: Optional[View]=..., to_view: Optional[View]=...) -> Rect | 矩形坐标转换 |
| function | set_blend_mode | set_blend_mode(mode: int) -> None | 设置绘图混合模式 (BLEND_* 常量) |
| function | set_shadow | set_shadow(color: Optional[_ColorLike], offset_x: float, offset_y: float, blur_radius: float) -> None | 设置阴影。color 为 None 时清除阴影 |
| class | autoreleasepool | autoreleasepool() | autoreleasepool 上下文(Python 中为 no-op) |
| function | load_view | load_view(name: Optional[str]=..., bindings: Optional[Dict[str, Any]]=..., stackframe: Any=..., verbose: bool=...) -> View | 加载 .pyui 文件。 name: 文件名(不含扩展名会自动加 .pyui) bindings: 名称到可调用对象的映射,用于绑定 action 等 |
| function | load_view_str | load_view_str(json_str: str, bindings: Optional[Dict[str, Any]]=..., stackframe: Any=..., verbose: bool=...) -> View | 从 JSON 字符串加载视图 |
| function | close_all | close_all(animated: bool=...) -> None | 关闭所有已展示的 UI 界面(Pythonista 兼容) |
| function | dump_view | dump_view(view: View, indent: int=...) -> None | 调试输出视图树(Pythonista 兼容) |
#失败路径
| 情况 | 应该怎么处理 |
|---|---|
| 页面空白 | 确认已创建根 ui.View,设置了非零 frame,并调用 present(...)。 |
| 点击无反应 | 回调要赋函数对象,例如 button.action = tapped,不要写成 tapped()。 |
| 布局错位 | 检查 frame、flex 和父视图尺寸;复杂响应式页面优先改用 appui。 |
| 输入或列表状态难维护 | 把新页面迁到 appui.State、Form、List 和 NavigationStack。 |
#发布前检查
| 检查项 | 合格标准 |
|---|---|
| 根视图 | 有非零 frame,并设置清晰的 name 或标题。 |
| 子视图 | 所有控件都已加入父视图,位置不依赖魔法数字之外的隐式状态。 |
| 回调 | action 传函数对象,不写成 action=handler()。 |
| 布局 | 横竖屏或不同窗口尺寸下,关键控件不会移出可见区域。 |
| 边界 | 新功能不混用 ui 和 appui 组件树。 |
#使用规则
- 不要把 ui 组件和 appui 组件混在同一棵界面树里。
- 需要响应式原生表单、列表或导航时优先选 appui。
- ui 页面必须显式设置 frame/flex,并通过 present(...) 呈现。