用途
flutter-native-ui 是一个用于构建「原生观感」Flutter 界面的技能,面向 Material 3 与 Cupertino 两套设计体系。它要求所有 UI 决策都以当前 Flutter 和 Dart 官方文档为依据,而不是 React Native 或 Expo 的既有习惯,并建议先检查 flutter --version。官方来源包括 docs.flutter.dev、api.flutter.dev、dart.dev、m3.material.io,以及来自 flutter.dev 的 pub.dev 包。
前提约束
如果项目已经存在设计系统、tokens 或路由器,应当在原有基础上扩展,不要另造第二套主题或第二个导航器。如果组件使用了自定义 ThemeExtension、品牌 token 文件,或用 *Pressable 取代 Material ink,则说明该项目应遵循 flutter-custom-design-system,不应把它「纠正」回原生 Material 3 的样子。
参考资料
该技能提供一个 references 目录,按需只读需要的文件:navigation.md(go_router、标签页、sheets、对话框、转场)、theming.md(ColorScheme、TextTheme、明暗主题、Cupertino)、layout.md(SafeArea、MediaQuery.*Of、断点、slivers)、controls.md(自适应构造器、按钮、输入框、选择器)、animations.md(隐式动画优先、Hero、M3 动效、列表)、icons-and-media.md(Icons.adaptive、图片、相机、音频、视频)、visual-effects.md(阴影、模糊、渐变)、storage.md(偏好设置、安全存储、文件、SQL)。
设计系统选择
需要选定一套应用外壳并保持一致:跨平台(默认)使用 MaterialApp / MaterialApp.router,搭配 Material 3 与 .adaptive() 控件;iOS 优先使用 CupertinoApp / CupertinoApp.router,搭配 Cupertino 组件与 HIG 间距;已有应用则沿用其现有方案,不要为了证明观点而重做整个应用样式。Flutter 会自动适配系统行为(滚动物理、返回手势、文本编辑、触感),但不会自动替换标签栏、应用栏或信息架构,这些需要显式选择。除 .adaptive() 构造器和 Icons.adaptive 外,同一界面不要混用 Material 与 Cupertino 组件。自 Flutter 3.16 起 Material 3 已是默认,不应设置 useMaterial3: false,也不应把 useMaterial3: true 当作可选开启项传入。
Imports(Flutter 3.47+)
Material 与 Cupertino 已作为独立的 1.0 包发布:material_ui 与 cupertino_ui。SDK 中的副本在 3.47 仍可用,但已计划在 2026 年秋季稳定版中弃用。已有项目保持现有 import 风格;新建 3.47+ 应用或被要求迁移时,可执行 flutter pub add material_ui(构建 Cupertino 外壳时还需 flutter pub add cupertino_ui),再运行 dart fix --apply --code=migrate_design_widgets,它会将 package:flutter/material.dart 重写为 package:material_ui/material_ui.dart,package:flutter/cupertino.dart 重写为 package:cupertino_ui/cupertino_ui.dart。若 pubspec.yaml 未更新,先执行 flutter pub add material_ui(及 cupertino_ui),再执行一次 dart fix --apply。仍导入 SDK 库的旧包组件需要桥接,可通过 MaterialApp 的 builder 使用 MaterialUiCompatibilityBridge。本地化方面,应使用来自 material_ui 的 localizationsDelegates: GlobalMaterialLocalizations.delegates(包含 Cupertino 与 Widgets),迁移后不要再保留 flutter_localizations 的 GlobalMaterialLocalizations / GlobalCupertinoLocalizations。
构建页面流程
先阅读现有主题、路由和邻近页面,复制其约定。选择脚手架:Material 用 Scaffold + AppBar / SliverAppBar,Cupertino 用 CupertinoPageScaffold + CupertinoNavigationBar / CupertinoSliverNavigationBar。页面标题应放在应用栏或导航栏中,而不是正文顶部的自定义 Text。内容内边距应通过 body 外层的 SafeArea 设置,而不是包裹整个 Scaffold(AppBar 已绘制在状态栏下方);嵌套 SafeArea 会合并,不要自行读取 MediaQuery.padding 添加内边距。可滚动页面使用 CustomScrollView + slivers 或 ListView.builder;表单使用 ListView / CustomScrollView,而不是会溢出的裸 Column。颜色与字体来自 ColorScheme.of(context) 和 TextTheme.of(context),不要用原始 Colors.blue 或一次性 fontSize: 13。交互控件应加上 const ValueKey<String>('kebab-name')。编辑后先热重载,再按 Verify 流程校验。
硬性规则(主题与颜色)
用 ColorScheme.fromSeed 构建配色,搭配 theme + darkTheme + themeMode。通过 ColorScheme.of(context) 和 TextTheme.of(context) 读取 token。使用 M3 角色:primary/onPrimary、surface/onSurface、surfaceContainer*、outline、error,不用 primarySwatch,也不用 accentColor。Cupertino 颜色使用 CupertinoColors / CupertinoDynamicColor,并通过 CupertinoDynamicColor.resolve(color, context) 解析。
硬性规则(布局)
使用 MediaQuery.sizeOf / paddingOf / viewInsetsOf / viewPaddingOf / textScalerOf / platformBrightnessOf / highContrastOf / accessibleNavigationOf,绝不用 MediaQuery.of(context).size(会在每次 MediaQuery 变化时重建)。Row/Column/Flex 用 spacing 做间隙,Wrap 用 spacing + runSpacing,不要在每两个子项之间塞 SizedBox。按窗口大小而非设备类型做判断,绝不用 Platform.isIOS 选择手机或平板布局,绝不在应用根部使用 OrientationBuilder。断点:compact <600,medium 600–839,expanded ≥840(M3 窗口尺寸类)。不要锁定方向,不要使用 flutter_screenutil 或基于设计像素的缩放器。
硬性规则(组件)
新代码应使用 FilledButton、FilledButton.tonal、OutlinedButton、TextButton(而非 ElevatedButton 作主操作、RaisedButton、FlatButton);使用 NavigationBar + NavigationDestination(而非 BottomNavigationBar);大宽度使用 NavigationRail / NavigationDrawer(而非把手机导航栏拉伸到桌面);使用 SegmentedButton(而非 ToggleButtons);使用 DropdownMenu(而非 DropdownButton / DropdownButtonFormField);使用 MenuAnchor / MenuBar(而非 PopupMenuButton 做新菜单);使用 SearchAnchor / SearchBar(而非自造搜索框);使用 Card.filled / Card.outlined(而非随意 Container + BoxShadow);使用 ListView.builder / slivers(而非 ListView(children: 大列表));对这些系统原生体验使用 Switch.adaptive、Slider.adaptive、Checkbox.adaptive、Radio.adaptive、CircularProgressIndicator.adaptive、RefreshIndicator.adaptive、AlertDialog.adaptive、showAdaptiveDialog,而非平台无关的 Material 控件。
硬性规则(导航)
新的多页面应用使用 go_router + MaterialApp.router / CupertinoApp.router,官方文档不推荐 MaterialApp.routes 命名路由。已有 Navigator.push 的应用保持 Navigator,除非被要求迁移。需要保留各标签页独立堆栈时使用 StatefulShellRoute.indexedStack。Sheets 和对话框保持非页面级(showModalBottomSheet、showAdaptiveDialog),除非必须可深链,否则不要把每个弹层都做成 GoRoute。全屏模态使用 fullscreenDialog: true(iOS present)或带该页面标志的 GoRoute。
硬性规则(Dart / 组件)
使用 const 构造器,用小组件替代单个 400 行的 build。文件名用 snake_case.dart,类型用 PascalCase,交互 key 用 kebab-case 字符串。用户可能复制的值使用 SelectableText 或 SelectionArea 祖先。表格数字使用 fontFeatures: const [FontFeature.tabularFigures()]。仅调试用的跳转芯片和预填内容应位于 kDebugMode 之后,且仍走真实鉴权路径。不要为了交付一个页面而引入 GetX、flutter_screenutil、FontAwesome-for-chrome 或新的状态管理包。
不要照搬 Expo/RN 习惯
不要使用 StyleSheet、process.env.EXPO_OS、SafeAreaView、CSS boxShadow,或 kebab-case 源文件名。不要用 flex: 1 风格的 map,改用 Expanded / Flexible。不要通过 expo-image 使用 SF Symbols,改用 Icons.adaptive / CupertinoIcons。
Verify
UI 工作不是编辑生效就算完成。需对改动文件保持 analyzer 干净(dart analyze / flutter analyze)。对运行中的应用热重载;若改动了 main、路由或全局主题则热重启。查看运行帧(截图、组件预览或项目的可视化循环),过期的帧不构成证据。孤立组件可使用来自 package:flutter/widget_previews.dart 的 @Preview(3.47 稳定)。检查所改页面的空、加载、错误、键盘打开状态,以及读取同一状态的其他路由。