构建

画面就是文件 — 四行开始的 UI 运行时

作者: makemind · 2026年5月13日

客户端课程第 4 讲里画面到了。读 ui://desk 拿回 546 字节,客户端把它当作字符串收下,数了长度就结束了。

这一条线讲的是另一半:那串字符怎么变成画。

四行

{ "type": "page", "title": "Step 1",
  "content": { "type": "center",
    "child": { "type": "text", "text": "The whole screen is this file",
               "style": { "fontSize": 34, "fontWeight": "bold", "color": "#111827" } } } }

233 字节。这就是画面的全部。三个节点。

  • page — 一个画面,带标题。
  • center — 把唯一的子节点放到中间。
  • text — 文字。

是一棵树。page 里有一个 contentcenter 里有一个 child。装多个子节点的节点在第 2 讲出现。

画它的那一侧

final rt = MCPUIRuntime(enableDebugMode: false);
// screen = jsonDecode(文件内容)
await tester.runAsync(() => rt.initialize(screen));
await tester.pumpWidget(RepaintBoundary(
  key: key,
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: Builder(
      builder: (c) => rt.buildUI(
        context: c,
        onToolCall: (tool, params) async => fired.add(tool),
      ),
    ),
  ),
));

把 map 交给 initializebuildUI 返回一个 widget。中间没有关于画面的任何判断。

这个宿主跑在测试里,是为了能截图。在真实应用里,runApp 站在 pumpWidget 的位置 — 上下两行不变。

onToolCall 到第 4 讲才用。现在只是把名字记下来。

要点在于「什么不在」

这条线的宿主只有一个文件:host/test/capture_test.dart。五讲下来它几乎不变 — 五个画面都由同一个宿主来画。

所以这门课最重要的检查不是「有没有」,而是 「有没有不在」

# 画面上看得见的字若在程序里,就失败。
grep -rq 'The whole screen is this file' $SRC && \
  die "step1: the words on the screen are inside the host"

# 宿主若在手写 widget,就失败。
grep -rqE '\bText\(|\bColumn\(|\bRow\(|\bElevatedButton\(' $SRC && \
  die "step1: the host builds widgets by hand"

$SRChost/test host/pubspec.yamlhost/build 被排除了 — 编译产物里原样嵌着这些字符串,扫进去这项检查就永远通过。第 4 讲的检查刚接上时,正是因此误报过一次。

此内容需要开发者或更高等级

登录并升级您的方案即可继续阅读。

查看方案
Twitter