关于 flutter-ui
面向 Flutter UI 开发的技能,通过参考文档、设计检查点和审计脚本指导状态管理、导航、主题、布局、动画与性能实践。
主要功能
- 01编码前检查相关参考文档
- 02按现有导入识别状态管理方式
- 03指导 GoRouter 导航与返回逻辑
- 04提供主题、布局和动画规则
- 05用脚本审计常量与资源释放
适用场景
- 01为新屏幕确定状态管理和导航方案
- 02检查动画控制器资源释放及重绘边界
- 03排查缺少 const 或弃用 API
- 04发布前核对主题、布局和错误状态
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ naimehossein77/ claude-flutter-ui-skills --skill flutter-ui安装后在 Claude Code 中输入/flutter-ui确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
flutter-animations.mdflutter-custom-paint.mdflutter-design-thinking.mdflutter-layout-system.mdflutter-navigation.mdflutter-performance.mdflutter-state-ui.mdflutter-theme-system.mdscripts/flutter_ui_audit.py详细介绍
Flutter UI 系统
理念: Flutter 管控每个像素。优先使用 const。由主题驱动。使用 GPU 动画。状态限定作用域。
🔧 运行时脚本
| 脚本 | 用途 | 用法 |
|---|---|---|
scripts/flutter_ui_audit.py | 审计:检查缺少 const、dispose 泄漏和已弃用的 API | python scripts/flutter_ui_audit.py <path> |
🔴 强制要求:先阅读参考文件
⛔ 阅读相关文件之前不要编写代码:
| 文件 | 内容 | 优先级 |
|---|---|---|
| flutter-design-thinking.md | 防止死记硬背,强制进行上下文分析 | ⬜ 最优先 |
| flutter-state-ui.md | Claude 必须遵循的 Riverpod + Provider 规则 | ⬜ 关键 |
| flutter-navigation.md | GoRouter、PopScope、返回逻辑、深层链接 | ⬜ 关键 |
| flutter-animations.md | 隐式动画、显式动画、Hero、页面转场、物理效果 | ⬜ 关键 |
| flutter-performance.md | const、RepaintBoundary、60fps、着色器预热 | ⬜ 关键 |
| flutter-theme-system.md | Material 3、ColorScheme、TextTheme、ThemeExtension | ⬜ 阅读 |
| flutter-layout-system.md | 响应式布局、LayoutBuilder、MediaQuery、Slivers | ⬜ 阅读 |
| flutter-custom-paint.md | Canvas、CustomPainter、像素级渲染 | ⬜ 阅读 |
🧠 先阅读 flutter-design-thinking.md — 防止 AI 套用记忆中的模式。
⚠️ 先询问,不要自行假设
| 方面 | 询问 |
|---|---|
| 状态管理器 | “使用 Riverpod、Provider、BLoC,还是原生方案?” |
| 导航 | “使用 GoRouter、auto_route,还是 Navigator 1.0?” |
| 设计系统 | “使用 Material 3、Cupertino,还是自定义方案?” |
| 平台 | “仅面向手机,还是要适配平板/桌面?” |
| Flutter 版本 | “使用 3.12+ 吗?(PopScope 还是 WillPopScope)” |
⛔ 反模式
性能
| ❌ 绝不使用 | ✅ 始终使用 |
|---|---|
使用静态子项的 ListView 展示长列表 | ListView.builder |
在动画循环中调用 setState | AnimatedBuilder |
使用 Opacity(opacity: 0) 隐藏内容 | Visibility 或条件渲染 |
在列表中使用 IntrinsicHeight/IntrinsicWidth | 固定高度或 SliverFixedExtentList |
StatelessWidget 缺少 const | 始终使用 const MyWidget({super.key}) |
Column + ListView 未使用 Expanded | 将 ListView 包在 Expanded 中 |
在组件中硬编码 Color(0xFF...) | Theme.of(context).colorScheme.primary |
硬编码 fontSize | Theme.of(context).textTheme.bodyLarge |
动画
| ❌ 绝不使用 | ✅ 始终使用 |
|---|---|
对 width/height 设置动画 | 对 Transform 设置动画(缩放/平移) |
AnimationController 未调用 dispose() | 在 dispose() 方法中释放 |
addListener(() => setState((){})) | AnimatedBuilder |
复杂绘制器缺少 RepaintBoundary | 将 CustomPaint 包在 RepaintBoundary 中 |
| Hero 标签冲突 | 使用由数据驱动的唯一标签 |
| 未设置缓动曲线 | 至少使用 Curves.easeInOut |
导航
| ❌ 绝不使用 | ✅ 始终使用 |
|---|---|
WillPopScope(Flutter 3.12+ 中已弃用) | 使用带 canPop + onPopInvokedWithResult 的 PopScope |
在 GoRouter 应用中使用 Navigator.push | context.go() / context.push() |
| 到处硬编码路由字符串 | 集中定义路由常量 |
| 认证页面没有路由守卫 | GoRouter redirect 回调 |
| 根页面上的返回按钮不起作用 | PopScope(canPop: false) + 退出对话框 |
状态
| ❌ 绝不 | ✅ 始终 |
|---|---|
在 build() 中使用 ref.read()(Riverpod) | 在 build() 中使用 ref.watch() |
在 build() 内定义 Provider | 顶层 final myProvider = ... |
在 build() 中使用 context.read<T>()(Provider) | 在 build() 中使用 context.watch<T>() |
在 ChangeNotifier 中使用 BuildContext | 传递数据,而非 context |
| 在修改前调用 `notifyListeners()`` | 先修改,再调用 notifyListeners() |
使用没有错误处理器的 .when() | 始终使用 when(data:, loading:, error:) |
用 Consumer<T> 包裹整个屏幕 | 在最小的 widget 上使用 Selector<T, R> |
🗺️ 导航:go() 与 push() 和 replace()
| 方法 | 返回栈 | 适用场景 |
|---|---|---|
context.go('/path') | 替换整个栈 | 切换标签页、认证重定向 |
context.push('/path') | 添加到栈中 | 逐层深入导航 |
context.replace('/path') | 替换当前页面,不可返回 | 认证后从登录页 → 首页 |
context.pop() | 移除当前页面 | 返回按钮、关闭模态框 |
context.pop(result) | 移除当前页面并返回数据 | 提交表单、选择器结果 |
🎬 动画选择
WHAT ARE YOU ANIMATING?
├── Simple property change → AnimatedContainer, AnimatedOpacity, TweenAnimationBuilder
├── Complex sequence/stagger → AnimationController + CurvedAnimation + AnimatedBuilder
├── Shared element (cross-screen) → Hero widget + unique tag
├── Page enter/exit → GoRouter CustomTransitionPage or pageBuilder
├── Spring/bounce/momentum → SpringSimulation, BouncingScrollPhysics
└── Vector/sprite → Rive or Lottie package
GPU 安全: transform、opacity → 流畅
受 CPU 限制: width、height、margin、padding → 卡顿
📊 状态管理器自动检测
编写任何状态代码前先扫描导入项:
| 找到的导入项 | 使用 |
|---|---|
flutter_riverpod | Riverpod 规则 |
provider 包 | Provider 规则 |
| 两者都没有 | 询问用户 → 新项目推荐 Riverpod |
⚡ 性能速查
class MyCard extends StatelessWidget {
const MyCard({super.key});
@override
Widget build(BuildContext context) => const Padding(
padding: EdgeInsets.all(16),
child: Text('Hello'),
);
}
RepaintBoundary(child: AnimatedWidget(...))
ListView.builder(itemCount: n, itemBuilder: (ctx, i) => Item(items[i]))
final name = ref.watch(userProvider.select((u) => u.name));
🧠 检查点(开始任何 Flutter 工作前填写)
🧠 FLUTTER CHECKPOINT:
State: [ Riverpod / Provider / BLoC / Vanilla ]
Navigation: [ GoRouter / auto_route / Navigator 1.0 ]
Design: [ Material 3 / Cupertino / Custom ]
Platforms: [ Mobile / Tablet / Desktop / Web ]
Files Read: [ List files read ]
3 Rules I Will Apply:
1. _______________
2. _______________
3. _______________
Anti-Patterns I Will Avoid:
1. _______________
2. _______________
🔴 无法填写检查点?→ 阅读技能文件。
📋 检查清单
每个屏幕
- 如果使用 Riverpod,是否使用
ConsumerWidget?是否使用const构造函数? - 触控目标是否 ≥ 48dp?
- 是否定义了加载和错误状态?
- 是否测试了返回导航(PopScope 或 GoRouter canPop)?
- 是否没有硬编码颜色或字号?
每个动画
- 是否为
transform/opacity设置动画,而不是width/height? - 是否调用了
AnimationController.dispose()? - 时长是否 ≥ 150ms,且带缓动曲线?
- 复杂绘制器是否使用了
RepaintBoundary?
发布前
- 运行审计脚本 — 修复所有 🔴 项
-
flutter analyze— 没有缺少 const 的警告 - 测试 Android 返回按钮 — 没有卡住的屏幕
- 测试深色模式 — 所有颜色均来自 Theme
- 测试平板布局 — 没有溢出
- 所有
.when()都处理了错误状态
📚 参考文件
| 文件 | 适用场景 |
|---|---|
| flutter-design-thinking.md | 首先阅读 — 防止 AI 默认行为 |
| flutter-state-ui.md | Riverpod 和 Provider 规则、模式、迁移 |
| flutter-navigation.md | GoRouter、返回逻辑、深层链接、标签页 |
| flutter-animations.md | 隐式、显式、Hero、转场、物理效果 |
| flutter-theme-system.md | Material 3、ColorScheme、TextTheme |
| flutter-layout-system.md | 响应式、LayoutBuilder、Slivers |
| flutter-performance.md | const、RepaintBoundary、DevTools |
| flutter-custom-paint.md | Canvas、CustomPainter、像素级精准 |
像素级精准 = 每个间距、颜色和字体选择都有意为之,并由主题驱动。通过设计系统掌控每个像素,而不是依赖魔法数字。
评论
支持 Markdown