← 返回 Skills 市场
yhongm

Flutter Dev Skill

作者 yhongm · GitHub ↗ · v1.0.0 · MIT-0
cross-platform ✓ 安全检测通过
61
总下载
0
收藏
0
当前安装
1
版本数
在 OpenClaw 中安装
/install flutter-dev-skill
功能描述
Flutter 跨平台移动开发技能。覆盖 Flutter 入门、Material 3 迁移、布局约束、动画 (Hero/Staggered)、自适应响应式布局、平台适配、大型屏幕支持、State 管理方案对比、 持久化、网络请求、性能优化。当用户提到 Flutter、Dart、移动开发、跨平台、Material...
使用说明 (SKILL.md)

Widget 体系与 State 管理

Widget 类型

类型 说明 示例
StatelessWidget 不可变,props 决定 UI Text, Icon, Container
StatefulWidget 可变,通过 State 管理变化 Checkbox, TextField, Scaffold

State 管理基础

class MyWidget extends StatefulWidget {
  @override
  State\x3CMyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State\x3CMyWidget> {
  int _counter = 0;

  void _increment() {
    setState(() { _counter++; }); // 触发重建
  }
}

BuildContext

每个 Widget 的 build() 方法接收 BuildContext。Context 包含:

  • 当前 widget 在 widget 树中的位置
  • 访问 Theme.of(context)MediaQuery.of(context)
  • 访问祖先 widget 提供的 InheritedWidget

布局约束

核心规则

"Constraints go down. Sizes go up. Parent sets position."

父 widget 向下传递约束;子 widget 向上报告尺寸;父 widget 决定子 widget 的位置。

BoxConstraints

类型 说明 场景
Tight max == min,固定尺寸 SizedBox(width: 100)
Loose min == 0,尺寸可变 Container() 默认
Unbounded max == double.infinity Scrollable、Flex 延伸方向

常用布局 Widget

Widget 用途
Container 通用盒子
SizedBox 固定尺寸盒子
ConstrainedBox 施加额外约束
Padding 内边距
Row / Column 线性布局(Flex)
Expanded 填充剩余空间
Flexible 类似 Expanded,可控制策略
Stack 绝对定位布局
Positioned Stack 中定位
LayoutBuilder 布局阶段获取约束

常见布局错误

// ❌ Expanded 在 Stack 中无效 → 用 Positioned 或 Align
// ❌ 在 unbounded 约束中 Expanded 无效 → 用 Slivers

// ✅ ConstrainedBox 在 Loose 约束内
ConstrainedBox(
  constraints: BoxConstraints(minWidth: 100, maxWidth: 200),
  child: Text('Hello'),
)

详细参考:layout-constraints.md


Material 3 迁移

核心开关

MaterialApp(
  theme: ThemeData(useMaterial3: true),
);

关键变化

M2 M3
background surface
primarySwatch ColorScheme.fromSeed()
ButtonTheme FilledButtonTheme
MaterialState WidgetState
FlatButton FilledButton

种子色

ColorScheme.fromSeed(seedColor: Colors.blue)

详细参考:material-3.md


动画系统

Hero 动画

// 页面 A 和 B
Hero(
  tag: 'photo',
  child: Image.asset('photo.jpg'),
)

Staggered 动画

// 一个控制器驱动多个 Interval 动画
Animation\x3Cdouble> get _opacity => Tween(begin: 0.0, end: 1.0).animate(
  CurvedAnimation(parent: _controller, curve: Interval(0.0, 0.5)),
);
Animation\x3Cdouble> get _scale => Tween(begin: 0.0, end: 1.0).animate(
  CurvedAnimation(parent: _controller, curve: Interval(0.5, 1.0)),
);

详细参考:hero-animations.md | staggered-animations.md


State 管理方案对比

Provider(入门级)

// ChangeNotifier
class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() { _count++; notifyListeners(); }
}

// 注册
runApp(ChangeNotifierProvider(
  create: (_) => CounterModel(),
  child: MyApp(),
));

// 重建 UI
Consumer\x3CCounterModel>(
  builder: (context, model, child) => Text('${model.count}'),
);

// 读取(不重建)
final count = context.read\x3CCounterModel>().count;

Riverpod(生产推荐)

// 纯 Dart,无 context 依赖
final counterProvider = StateNotifierProvider\x3CCounterNotifier, int>((ref) {
  return CounterNotifier();
});

class CounterNotifier extends StateNotifier\x3Cint> {
  CounterNotifier() : super(0);
  void increment() => state++;
}

// 使用
Widget build(BuildContext context, WidgetRef ref) {
  final count = ref.watch(counterProvider);
  return Text('$count');
}

// ref.watch vs ref.read
// watch: 重建 UI(监听变化)
// read: 不重建,仅读取当前值(事件处理中常用)

// 强制刷新 Provider
ref.invalidate(counterProvider);  // 重置状态,重新执行 Provider 回调
// 或者基于现有状态刷新
ref.invalidate(myProvider);  // 下次访问时重新创建

// Family Provider(带参数)
final userProvider = Provider.family\x3CUser, String>((ref, id) {
  return User(id: id, name: 'User $id');
});
// 使用
final user = ref.watch(userProvider('abc'));

Provider 作用域

// ✅ 顶层 Provider:全局单例
final prefsProvider = Provider\x3CSharedPreferences>((ref) {
  return SharedPreferences.getInstance() as SharedPreferences;
});

// ✅ 作用域 Provider:仅在子树内有效
ProviderScope(
  overrides: [counterProvider.overrideWith(() => MockCounter())],
  child: MyApp(),
);

// ✅ Child Provider:可读取父 Provider
final userProvider = Provider((ref) {
  final prefs = ref.watch(prefsProvider); // 依赖父 Provider
  return UserRepo(prefs: prefs);
});

BLoC(大型项目)

class CounterBloc extends Bloc\x3CCounterEvent, int> {
  CounterBloc() : super(0) {
    on\x3CIncrement>((event, emit) => emit(state + 1));
  }
}

BlocBuilder\x3CCounterBloc, int>(
  builder: (context, count) => Text('$count'),
)

方案选型

方案 复杂度 适用场景
setState 简单 widget
Provider 中小型应用
Riverpod 中高 生产推荐
BLoC 团队协作/大型项目

持久化存储

SharedPreferences(轻量配置)

final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', 'yhong');
await prefs.setInt('counter', 42);
final name = prefs.getString('username') ?? '';
await prefs.remove('counter');

SQLite(结构化数据)

final db = await openDatabase('myapp.db', version: 1,
  onCreate: (db, version) async {
    await db.execute(
      'CREATE TABLE tasks(id INTEGER PRIMARY KEY, title TEXT)');
  });

await db.insert('tasks', {'title': 'Finish report'});
final maps = await db.query('tasks', where: 'id = ?', whereArgs: [1]);

// 批量事务
await db.transaction((txn) async {
  for (final task in tasks) await txn.insert('tasks', task);
});

Hive(高性能 KV)

Hive.registerAdapter(TaskAdapter());
final box = await Hive.openBox\x3CTask>('tasks');
box.put('task1', Task(title: 'Hello'));
final task = box.get('task1');

网络请求

Dio(推荐)

final dio = Dio(BaseOptions(
  baseUrl: 'https://api.example.com',
  connectTimeout: Duration(seconds: 10),
  headers: {'Content-Type': 'application/json'},
));

// 全局拦截器:Token 自动附加
dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    final token = getToken(); // 从安全存储读取
    if (token != null) {
      options.headers['Authorization'] = 'Bearer $token';
    }
    handler.next(options);
  },
  onError: (error, handler) {
    // Token 过期自动刷新重试
    if (error.response?.statusCode == 401) {
      refreshToken().then((newToken) {
        // 重试原请求
        final opts = error.requestOptions;
        opts.headers['Authorization'] = 'Bearer $newToken';
        dio.fetch(opts).then(
          (r) => handler.resolve(r),
          (e) => handler.reject(e),
        );
      }).catchError((e) => handler.reject(error));
    } else {
      handler.next(error);
    }
  },
));

// GET / POST
final resp = await dio.get('/users', queryParameters: {'page': 1});
final resp = await dio.post('/users', data: {'name': 'yhong'});

统一响应体处理

// 常见后端响应格式:{ code, data, message }
class ApiResp\x3CT> {
  final int code;
  final T? data;
  final String? message;

  bool get ok => code == 0;
}

// Dio 响应拦截器统一解析
dio.interceptors.add(InterceptorsWrapper(
  onResponse: (resp, handler) {
    final body = resp.data;
    if (body is Map && body.containsKey('code')) {
      if (body['code'] != 0) {
        // 业务错误,转为异常
        handler.reject(
          DioException(
            requestOptions: resp.requestOptions,
            error: body['message'] ?? 'Unknown error',
            type: DioExceptionType.badResponse,
          ),
        );
        return;
      }
      // 替换为 data 部分
      resp.data = body['data'];
    }
    handler.next(resp);
  },
));

// 使用时直接取 data
final List users = await dio.get('/users').then((r) => r.data);

错误处理

try {
  final resp = await dio.get('/users');
} on DioException catch (e) {
  switch (e.type) {
    case DioExceptionType.connectionTimeout:
      // 网络超时
    case DioExceptionType.badResponse:
      final statusCode = e.response?.statusCode;
      if (statusCode == 401) { /* 未授权 */ }
      if (statusCode == 403) { /* 禁止 */ }
      if (statusCode == 404) { /* 资源不存在 */ }
      if (statusCode != null && statusCode >= 500) { /* 服务器错误 */ }
    case DioExceptionType.cancel:
      // 请求被取消
    default:
      // 网络不可达等
  }
}

JSON 解析

@JsonSerializable()
class User {
  final String name;
  final int age;
  factory User.fromJson(Map\x3CString, dynamic> json) => _$UserFromJson(json);
  Map\x3CString, dynamic> toJson() => _$UserToJson(this);
}

性能优化

重建控制

// ✅ Selector 精确重建(替代 Consumer)
Selector\x3CModel, String>(
  selector: (_, m) => m.title,
  builder: (_, title, __) => Text(title),
);

// ✅ const 构造
const Text('Hello');
const Padding(padding: EdgeInsets.all(16));

// ❌ build 中创建新对象
Widget build(BuildContext context) {
  return SomeWidget(items: List.generate(100, (i) => Item(i))); // 每次重建
}

// ✅ initState 中初始化
final items = List.generate(100, (i) => Item(i));

长列表优化

// ✅ ListView.builder 懒加载
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) => ListTile(title: Text(items[index])),
);

// ✅ cacheExtent
ListView.builder(cacheExtent: 200, itemBuilder: ...);

// ✅ RepaintBoundary 隔离重绘
RepaintBoundary(child: MyComplexWidget());

响应式布局实战

自适应 Scaffold

class AdaptiveScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.of(context).size.width;

    if (width \x3C 600) {
      return Scaffold(
        body: body,
        bottomNavigationBar: NavigationBar(
          selectedIndex: currentIndex,
          onDestinationSelected: onIndexChanged,
          destinations: _destinations,
        ),
      );
    }

    return Scaffold(
      body: Row(
        children: [
          NavigationRail(
            selectedIndex: currentIndex,
            onDestinationSelected: onIndexChanged,
            labelType: width > 840
                ? NavigationRailLabelType.all
                : NavigationRailLabelType.selected,
            destinations: _destinations
                .map((d) => NavigationRailDestination(
                      icon: d.icon,
                      label: d.label,
                    ))
                .toList(),
          ),
          const VerticalDivider(thickness: 1, width: 1),
          Expanded(child: body),
        ],
      ),
    );
  }
}

响应式列数

LayoutBuilder(
  builder: (context, constraints) {
    final cols = constraints.maxWidth > 900 ? 3
               : constraints.maxWidth > 600 ? 2
               : 1;
    return GridView.count(
      crossAxisCount: cols,
      childAspectRatio: 1.5,
      children: items.map((item) => Card(child: item)).toList(),
    );
  },
);

详细参考:adaptive-responsive.md | large-screens.md


平台适配

平台检测

final idiom = MediaQuery.of(context).size.shortestSide >= 600 ? 'tablet' : 'phone';
final platform = Theme.of(context).platform;

键盘快捷键

Shortcuts(
  shortcuts: {
    LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyS): SaveIntent(),
  },
  child: Actions(
    actions: { SaveIntent: CallbackAction(onInvoke: (_) => _save()) },
    child: focusNode,
  ),
)

鼠标 Hover

MouseRegion(
  onEnter: (_) => setState(() => _isHovered = true),
  onExit: (_) => setState(() => _isHovered = false),
  child: MyWidget(),
)

详细参考:platform-idioms.md


避坑指南

State 管理

错误 正确
❌ 在 build() 中调用 setState() ✅ 在回调中调用
initState() 中直接使用 context didChangeDependencies()
❌ 大型对象放 State const 构造函数

布局

错误 正确
ExpandedStack Positioned/Align
❌ 硬编码尺寸 MediaQuery/LayoutBuilder

M3 迁移

错误 正确
ButtonTheme FilledButtonTheme
background/onBackground surface/onSurface
MaterialState WidgetState

快速参考

热重载 vs 热重启

操作 效果
R (Hot Reload) 保持 State,仅重建 Widget 树
Shift+R (Hot Restart) State 丢失,重新执行 main()

常用 EdgeInsets

EdgeInsets.all(16.0)
EdgeInsets.symmetric(v: 8)
EdgeInsets.only(left: 16)
EdgeInsets.fromLTRB(4,8,4,8)

AnimationController 生命周期

_controller = AnimationController(
  duration: Duration(milliseconds: 300),
  vsync: this,
);

@override
void dispose() {
  _controller.dispose();
  super.dispose();
}

组件速查

按钮 导航 容器
FilledButton AppBar Card
FilledButton.tonal NavigationBar Dialog
OutlinedButton NavigationRail SnackBar
TextButton NavigationDrawer BottomSheet

详细参考:buttons-input.md | navigation-components.md | display-components.md


来源

文档版本:Flutter 3.x + Material 3 URL: https://flutter.cn/docs 抓取时间:2026-04-24

参考文档

文件 行数 覆盖内容
material-3.md 767 Material 3 迁移完整指南
layout-constraints.md 368 布局约束与约束传递机制
overview-getting-started.md 376 Flutter 入门与平台能力
buttons-input.md 424 按钮与输入组件
adaptive-responsive.md 298 自适应响应式布局
platform-idioms.md 358 平台适配与设备类型
large-screens.md 264 大屏幕与折叠屏支持
navigation-components.md 350 导航组件
display-components.md 300 展示组件
hero-animations.md 239 Hero 动画
staggered-animations.md 354 交错动画
testing.md 121 测试指南(单元/Widget/集成/Mock)
dependency-injection.md 81 依赖注入(get_it/Riverpod/injectable)

| testing.md | 参考文档:测试指南 |

单元测试(flutter_test)

详见 testing.md(flutter_test / widget / integration / Mock)

class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;
  void increment() { _count++; notifyListeners(); }
}

test('CounterModel increments correctly', () {
  final model = CounterModel();
  expect(model.count, 0);
  model.increment();
  expect(model.count, 1);
});

Widget 测试

testWidgets('CounterWidget displays count and increments', (tester) async {
  await tester.pumpWidget(MaterialApp(home: CounterWidget()));
  expect(find.text('0'), findsOneWidget);
  await tester.tap(find.byIcon(Icons.add));
  await tester.pump();
  expect(find.text('1'), findsOneWidget);
});

Riverpod Provider 测试

testProvider('counterProvider increments', (override) async {
  await runProviderScope((ref) async {
    final counter = ref.watch(counterProvider);
    expect(counter, 0);
    ref.read(counterProvider.notifier).increment();
    expect(ref.watch(counterProvider), 1);
  }, overrides: []);
});

集成测试

setUpAll(() async { await FlutterDriver.connect(); });

test('app loads and shows home', () async {
  final driver = await FlutterDriver.connect();
  await driver.waitFor(find.byType('MyHomePage'));
  expect(find.text('Home'), findsOneWidget);
});

Mock + mockito

@GenerateMocks([UserRepository])
void main() {
  late MockUserRepository mockRepo;
  setUp(() { mockRepo = MockUserRepository(); });

  test('loads user data', () async {
    when(mockRepo.getUser('123'))
        .thenAnswer((_) async => User(id: '123', name: 'Alice'));
    final user = await mockRepo.getUser('123');
    expect(user.name, 'Alice');
    verify(mockRepo.getUser('123')).called(1);
  });
}

依赖注入

详见 dependency-injection.md(get_it / injectable / DI 陷阱)

import 'package:get_it/get_it.dart';
final getIt = GetIt.instance;

void setupDependencies() {
  getIt.registerLazySingleton\x3CDio>(() => Dio());
  getIt.registerFactory\x3CUserRepository>(
    () => UserRepository(dio: getIt\x3CDio>()),
  );
  getIt.registerSingleton\x3CSharedPreferences>(prefs);
}

final repo = getIt\x3CUserRepository>();

Riverpod + get_it

final dioProvider = Provider\x3CDio>((ref) => getIt\x3CDio>());
final userRepoProvider = Provider\x3CUserRepository>(
  (ref) => UserRepository(dio: ref.watch(dioProvider)),
);

injectable

@injectable
class AuthRepository {
  final Dio dio;
  AuthRepository({required this.dio});
}
// configureDependencies(); // 自动生成

DI 陷阱

错误 正确
❌ 在 build()GetIt.instance\x3CDio>() ✅ 顶层 setupDependencies() 中注册
❌ 单例中引用非单例 ✅ 确保生命周期一致
❌ 直接 Dio() 硬编码 ✅ 通过 getIt\x3CDio>() 注入

输出格式规范

回复结构

  1. 直接回答 — 一段简洁的话给出核心答案
  2. 代码示例 — 提供完整的 Dart/Flutter 代码
  3. 实现要点 — 关键步骤和注意事项
  4. 避坑提醒 — 常见错误 + 正确做法

禁用格式

  • ❌ 不要显式分层(避免"第一层/第二层/框架分析"等字眼)
  • ❌ 不要长篇解释概念,要直接给出实现
  • ❌ 不要只给代码片段,要给完整可运行的示例
  • ✅ 输出应是一段干净的话 + 完整代码
安全使用建议
This skill is a documentation-only Flutter/Dart reference and appears coherent and low-risk: it asks for nothing and installs nothing. Before installing, you may still want to: (1) confirm you trust the publisher (source is listed as unknown), (2) review the trigger list so the agent only activates on appropriate queries, and (3) if you enable autonomous invocation, be aware the agent may offer Flutter guidance proactively — but there are no credentials or installers here to expose or execute.
功能分析
Type: OpenClaw Skill Name: flutter-dev-skill Version: 1.0.0 The Flutter development skill bundle is a comprehensive educational resource covering Widget architecture, state management (Provider/Riverpod/BLoC), networking (Dio), and Material 3 migration. The code snippets and documentation in SKILL.md and the references directory are standard, high-quality Flutter best practices. There is no evidence of malicious intent, data exfiltration, or prompt injection; the instructions provided to the agent are strictly focused on formatting technical responses for the user.
能力评估
Purpose & Capability
Name/description match the provided content: the bundle contains a large set of Flutter/Dart reference docs, examples, and migration guidance, which is appropriate for a "Flutter Dev" skill. There are no unrelated requirements (no cloud creds, no system binaries).
Instruction Scope
SKILL.md and the included reference files are documentation and code examples only. They do not instruct the agent to read system files, access environment variables, or transmit data to external endpoints beyond citing public Flutter docs. Trigger keywords are Flutter/Dart related and appropriate.
Install Mechanism
No install spec and no code files that would be downloaded or executed. Instruction-only skills are lowest risk because nothing is written to disk by an installer.
Credentials
The skill requests no environment variables, credentials, or config paths. There are no secret-bearing env names or disproportionate credential requests.
Persistence & Privilege
always is false and the skill is user-invocable; it does not request persistent system-wide privileges or modify other skills. Autonomous invocation is allowed by platform default but not combined with other red flags here.
如何使用
  1. 确保已安装 OpenClaw(本地或 Docker 部署)
  2. 在对话框中输入安装命令:/install flutter-dev-skill
  3. 安装完成后,直接呼叫该 Skill 的名称或使用 /flutter-dev-skill 触发
  4. 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
版本历史
v1.0.0
- Initial release of flutter-dev skill. - Covers Flutter basics, Material 3 migration, layout constraints, animations (Hero/Staggered), adaptive layouts, platform adaptation, large screen support, state management solutions comparison, persistence, networking, and performance optimization. - Triggers on a wide range of Flutter, Dart, and mobile development keywords. - Provides in-depth code samples and best practices for state management (setState, Provider, Riverpod, BLoC), layout constraints, animations, and more. - References official Flutter documentation for further reading.
元数据
Slug flutter-dev-skill
版本 1.0.0
许可证 MIT-0
累计安装 0
当前安装数 0
历史版本数 1
常见问题

Flutter Dev Skill 是什么?

Flutter 跨平台移动开发技能。覆盖 Flutter 入门、Material 3 迁移、布局约束、动画 (Hero/Staggered)、自适应响应式布局、平台适配、大型屏幕支持、State 管理方案对比、 持久化、网络请求、性能优化。当用户提到 Flutter、Dart、移动开发、跨平台、Material... 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 61 次。

如何安装 Flutter Dev Skill?

在 OpenClaw 或 Claude Code 对话框中运行命令「/install flutter-dev-skill」即可一键安装,无需额外配置。

Flutter Dev Skill 是免费的吗?

是的,Flutter Dev Skill 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。

Flutter Dev Skill 支持哪些平台?

Flutter Dev Skill 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。

谁开发了 Flutter Dev Skill?

由 yhongm(@yhongm)开发并维护,当前版本 v1.0.0。

💬 留言讨论