Skip to content

Flutter 完整教程

Flutter 教程知识体系
├── 一、初级篇 - 从入门到会用
│   ├── 1.1 Flutter 介绍与环境搭建
│   ├── 1.2 Dart 语言基础
│   ├── 1.3 Flutter 基础组件
│   ├── 1.4 布局系统
│   ├── 1.5 状态管理入门
│   ├── 1.6 路由与导航
│   └── 1.7 资源管理
├── 二、中级篇 - 开发常用功能
│   ├── 2.1 表单处理与验证
│   ├── 2.2 列表与懒加载
│   ├── 2.3 动画系统
│   ├── 2.4 本地存储
│   ├── 2.5 网络请求
│   └── 2.6 状态管理进阶
├── 三、高级篇 - 深入与优化
│   ├── 3.1 自定义绘制
│   ├── 3.2 性能优化
│   ├── 3.3 平台通道
│   ├── 3.4 测试体系
│   ├── 3.5 打包发布
│   └── 3.6 主题与国际化
├── 四、资深篇 - 架构与原理
│   ├── 4.1 架构设计模式
│   ├── 4.2 Flutter 引擎原理
│   ├── 4.3 渲染管线
│   ├── 4.4 Isolate 与并发
│   ├── 4.5 CI/CD 与工程化
│   ├── 4.6 混合开发(Add-to-App)
│   └── 4.7 插件开发
└── 五、异常问题专题
    ├── 5.1 环境配置问题
    ├── 5.2 布局渲染异常
    ├── 5.3 状态管理陷阱
    ├── 5.4 网络请求问题
    ├── 5.5 性能问题
    ├── 5.6 平台兼容问题
    ├── 5.7 内存泄漏
    └── 5.8 构建打包问题

扩展专题导航


一、初级篇 - 从入门到会用

1.1 Flutter 介绍与环境搭建

什么是 Flutter

Flutter 是 Google 推出的一款开源 UI 框架,使用 Dart 语言开发,可以实现一套代码同时运行在 iOS、Android、Web、Windows、macOS、Linux 等多个平台。与 React Native 等框架不同,Flutter 不使用平台原生组件,而是自带渲染引擎(Skia/Impeller),直接绘制像素,因此可以实现像素级一致的跨平台体验。

Flutter 的核心特点

  • 跨平台:一套代码,多端运行(iOS、Android、Web、桌面)
  • 高性能:自绘引擎,60fps/120fps 流畅渲染,无桥接性能损耗
  • 热重载:修改代码后毫秒级生效,极大提升开发效率
  • 丰富的组件库:Material Design 和 Cupertino 两套风格组件
  • Dart 语言:AOT 编译保证性能,JIT 编译支持热重载

环境搭建步骤

1. 安装 Flutter SDK

bash
# macOS / Linux
git clone https://github.com/flutter/flutter.git -b stable
export PATH="$PATH:`pwd`/flutter/bin"

# Windows:下载安装包
# https://docs.flutter.dev/get-started/install/windows

2. 运行 flutter doctor 检查环境

bash
flutter doctor

输出示例:

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.x.x, on macOS 14.x)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.x)
[✓] Xcode - develop for iOS and macOS (Xcode 15.x)
[✓] Chrome - develop for the web
[✓] Android Studio
[✓] VS Code
[✓] Connected device (3 available)
[✓] Network resources

3. 创建并运行第一个项目

bash
flutter create my_first_app
cd my_first_app
flutter run

4. 开发工具选择

工具特点
VS Code + Flutter 插件轻量、免费、插件丰富
Android Studio + Flutter 插件功能全面、内置模拟器管理
IntelliJ IDEA企业级 IDE、功能强大

推荐初学者使用 VS Code,配合 Flutter 和 Dart 插件即可获得良好的开发体验。


1.2 Dart 语言基础

Dart 是 Flutter 的开发语言,由 Google 设计,是一门面向对象、类定义、单继承的语言,语法风格类似 Java 和 JavaScript 的结合体。

变量与类型(含空安全)

Dart 是强类型语言,同时支持类型推断。从 Dart 2.12 开始,空安全(Null Safety)成为默认行为。

dart
// 基本类型
String name = 'Flutter';
int count = 42;
double price = 9.99;
bool isActive = true;

// 类型推断 - 使用 var
var message = 'Hello'; // 自动推断为 String
var number = 100;      // 自动推断为 int

// 空安全 - 默认不可空
String title = 'Hello';       // 不可空,必须赋值
String? subtitle = null;      // 可空,使用 ? 标记

// late 延迟初始化
late String description; // 稍后赋值,但使用前必须赋值

// final 和 const
final String appName = 'MyApp';  // 运行时常量,只能赋值一次
const double pi = 3.14159;       // 编译时常量

// 空安全操作符
String? nullableName;
String displayName = nullableName ?? '默认值';  // 如果为 null 则使用默认值
String? upperName = nullableName?.toUpperCase(); // 安全调用

函数

Dart 函数是一等公民,支持箭头函数、可选参数、命名参数等特性。

dart
// 基本函数
int add(int a, int b) {
  return a + b;
}

// 箭头函数(表达式函数)
int multiply(int a, int b) => a * b;

// 命名参数 - 使用 {} 包裹
void greet({required String name, int age = 18}) {
  print('你好, $name, 年龄: $age');
}

// 调用命名参数
greet(name: '张三', age: 25);
greet(name: '李四'); // age 使用默认值 18

// 位置可选参数 - 使用 [] 包裹
String formatName(String first, [String? last]) {
  return last != null ? '$first $last' : first;
}

formatName('张');        // 输出: 张
formatName('张', '三');   // 输出: 张 三

// 匿名函数与回调
var numbers = [1, 2, 3, 4, 5];
var doubled = numbers.map((n) => n * 2).toList(); // [2, 4, 6, 8, 10]

类与对象

dart
class Person {
  // 属性
  final String name;
  int _age; // 私有属性,以 _ 开头

  // 构造函数
  Person(this.name, this._age);

  // 命名构造函数
  Person.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        _age = json['age'];

  // 命名构造函数 - 出生
  Person.born(this.name) : _age = 0;

  // Getter
  int get age => _age;

  // Setter
  set age(int value) {
    if (value >= 0) _age = value;
  }

  // 方法
  String introduce() => '我是$name,今年$_age岁';

  // 重写 toString
  @override
  String toString() => 'Person(name: $name, age: $_age)';
}

// 使用
var person = Person('张三', 25);
var fromJson = Person.fromJson({'name': '李四', 'age': 30});
print(person.introduce()); // 我是张三,今年25岁

// 继承
class Student extends Person {
  final String school;

  Student(String name, int age, this.school) : super(name, age);

  @override
  String introduce() => '${super.introduce()},就读于$school';
}

// 抽象类
abstract class Shape {
  double area(); // 抽象方法

  void describe() {
    print('面积: ${area()}');
  }
}

class Circle extends Shape {
  final double radius;
  Circle(this.radius);

  @override
  double area() => 3.14159 * radius * radius;
}

// Mixin - 代码复用
mixin Flyable {
  void fly() => print('正在飞行');
}

mixin Swimmable {
  void swim() => print('正在游泳');
}

class Duck extends Animal with Flyable, Swimmable {
  // Duck 同时拥有 fly 和 swim 能力
}

集合(List/Map/Set)

dart
// List 列表
List<String> fruits = ['苹果', '香蕉', '橘子'];
fruits.add('葡萄');
fruits.insert(1, '西瓜');
var filtered = fruits.where((f) => f.length > 1).toList();
var mapped = fruits.map((f) => '好吃的$f').toList();

// List 展开
List<List<int>> nested = [[1, 2], [3, 4], [5]];
List<int> flat = nested.expand((e) => e).toList(); // [1, 2, 3, 4, 5]

// Map 映射
Map<String, int> scores = {
  '语文': 90,
  '数学': 95,
  '英语': 88,
};
scores['物理'] = 92;
scores.forEach((key, value) => print('$key: $value'));
var keys = scores.keys.toList();
var values = scores.values.toList();

// Set 集合 - 元素唯一
Set<int> uniqueNumbers = {1, 2, 3, 3, 4}; // {1, 2, 3, 4}
uniqueNumbers.add(5);
uniqueNumbers.contains(3); // true

// 集合操作
var a = {1, 2, 3};
var b = {3, 4, 5};
var union = a.union(b);        // {1, 2, 3, 4, 5}
var intersection = a.intersection(b); // {3}
var difference = a.difference(b);     // {1, 2}

异步编程(Future/async-await/Stream)

dart
// Future - 表示一个异步操作的结果
Future<String> fetchUserName() async {
  // 模拟网络请求
  await Future.delayed(Duration(seconds: 2));
  return '张三';
}

// 使用 async/await
Future<void> loadData() async {
  print('开始加载...');
  try {
    String name = await fetchUserName();
    print('加载完成: $name');
  } catch (e) {
    print('加载失败: $e');
  } finally {
    print('加载结束');
  }
}

// Future 链式调用
Future<int> calculate() {
  return Future.value(10)
      .then((value) => value * 2)
      .then((value) => value + 5)
      .catchError((error) => 0);
}

// Stream - 异步数据序列
Stream<int> countStream(int max) async* {
  for (int i = 1; i <= max; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

// 监听 Stream
void listenStream() {
  countStream(5).listen(
    (data) => print('收到: $data'),
    onError: (error) => print('错误: $error'),
    onDone: () => print('完成'),
  );
}

// 使用 async/await 处理 Stream
Future<void> processStream() async {
  await for (var value in countStream(3)) {
    print('处理: $value');
  }
}

// StreamController - 创建自定义 Stream
import 'dart:async';

class MessageBus {
  final _controller = StreamController<String>.broadcast();

  Stream<String> get messages => _controller.stream;

  void send(String message) => _controller.add(message);

  void dispose() => _controller.close();
}

1.3 Flutter 基础组件

Widget 概念

在 Flutter 中,几乎一切都是 Widget。Widget 是 UI 的基本构建块,描述了界面的一部分配置。Widget 本身是不可变的,Flutter 通过重建 Widget 树来更新界面。

Widget 分为两大类:

  • StatelessWidget:无状态组件,一旦创建就不会改变
  • StatefulWidget:有状态组件,可以通过 setState 触发重建
dart
// StatelessWidget 示例
class GreetingCard extends StatelessWidget {
  final String title;
  final String subtitle;

  const GreetingCard({super.key, required this.title, required this.subtitle});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            Text(subtitle),
          ],
        ),
      ),
    );
  }
}

// StatefulWidget 示例
class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('计数: $_count', style: const TextStyle(fontSize: 24)),
        ElevatedButton(
          onPressed: () => setState(() => _count++),
          child: const Text('增加'),
        ),
      ],
    );
  }
}

Text 文本组件

dart
// 基本文本
const Text('Hello Flutter')

// 富文本样式
Text(
  '重要通知',
  style: TextStyle(
    fontSize: 18,
    fontWeight: FontWeight.bold,
    color: Colors.red,
    letterSpacing: 1.2,
    decoration: TextDecoration.underline,
  ),
)

// 富文本拼接
Text.rich(
  TextSpan(
    text: '已阅读并同意',
    style: const TextStyle(color: Colors.grey),
    children: [
      TextSpan(
        text: '《用户协议》',
        style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
      ),
      const TextSpan(text: '和'),
      TextSpan(
        text: '《隐私政策》',
        style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
      ),
    ],
  ),
)

// 文本溢出处理
Text(
  '这是一段很长的文本内容,需要截断显示省略号',
  maxLines: 1,
  overflow: TextOverflow.ellipsis,
)

Image 图片组件

dart
// 网络图片
Image.network(
  'https://example.com/photo.jpg',
  width: 200,
  height: 200,
  fit: BoxFit.cover, // 填充模式
  loadingBuilder: (context, child, loadingProgress) {
    if (loadingProgress == null) return child;
    return CircularProgressIndicator(
      value: loadingProgress.expectedTotalBytes != null
          ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
          : null,
    );
  },
  errorBuilder: (context, error, stackTrace) {
    return const Icon(Icons.broken_image, size: 100);
  },
)

// 本地资源图片(需在 pubspec.yaml 中配置)
Image.asset('assets/images/logo.png', width: 100)

// 文件图片
Image.file(File('/path/to/image.png'))

// 内存图片
Image.memory(uint8ListBytes)

// 圆形图片
CircleAvatar(
  radius: 50,
  backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
)

// 使用 cached_network_image 缓存网络图片
CachedNetworkImage(
  imageUrl: 'https://example.com/photo.jpg',
  placeholder: (context, url) => const CircularProgressIndicator(),
  errorWidget: (context, url, error) => const Icon(Icons.error),
)

Icon 图标组件

dart
// Material 图标
const Icon(Icons.home, size: 30, color: Colors.blue)
const Icon(Icons.favorite, size: 24, color: Colors.red)
const Icon(Icons.settings, size: 28)

// IconButton 可点击图标
IconButton(
  icon: const Icon(Icons.search),
  onPressed: () => print('搜索'),
  tooltip: '搜索',
)

// 带背景的图标
CircleAvatar(
  backgroundColor: Colors.blue,
  child: IconButton(
    icon: const Icon(Icons.add, color: Colors.white),
    onPressed: () {},
  ),
)

Button 按钮系列

dart
// ElevatedButton - 填充按钮
ElevatedButton(
  onPressed: () => print('点击'),
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.blue,
    foregroundColor: Colors.white,
    padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
  ),
  child: const Text('提交'),
)

// OutlinedButton - 边框按钮
OutlinedButton(
  onPressed: () => print('点击'),
  style: OutlinedButton.styleFrom(
    side: const BorderSide(color: Colors.blue),
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
  ),
  child: const Text('取消'),
)

// TextButton - 文本按钮
TextButton(
  onPressed: () => print('点击'),
  child: const Text('了解更多'),
)

// IconButton - 图标按钮
IconButton(
  icon: const Icon(Icons.favorite),
  color: Colors.red,
  onPressed: () {},
)

// FloatingActionButton - 浮动按钮
FloatingActionButton(
  onPressed: () => print('新增'),
  child: const Icon(Icons.add),
)

// 按钮组
ButtonBar(
  children: [
    TextButton(onPressed: () {}, child: const Text('取消')),
    ElevatedButton(onPressed: () {}, child: const Text('确认')),
  ],
)

TextField 输入框

dart
class MyForm extends StatefulWidget {
  const MyForm({super.key});

  @override
  State<MyForm> createState() => _MyFormState();
}

class _MyFormState extends State<MyForm> {
  final _controller = TextEditingController();

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

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      decoration: InputDecoration(
        labelText: '用户名',
        hintText: '请输入用户名',
        prefixIcon: const Icon(Icons.person),
        suffixIcon: IconButton(
          icon: const Icon(Icons.clear),
          onPressed: () => _controller.clear(),
        ),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(8),
        ),
        focusedBorder: OutlineInputBorder(
          borderRadius: BorderRadius.circular(8),
          borderSide: const BorderSide(color: Colors.blue, width: 2),
        ),
      ),
      onChanged: (value) => print('输入: $value'),
      onSubmitted: (value) => print('提交: $value'),
    );
  }
}

Checkbox/Radio/Switch 选择组件

dart
class SelectionDemo extends StatefulWidget {
  const SelectionDemo({super.key});

  @override
  State<SelectionDemo> createState() => _SelectionDemoState();
}

class _SelectionDemoState extends State<SelectionDemo> {
  bool _isChecked = false;
  String _gender = 'male';
  bool _isNotificationOn = true;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 复选框
        CheckboxListTile(
          title: const Text('同意用户协议'),
          value: _isChecked,
          onChanged: (value) => setState(() => _isChecked = value!),
        ),

        // 单选框
        RadioListTile<String>(
          title: const Text('男'),
          value: 'male',
          groupValue: _gender,
          onChanged: (value) => setState(() => _gender = value!),
        ),
        RadioListTile<String>(
          title: const Text('女'),
          value: 'female',
          groupValue: _gender,
          onChanged: (value) => setState(() => _gender = value!),
        ),

        // 开关
        SwitchListTile(
          title: const Text('消息通知'),
          value: _isNotificationOn,
          onChanged: (value) => setState(() => _isNotificationOn = value),
        ),
      ],
    );
  }
}

1.4 布局系统

Flutter 的布局系统基于约束(Constraints)传递机制:父组件向子组件传递约束,子组件根据约束决定自身大小,然后向父组件返回尺寸和位置。

Container 容器

dart
// 基本容器
Container(
  width: 200,
  height: 100,
  padding: const EdgeInsets.all(16),
  margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
  decoration: BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.circular(12),
    boxShadow: [
      BoxShadow(
        color: Colors.black.withOpacity(0.1),
        blurRadius: 10,
        offset: const Offset(0, 4),
      ),
    ],
  ),
  child: const Center(child: Text('容器内容')),
)

// 渐变背景
Container(
  width: double.infinity,
  height: 200,
  decoration: const BoxDecoration(
    gradient: LinearGradient(
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
      colors: [Colors.blue, Colors.purple],
    ),
  ),
  child: const Center(
    child: Text('渐变背景', style: TextStyle(color: Colors.white, fontSize: 24)),
  ),
)

Row/Column 行列布局

dart
// Row - 水平排列
Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 主轴对齐
  crossAxisAlignment: CrossAxisAlignment.center,     // 交叉轴对齐
  children: [
    Icon(Icons.star, color: Colors.yellow),
    Text('评分'),
    Text('4.5'),
  ],
)

// Column - 垂直排列
Column(
  mainAxisAlignment: MainAxisAlignment.start,
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Text('标题', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
    SizedBox(height: 8),
    Text('描述内容'),
    SizedBox(height: 16),
    Row(
      children: [
        Icon(Icons.location_on, size: 16),
        SizedBox(width: 4),
        Text('北京市'),
      ],
    ),
  ],
)

// MainAxisSize 控制大小
Row(
  mainAxisSize: MainAxisSize.min, // 仅占用子组件所需空间
  children: [
    Icon(Icons.check),
    SizedBox(width: 4),
    Text('已完成'),
  ],
)

Stack/Positioned 层叠布局

dart
// Stack - 层叠布局,子组件按顺序从底到顶堆叠
Stack(
  children: [
    // 底层 - 背景图
    Container(
      width: double.infinity,
      height: 200,
      decoration: BoxDecoration(
        image: DecorationImage(
          image: NetworkImage('https://example.com/bg.jpg'),
          fit: BoxFit.cover,
        ),
        borderRadius: BorderRadius.circular(12),
      ),
    ),

    // 中间层 - 半透明遮罩
    Positioned.fill(
      child: Container(
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(12),
          color: Colors.black.withOpacity(0.3),
        ),
      ),
    ),

    // 顶层 - 文字
    const Positioned(
      left: 16,
      bottom: 16,
      child: Text(
        '风景如画',
        style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
      ),
    ),

    // 右上角标签
    const Positioned(
      top: 8,
      right: 8,
      child: Chip(label: Text('热门')),
    ),
  ],
)

ListView 列表视图

dart
// 静态列表
ListView(
  padding: const EdgeInsets.all(16),
  children: [
    ListTile(leading: Icon(Icons.inbox), title: Text('收件箱'), trailing: Text('12')),
    Divider(),
    ListTile(leading: Icon(Icons.send), title: Text('已发送'), trailing: Text('3')),
    Divider(),
    ListTile(leading: Icon(Icons.drafts), title: Text('草稿箱')),
  ],
)

// 动态列表
ListView.builder(
  itemCount: 100,
  itemBuilder: (context, index) {
    return ListTile(
      leading: CircleAvatar(child: Text('${index + 1}')),
      title: Text('第 ${index + 1} 项'),
      subtitle: Text('这是第 ${index + 1} 项的描述'),
      trailing: Icon(Icons.chevron_right),
      onTap: () => print('点击了第 ${index + 1} 项'),
    );
  },
)

GridView 网格视图

dart
// 固定列数网格
GridView.count(
  crossAxisCount: 3, // 每行3列
  mainAxisSpacing: 10, // 主轴间距
  crossAxisSpacing: 10, // 交叉轴间距
  childAspectRatio: 1.0, // 子项宽高比
  children: List.generate(9, (index) {
    return Container(
      color: Colors.blue[(index % 9 + 1) * 100],
      child: Center(child: Text('Item ${index + 1}')),
    );
  }),
)

// 动态网格
GridView.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    mainAxisSpacing: 10,
    crossAxisSpacing: 10,
    childAspectRatio: 0.8,
  ),
  itemCount: 20,
  itemBuilder: (context, index) {
    return Card(
      child: Column(
        children: [
          Expanded(
            child: Container(color: Colors.grey[300]),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Text('商品 ${index + 1}'),
          ),
        ],
      ),
    );
  },
)

Wrap 流式布局

dart
// Wrap - 自动换行布局,适合标签、芯片等
Wrap(
  spacing: 8,    // 水平间距
  runSpacing: 8, // 垂直间距
  children: [
    'Flutter', 'Dart', 'Android', 'iOS', 'Web', 'Desktop', 'Fuchsia'
  ].map((tag) => Chip(
    label: Text(tag),
    avatar: CircleAvatar(
      backgroundColor: Colors.blue,
      child: Text(tag[0], style: const TextStyle(color: Colors.white)),
    ),
  )).toList(),
)

Expanded/Flexible 弹性布局

dart
Row(
  children: [
    // 固定宽度
    Container(width: 80, color: Colors.red, child: Text('固定80')),

    // Expanded - 占据剩余所有空间
    Expanded(
      child: Container(color: Colors.green, child: Text('Expanded')),
    ),

    // Flexible - 弹性占用,可以缩小
    Flexible(
      flex: 2, // 占比权重
      child: Container(color: Colors.blue, child: Text('Flexible flex:2')),
    ),

    // 另一个 Flexible
    Flexible(
      flex: 1,
      child: Container(color: Colors.yellow, child: Text('Flexible flex:1')),
    ),
  ],
)

// 经典布局:侧边栏 + 内容区
Row(
  children: [
    SizedBox(
      width: 200,
      child: NavigationDrawer(/* ... */),
    ),
    Expanded(
      child: ContentArea(/* ... */),
    ),
  ],
)

SizedBox 间距控制

dart
// 固定间距
const SizedBox(height: 16)
const SizedBox(width: 8)

// 固定尺寸容器
SizedBox(
  width: 100,
  height: 100,
  child: Container(color: Colors.blue),
)

// SizedBox.expand - 占满父组件
SizedBox.expand(
  child: Container(color: Colors.red),
)

// SizedBox.shrink - 零尺寸,常用于条件渲染
SizedBox.shrink() // 不占空间

1.5 状态管理入门

setState 工作原理

setState 是 Flutter 最基础的状态管理方式。调用 setState 后,Flutter 会标记当前 State 为脏(dirty),在下一帧重建该 Widget 的 build 方法。

dart
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _counter = 0;

  void _increment() {
    setState(() {
      _counter++; // 修改状态,触发重建
    });
  }

  void _decrement() {
    setState(() {
      _counter--;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('计数器')),
      body: Center(
        child: Text(
          '$_counter',
          style: const TextStyle(fontSize: 72),
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            heroTag: 'dec',
            onPressed: _decrement,
            child: const Icon(Icons.remove),
          ),
          const SizedBox(width: 16),
          FloatingActionButton(
            heroTag: 'inc',
            onPressed: _increment,
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

setState 只适用于简单、局部的状态管理。当状态需要跨组件共享时,应考虑 InheritedWidget、Provider 等方案。

StatefulWidget 生命周期

dart
class LifecycleDemo extends StatefulWidget {
  const LifecycleDemo({super.key});

  @override
  State<LifecycleDemo> createState() => _LifecycleDemoState();
}

class _LifecycleDemoState extends State<LifecycleDemo> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    // 1. 初始化时调用,只执行一次
    // 适合做初始化操作:控制器初始化、数据加载等
    WidgetsBinding.instance.addObserver(this);
    print('initState');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // 2. initState 之后立即调用
    // 或 InheritedWidget 更新时调用
    print('didChangeDependencies');
  }

  @override
  void didUpdateWidget(covariant LifecycleDemo oldWidget) {
    super.didUpdateWidget(oldWidget);
    // 3. 父组件重建时调用
    // 可以根据新旧 widget 差异做处理
    print('didUpdateWidget');
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    // 应用前后台切换
    print('AppLifecycleState: $state');
  }

  @override
  Widget build(BuildContext context) {
    // 5. 构建UI
    print('build');
    return const Scaffold(body: Center(child: Text('生命周期演示')));
  }

  @override
  void deactivate() {
    // 6. 从树中移除时调用(可能重新插入)
    print('deactivate');
    super.deactivate();
  }

  @override
  void dispose() {
    // 7. 组件销毁时调用,只执行一次
    // 必须做清理工作:取消监听、释放控制器等
    WidgetsBinding.instance.removeObserver(this);
    print('dispose');
    super.dispose();
  }
}

InheritedWidget 简介

InheritedWidget 是 Flutter 中数据向下传递的核心机制,子组件可以通过 of 方法获取祖先节点的数据,当数据变化时自动重建依赖的子组件。

dart
// 定义 InheritedWidget
class CounterInheritedWidget extends InheritedWidget {
  final int count;
  final VoidCallback increment;

  const CounterInheritedWidget({
    super.key,
    required this.count,
    required this.increment,
    required super.child,
  });

  // 提供便捷获取方法
  static CounterInheritedWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<CounterInheritedWidget>();
  }

  // 决定是否通知依赖的子组件重建
  @override
  bool updateShouldNotify(CounterInheritedWidget oldWidget) {
    return count != oldWidget.count;
  }
}

// 在父组件中使用
class CounterProvider extends StatefulWidget {
  const CounterProvider({super.key, required this.child});
  final Widget child;

  @override
  State<CounterProvider> createState() => _CounterProviderState();
}

class _CounterProviderState extends State<CounterProvider> {
  int _count = 0;

  void _increment() => setState(() => _count++);

  @override
  Widget build(BuildContext context) {
    return CounterInheritedWidget(
      count: _count,
      increment: _increment,
      child: widget.child,
    );
  }
}

// 在子组件中获取数据
class CounterDisplay extends StatelessWidget {
  const CounterDisplay({super.key});

  @override
  Widget build(BuildContext context) {
    final data = CounterInheritedWidget.of(context);
    return Column(
      children: [
        Text('计数: ${data?.count ?? 0}'),
        ElevatedButton(
          onPressed: data?.increment,
          child: const Text('增加'),
        ),
      ],
    );
  }
}

1.6 路由与导航

dart
// 跳转到新页面
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const DetailPage()),
);

// 返回上一页
Navigator.pop(context);

// 带参数跳转
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailPage(itemId: 42, title: '详情'),
  ),
);

// 带结果返回
// 发起方
final result = await Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const SelectPage()),
);
print('选择结果: $result');

// 接收方返回结果
Navigator.pop(context, '选中的选项');

命名路由

dart
// 在 MaterialApp 中定义路由表
MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => const HomePage(),
    '/detail': (context) => const DetailPage(),
    '/settings': (context) => const SettingsPage(),
    '/profile': (context) => const ProfilePage(),
  },
)

// 使用命名路由跳转
Navigator.pushNamed(context, '/detail');

// 带参数的命名路由
Navigator.pushNamed(
  context,
  '/detail',
  arguments: {'id': 42, 'name': '张三'},
);

// 在目标页面获取参数
class DetailPage extends StatelessWidget {
  const DetailPage({super.key});

  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?;
    final id = args?['id'];
    final name = args?['name'];

    return Scaffold(
      appBar: AppBar(title: Text('详情 - $name')),
      body: Center(child: Text('ID: $id')),
    );
  }
}

路由传参与返回结果

dart
// 完整示例:商品列表 -> 商品详情 -> 确认购买
class ProductListPage extends StatelessWidget {
  const ProductListPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('商品列表')),
      body: ListView.builder(
        itemCount: 10,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('商品 ${index + 1}'),
            subtitle: Text(${(index + 1) * 99.9}'),
            onTap: () async {
              // 跳转并等待返回结果
              final result = await Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => ProductDetailPage(productId: index + 1),
                ),
              );
              if (result != null && result is String) {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text(result)),
                );
              }
            },
          );
        },
      ),
    );
  }
}

class ProductDetailPage extends StatelessWidget {
  final int productId;
  const ProductDetailPage({super.key, required this.productId});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('商品 $productId')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 返回购买结果
            Navigator.pop(context, '已购买商品 $productId');
          },
          child: const Text('立即购买'),
        ),
      ),
    );
  }
}

1.7 资源管理

图片资源管理

yaml
# pubspec.yaml
flutter:
  assets:
    - assets/images/
    - assets/images/2x/
    - assets/data/config.json
dart
// 加载资源图片
Image.asset('assets/images/logo.png')

// 不同分辨率适配(1x, 2x, 3x)
// 目录结构:
// assets/images/logo.png      (1x)
// assets/images/2.0x/logo.png (2x)
// assets/images/3.0x/logo.png (3x)
// Flutter 会根据设备像素密度自动选择

// 加载 JSON 资源
Future<Map<String, dynamic>> loadConfig() async {
  final jsonStr = await rootBundle.loadString('assets/data/config.json');
  return json.decode(jsonStr);
}

字体资源管理

yaml
# pubspec.yaml
flutter:
  fonts:
    - family: Roboto
      fonts:
        - asset: assets/fonts/Roboto-Regular.ttf
        - asset: assets/fonts/Roboto-Bold.ttf
          weight: 700
        - asset: assets/fonts/Roboto-Italic.ttf
          style: italic
    - family: CustomIcons
      fonts:
        - asset: assets/fonts/CustomIcons.ttf
dart
// 使用自定义字体
Text(
  '自定义字体',
  style: TextStyle(
    fontFamily: 'Roboto',
    fontSize: 16,
    fontWeight: FontWeight.w700,
  ),
)

// 全局字体配置
MaterialApp(
  theme: ThemeData(
    fontFamily: 'Roboto',
  ),
)

pubspec.yaml 配置说明

yaml
name: my_app               # 包名
description: 我的Flutter应用  # 描述
version: 1.0.0+1           # 版本号+构建号

environment:
  sdk: '>=3.0.0 <4.0.0'   # Dart SDK 版本约束

dependencies:               # 生产依赖
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.6  # iOS 风格图标
  http: ^1.1.0             # 网络请求
  shared_preferences: ^2.2.0 # 本地存储

dev_dependencies:           # 开发依赖
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.0    # 代码规范

flutter:                    # Flutter 特有配置
  uses-material-design: true # 使用 Material 图标
  assets:
    - assets/images/
    - assets/data/
  fonts:
    - family: Roboto
      fonts:
        - asset: assets/fonts/Roboto-Regular.ttf

二、中级篇 - 开发常用功能

2.1 表单处理与验证

Form/FormField 基础

dart
class LoginForm extends StatefulWidget {
  const LoginForm({super.key});

  @override
  State<LoginForm> createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  // 提交表单
  void _submit() {
    if (_formKey.currentState!.validate()) {
      // 验证通过,执行登录逻辑
      print('邮箱: ${_emailController.text}');
      print('密码: ${_passwordController.text}');
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('登录成功')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          // 邮箱输入
          TextFormField(
            controller: _emailController,
            decoration: const InputDecoration(
              labelText: '邮箱',
              prefixIcon: Icon(Icons.email),
            ),
            keyboardType: TextInputType.emailAddress,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return '请输入邮箱';
              }
              if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
                return '请输入有效的邮箱地址';
              }
              return null; // 验证通过
            },
          ),
          const SizedBox(height: 16),

          // 密码输入
          TextFormField(
            controller: _passwordController,
            decoration: const InputDecoration(
              labelText: '密码',
              prefixIcon: Icon(Icons.lock),
            ),
            obscureText: true,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return '请输入密码';
              }
              if (value.length < 6) {
                return '密码至少6位';
              }
              return null;
            },
          ),
          const SizedBox(height: 24),

          // 提交按钮
          SizedBox(
            width: double.infinity,
            child: ElevatedButton(
              onPressed: _submit,
              child: const Text('登录'),
            ),
          ),
        ],
      ),
    );
  }
}

自定义验证器

dart
// 通用验证器工具类
class Validators {
  // 必填验证
  static String? required(String? value, [String field = '此字段']) {
    if (value == null || value.trim().isEmpty) {
      return '$field不能为空';
    }
    return null;
  }

  // 手机号验证
  static String? phone(String? value) {
    if (value == null || value.isEmpty) return '请输入手机号';
    if (!RegExp(r'^1[3-9]\d{9}$').hasMatch(value)) return '请输入有效的手机号';
    return null;
  }

  // 密码强度验证
  static String? password(String? value) {
    if (value == null || value.isEmpty) return '请输入密码';
    if (value.length < 8) return '密码至少8位';
    if (!RegExp(r'[A-Z]').hasMatch(value)) return '密码需包含大写字母';
    if (!RegExp(r'[a-z]').hasMatch(value)) return '密码需包含小写字母';
    if (!RegExp(r'[0-9]').hasMatch(value)) return '密码需包含数字';
    return null;
  }

  // 确认密码
  static String Function(String?) confirmPassword(String password) {
    return (String? value) {
      if (value != password) return '两次密码不一致';
      return null;
    };
  }

  // 组合验证器
  static String? Function(String?) compose(List<String? Function(String?)> validators) {
    return (String? value) {
      for (final validator in validators) {
        final result = validator(value);
        if (result != null) return result;
      }
      return null;
    };
  }
}

// 使用自定义验证器
TextFormField(
  validator: Validators.compose([
    (v) => Validators.required(v, '手机号'),
    Validators.phone,
  ]),
)

2.2 列表与懒加载

ListView.builder/separated

dart
// ListView.builder - 大数据量列表,按需构建
ListView.builder(
  itemCount: 1000,
  itemExtent: 72, // 固定项高度,提升性能
  itemBuilder: (context, index) {
    return ListTile(
      leading: CircleAvatar(child: Text('${index + 1}')),
      title: Text('联系人 ${index + 1}'),
      subtitle: Text('手机号: 138${index.toString().padLeft(8, '0')}'),
    );
  },
)

// ListView.separated - 带分隔线的列表
ListView.separated(
  itemCount: 20,
  separatorBuilder: (context, index) => const Divider(height: 1),
  itemBuilder: (context, index) {
    return ListTile(
      title: Text('消息 ${index + 1}'),
      subtitle: Text('这是消息内容...'),
      trailing: Text('10:${index.toString().padLeft(2, '0')}'),
    );
  },
)

RefreshIndicator 下拉刷新

dart
class RefreshListPage extends StatefulWidget {
  const RefreshListPage({super.key});

  @override
  State<RefreshListPage> createState() => _RefreshListPageState();
}

class _RefreshListPageState extends State<RefreshListPage> {
  List<String> _items = List.generate(20, (i) => 'Item ${i + 1}');

  // 下拉刷新
  Future<void> _onRefresh() async {
    await Future.delayed(const Duration(seconds: 2)); // 模拟网络请求
    setState(() {
      _items = List.generate(20, (i) => '刷新后 Item ${i + 1}');
    });
  }

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: _onRefresh,
      color: Colors.blue,
      child: ListView.builder(
        itemCount: _items.length,
        itemBuilder: (context, index) {
          return ListTile(title: Text(_items[index]));
        },
      ),
    );
  }
}

上拉加载更多

dart
class LoadMoreListPage extends StatefulWidget {
  const LoadMoreListPage({super.key});

  @override
  State<LoadMoreListPage> createState() => _LoadMoreListPageState();
}

class _LoadMoreListPageState extends State<LoadMoreListPage> {
  final ScrollController _scrollController = ScrollController();
  List<String> _items = [];
  int _page = 1;
  bool _isLoading = false;
  bool _hasMore = true;

  @override
  void initState() {
    super.initState();
    _loadData();

    // 监听滚动到底部
    _scrollController.addListener(() {
      if (_scrollController.position.pixels >=
          _scrollController.position.maxScrollExtent - 100) {
        if (!_isLoading && _hasMore) {
          _loadData();
        }
      }
    });
  }

  Future<void> _loadData() async {
    if (_isLoading) return;
    setState(() => _isLoading = true);

    // 模拟网络请求
    await Future.delayed(const Duration(seconds: 1));

    setState(() {
      _items.addAll(List.generate(20, (i) => 'Page $_page - Item ${i + 1}'));
      _page++;
      _isLoading = false;
      if (_page > 5) _hasMore = false; // 模拟没有更多数据
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _scrollController,
      itemCount: _items.length + 1, // +1 用于底部加载指示器
      itemBuilder: (context, index) {
        if (index == _items.length) {
          // 底部加载指示器
          return Padding(
            padding: const EdgeInsets.all(16),
            child: Center(
              child: _hasMore
                  ? const CircularProgressIndicator()
                  : const Text('没有更多数据了'),
            ),
          );
        }
        return ListTile(title: Text(_items[index]));
      },
    );
  }
}

AutomaticKeepAliveClientMixin

在列表中保持页面状态,防止切换后重建。

dart
class KeepAliveTab extends StatefulWidget {
  const KeepAliveTab({super.key});

  @override
  State<KeepAliveTab> createState() => _KeepAliveTabState();
}

class _KeepAliveTabState extends State<KeepAliveTab>
    with AutomaticKeepAliveClientMixin {
  int _counter = 0;

  @override
  bool get wantKeepAlive => true; // 保持存活

  @override
  Widget build(BuildContext context) {
    super.build(context); // 必须调用
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('计数: $_counter', style: const TextStyle(fontSize: 24)),
          ElevatedButton(
            onPressed: () => setState(() => _counter++),
            child: const Text('增加'),
          ),
        ],
      ),
    );
  }
}

// 在 TabBarView 中使用
class TabDemo extends StatelessWidget {
  const TabDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Tab Demo'),
          bottom: const TabBar(
            tabs: [
              Tab(text: '推荐'),
              Tab(text: '热门'),
              Tab(text: '最新'),
            ],
          ),
        ),
        body: const TabBarView(
          children: [
            KeepAliveTab(), // 切换 Tab 后状态保持
            KeepAliveTab(),
            KeepAliveTab(),
          ],
        ),
      ),
    );
  }
}

2.3 动画系统

隐式动画(AnimatedContainer/AnimatedOpacity)

隐式动画无需手动控制,只需设置目标值,Flutter 自动完成过渡动画。

dart
class ImplicitAnimationDemo extends StatefulWidget {
  const ImplicitAnimationDemo({super.key});

  @override
  State<ImplicitAnimationDemo> createState() => _ImplicitAnimationDemoState();
}

class _ImplicitAnimationDemoState extends State<ImplicitAnimationDemo> {
  bool _expanded = false;
  bool _visible = true;
  double _opacity = 1.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('隐式动画')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // AnimatedContainer - 容器属性动画
            AnimatedContainer(
              duration: const Duration(milliseconds: 500),
              curve: Curves.easeInOut,
              width: _expanded ? 200 : 100,
              height: _expanded ? 200 : 100,
              decoration: BoxDecoration(
                color: _expanded ? Colors.blue : Colors.red,
                borderRadius: BorderRadius.circular(_expanded ? 20 : 8),
              ),
              child: const Center(child: Text('点击切换')),
            ),
            const SizedBox(height: 32),

            // AnimatedOpacity - 透明度动画
            AnimatedOpacity(
              duration: const Duration(milliseconds: 300),
              opacity: _opacity,
              child: const Text('渐隐渐显', style: TextStyle(fontSize: 24)),
            ),

            const SizedBox(height: 32),

            // AnimatedSwitcher - 切换动画
            AnimatedSwitcher(
              duration: const Duration(milliseconds: 300),
              child: Text(
                _visible ? '显示状态' : '隐藏状态',
                key: ValueKey(_visible),
                style: const TextStyle(fontSize: 20),
              ),
            ),

            const SizedBox(height: 32),

            ElevatedButton(
              onPressed: () => setState(() {
                _expanded = !_expanded;
                _opacity = _opacity == 1.0 ? 0.2 : 1.0;
                _visible = !_visible;
              }),
              child: const Text('切换动画'),
            ),
          ],
        ),
      ),
    );
  }
}

显式动画(AnimationController/Tween)

显式动画需要手动创建 AnimationController 并控制动画的播放。

dart
class ExplicitAnimationDemo extends StatefulWidget {
  const ExplicitAnimationDemo({super.key});

  @override
  State<ExplicitAnimationDemo> createState() => _ExplicitAnimationDemoState();
}

class _ExplicitAnimationDemoState extends State<ExplicitAnimationDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );

    // Tween 定义动画范围
    _animation = Tween<double>(begin: 0, end: 1).animate(
      CurvedAnimation(parent: _controller, curve: Curves.elasticOut),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('显式动画')),
      body: Center(
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Transform.scale(
              scale: _animation.value,
              child: Transform.rotate(
                angle: _animation.value * 2 * 3.14159,
                child: child,
              ),
            );
          },
          child: Container(
            width: 100,
            height: 100,
            decoration: BoxDecoration(
              color: Colors.blue,
              borderRadius: BorderRadius.circular(12),
            ),
            child: const Center(
              child: Icon(Icons.star, color: Colors.white, size: 40),
            ),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          if (_controller.isCompleted) {
            _controller.reverse();
          } else {
            _controller.forward();
          }
        },
        child: const Icon(Icons.play_arrow),
      ),
    );
  }
}

Hero 动画

Hero 动画实现页面间的共享元素过渡效果。

dart
// 列表页
class HeroListPage extends StatelessWidget {
  const HeroListPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Hero 列表')),
      body: GridView.builder(
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          mainAxisSpacing: 8,
          crossAxisSpacing: 8,
        ),
        itemCount: 6,
        itemBuilder: (context, index) {
          return GestureDetector(
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => HeroDetailPage(index: index),
                ),
              );
            },
            child: Hero(
              tag: 'hero_$index', // 唯一标签,两个页面必须一致
              child: Container(
                color: Colors.primaries[index % Colors.primaries.length],
                child: Center(
                  child: Icon(
                    Icons.star,
                    color: Colors.white,
                    size: 40,
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

// 详情页
class HeroDetailPage extends StatelessWidget {
  final int index;
  const HeroDetailPage({super.key, required this.index});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('详情 ${index + 1}')),
      body: Center(
        child: Hero(
          tag: 'hero_$index', // 与列表页标签一致
          child: Container(
            width: 200,
            height: 200,
            color: Colors.primaries[index % Colors.primaries.length],
            child: const Center(
              child: Icon(Icons.star, color: Colors.white, size: 80),
            ),
          ),
        ),
      ),
    );
  }
}

交错动画

多个动画按顺序依次执行,形成波浪式效果。

dart
class StaggeredAnimationDemo extends StatefulWidget {
  const StaggeredAnimationDemo({super.key});

  @override
  State<StaggeredAnimationDemo> createState() => _StaggeredAnimationDemoState();
}

class _StaggeredAnimationDemoState extends State<StaggeredAnimationDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 1500),
      vsync: this,
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('交错动画')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: List.generate(5, (index) {
            // 每个元素的动画间隔不同
            final start = index * 0.1;
            final end = start + 0.5;
            return SlideTransition(
              position: Tween<Offset>(
                begin: const Offset(-1, 0),
                end: Offset.zero,
              ).animate(
                CurvedAnimation(
                  parent: _controller,
                  curve: Interval(start, end, curve: Curves.easeOut),
                ),
              ),
              child: FadeTransition(
                opacity: CurvedAnimation(
                  parent: _controller,
                  curve: Interval(start, end),
                ),
                child: Padding(
                  padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 32),
                  child: Container(
                    height: 50,
                    decoration: BoxDecoration(
                      color: Colors.primaries[index],
                      borderRadius: BorderRadius.circular(8),
                    ),
                  ),
                ),
              ),
            );
          }),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          if (_controller.isCompleted) {
            _controller.reverse();
          } else {
            _controller.forward();
          }
        },
        child: const Icon(Icons.play_arrow),
      ),
    );
  }
}

2.4 本地存储

SharedPreferences

适合存储简单的键值对数据,如用户设置、登录状态等。

dart
import 'package:shared_preferences/shared_preferences.dart';

class StorageService {
  // 保存数据
  Future<void> saveToken(String token) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('auth_token', token);
  }

  Future<void> saveSettings({
    required bool darkMode,
    required String language,
  }) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setBool('dark_mode', darkMode);
    await prefs.setString('language', language);
  }

  // 读取数据
  Future<String?> getToken() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getString('auth_token');
  }

  Future<bool> isDarkMode() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getBool('dark_mode') ?? false;
  }

  // 删除数据
  Future<void> clearAuth() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.remove('auth_token');
  }

  // 清空所有数据
  Future<void> clearAll() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.clear();
  }
}

sqflite 数据库

适合存储结构化数据,如聊天记录、离线数据等。

dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DatabaseHelper {
  static Database? _database;

  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }

  Future<Database> _initDatabase() async {
    final dbPath = await getDatabasesPath();
    return openDatabase(
      join(dbPath, 'app.db'),
      version: 1,
      onCreate: (db, version) async {
        // 创建表
        await db.execute('''
          CREATE TABLE users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            email TEXT UNIQUE NOT NULL,
            age INTEGER,
            created_at TEXT DEFAULT CURRENT_TIMESTAMP
          )
        ''');

        await db.execute('''
          CREATE TABLE notes (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            user_id INTEGER NOT NULL,
            title TEXT NOT NULL,
            content TEXT,
            updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (user_id) REFERENCES users (id)
          )
        ''');
      },
      onUpgrade: (db, oldVersion, newVersion) async {
        // 数据库升级
        if (oldVersion < 2) {
          await db.execute('ALTER TABLE users ADD COLUMN avatar TEXT');
        }
      },
    );
  }

  // 增
  Future<int> insertUser(Map<String, dynamic> user) async {
    final db = await database;
    return db.insert('users', user);
  }

  // 查
  Future<List<Map<String, dynamic>>> getUsers() async {
    final db = await database;
    return db.query('users', orderBy: 'created_at DESC');
  }

  Future<Map<String, dynamic>?> getUserById(int id) async {
    final db = await database;
    final results = await db.query('users', where: 'id = ?', whereArgs: [id]);
    return results.isNotEmpty ? results.first : null;
  }

  // 改
  Future<int> updateUser(Map<String, dynamic> user) async {
    final db = await database;
    return db.update('users', user, where: 'id = ?', whereArgs: [user['id']]);
  }

  // 删
  Future<int> deleteUser(int id) async {
    final db = await database;
    return db.delete('users', where: 'id = ?', whereArgs: [id]);
  }

  // 事务
  Future<void> transferNote(int fromUserId, int toUserId) async {
    final db = await database;
    await db.transaction((txn) async {
      await txn.update(
        'notes',
        {'user_id': toUserId},
        where: 'user_id = ?',
        whereArgs: [fromUserId],
      );
      await txn.delete('users', where: 'id = ?', whereArgs: [fromUserId]);
    });
  }
}

文件存储

适合存储较大的文件数据,如图片、PDF、日志等。

dart
import 'dart:io';
import 'package:path_provider/path_provider.dart';

class FileStorage {
  // 获取文档目录(可被系统备份)
  Future<Directory> get _documentsDir async {
    return getApplicationDocumentsDirectory();
  }

  // 获取临时目录(系统可随时清理)
  Future<Directory> get _tempDir async {
    return getTemporaryDirectory();
  }

  // 写入文本文件
  Future<File> writeText(String filename, String content) async {
    final dir = await _documentsDir;
    final file = File('${dir.path}/$filename');
    return file.writeAsString(content);
  }

  // 读取文本文件
  Future<String> readText(String filename) async {
    final dir = await _documentsDir;
    final file = File('${dir.path}/$filename');
    if (await file.exists()) {
      return file.readAsString();
    }
    throw FileSystemException('文件不存在', filename);
  }

  // 写入字节文件
  Future<File> writeBytes(String filename, List<int> bytes) async {
    final dir = await _documentsDir;
    final file = File('${dir.path}/$filename');
    return file.writeAsBytes(bytes);
  }

  // 删除文件
  Future<void> deleteFile(String filename) async {
    final dir = await _documentsDir;
    final file = File('${dir.path}/$filename');
    if (await file.exists()) {
      await file.delete();
    }
  }

  // 列出目录下所有文件
  Future<List<FileSystemEntity>> listFiles() async {
    final dir = await _documentsDir;
    return dir.list().toList();
  }
}

2.5 网络请求

http 包

Dart 官方提供的轻量级 HTTP 客户端。

dart
import 'dart:convert';
import 'package:http/http.dart' as http;

class HttpService {
  static const String baseUrl = 'https://api.example.com';

  // GET 请求
  Future<List<User>> getUsers() async {
    final response = await http.get(
      Uri.parse('$baseUrl/users'),
      headers: {'Authorization': 'Bearer token123'},
    );

    if (response.statusCode == 200) {
      final List data = json.decode(response.body);
      return data.map((json) => User.fromJson(json)).toList();
    } else {
      throw Exception('请求失败: ${response.statusCode}');
    }
  }

  // POST 请求
  Future<User> createUser(String name, String email) async {
    final response = await http.post(
      Uri.parse('$baseUrl/users'),
      headers: {'Content-Type': 'application/json'},
      body: json.encode({'name': name, 'email': email}),
    );

    if (response.statusCode == 201) {
      return User.fromJson(json.decode(response.body));
    } else {
      throw Exception('创建失败: ${response.statusCode}');
    }
  }

  // PUT 请求
  Future<User> updateUser(int id, Map<String, dynamic> data) async {
    final response = await http.put(
      Uri.parse('$baseUrl/users/$id'),
      headers: {'Content-Type': 'application/json'},
      body: json.encode(data),
    );

    if (response.statusCode == 200) {
      return User.fromJson(json.decode(response.body));
    } else {
      throw Exception('更新失败: ${response.statusCode}');
    }
  }

  // DELETE 请求
  Future<void> deleteUser(int id) async {
    final response = await http.delete(
      Uri.parse('$baseUrl/users/$id'),
      headers: {'Authorization': 'Bearer token123'},
    );

    if (response.statusCode != 204) {
      throw Exception('删除失败: ${response.statusCode}');
    }
  }
}

// 数据模型
class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }

  Map<String, dynamic> toJson() => {'id': id, 'name': name, 'email': email};
}

dio 包(拦截器、取消请求、文件上传下载)

功能更强大的 HTTP 客户端,支持拦截器、取消请求、FormData 等。

dart
import 'package:dio/dio.dart';

class DioService {
  late final Dio _dio;

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

    // 添加拦截器
    _dio.interceptors.addAll([
      _authInterceptor(),
      _logInterceptor(),
    ]);
  }

  // 认证拦截器 - 自动添加 Token
  Interceptor _authInterceptor() {
    return InterceptorsWrapper(
      onRequest: (options, handler) {
        final token = 'your_token_here';
        if (token.isNotEmpty) {
          options.headers['Authorization'] = 'Bearer $token';
        }
        handler.next(options);
      },
      onError: (error, handler) async {
        // Token 过期自动刷新
        if (error.response?.statusCode == 401) {
          try {
            final newToken = await _refreshToken();
            error.requestOptions.headers['Authorization'] = 'Bearer $newToken';
            final response = await _dio.fetch(error.requestOptions);
            handler.resolve(response);
          } catch (e) {
            handler.reject(error);
          }
        } else {
          handler.next(error);
        }
      },
    );
  }

  // 日志拦截器
  Interceptor _logInterceptor() {
    return LogInterceptor(
      request: true,
      requestBody: true,
      responseBody: true,
      error: true,
    );
  }

  Future<String> _refreshToken() async {
    final response = await _dio.post('/auth/refresh');
    return response.data['token'];
  }

  // GET 请求
  Future<Response> get(String path, {Map<String, dynamic>? params}) {
    return _dio.get(path, queryParameters: params);
  }

  // POST 请求
  Future<Response> post(String path, {dynamic data}) {
    return _dio.post(path, data: data);
  }

  // 取消请求
  Future<void> searchWithCancel() async {
    final cancelToken = CancelToken();

    // 5秒后自动取消
    Future.delayed(const Duration(seconds: 5), () {
      cancelToken.cancel('请求超时取消');
    });

    try {
      final response = await _dio.get(
        '/search',
        queryParameters: {'q': 'Flutter'},
        cancelToken: cancelToken,
      );
      print(response.data);
    } on DioException catch (e) {
      if (CancelToken.isCancel(e)) {
        print('请求被取消: ${e.message}');
      } else {
        print('请求错误: $e');
      }
    }
  }

  // 文件上传
  Future<Response> uploadFile(String filePath) async {
    final formData = FormData.fromMap({
      'file': await MultipartFile.fromFile(filePath, filename: 'upload.jpg'),
      'description': '文件描述',
    });
    return _dio.post('/upload', data: formData);
  }

  // 多文件上传
  Future<Response> uploadFiles(List<String> filePaths) async {
    final files = await Future.wait(
      filePaths.map((path) => MultipartFile.fromFile(path)),
    );
    final formData = FormData.fromMap({
      'files': files,
      'category': 'photos',
    });
    return _dio.post('/upload/batch', data: formData);
  }

  // 文件下载(带进度)
  Future<void> downloadFile(String url, String savePath) async {
    await _dio.download(
      url,
      savePath,
      onReceiveProgress: (received, total) {
        if (total != -1) {
          final progress = (received / total * 100).toStringAsFixed(0);
          print('下载进度: $progress%');
        }
      },
    );
  }
}

JSON 序列化

dart
// 手动序列化(适合小项目)
class User {
  final int id;
  final String name;
  final String? email;
  final Address? address;

  User({required this.id, required this.name, this.email, this.address});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      email: json['email'] as String?,
      address: json['address'] != null
          ? Address.fromJson(json['address'] as Map<String, dynamic>)
          : null,
    );
  }

  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'email': email,
    'address': address?.toJson(),
  };
}

class Address {
  final String city;
  final String street;

  Address({required this.city, required this.street});

  factory Address.fromJson(Map<String, dynamic> json) {
    return Address(
      city: json['city'] as String,
      street: json['street'] as String,
    );
  }

  Map<String, dynamic> toJson() => {'city': city, 'street': street};
}

// 使用 json_serializable 自动生成(推荐大项目)
// 1. 添加依赖
// dependencies:
//   json_annotation: ^4.8.0
// dev_dependencies:
//   build_runner: ^2.3.0
//   json_serializable: ^6.6.0

// 2. 编写模型
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart'; // 生成文件

@JsonSerializable()
class UserAuto {
  final int id;
  final String name;
  @JsonKey(name: 'email_address') // JSON 字段名映射
  final String? email;
  @JsonKey(defaultValue: false)
  final bool isActive;

  UserAuto({required this.id, required this.name, this.email, this.isActive = false});

  factory UserAuto.fromJson(Map<String, dynamic> json) => _$UserAutoFromJson(json);
  Map<String, dynamic> toJson() => _$UserAutoToJson(this);
}

// 3. 运行生成命令
// flutter pub run build_runner build
// flutter pub run build_runner watch  # 监听变化自动生成

2.6 状态管理进阶

Provider(ChangeNotifier/Consumer)

Provider 是 Flutter 官方推荐的状态管理方案,基于 InheritedWidget 封装。

dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// 1. 定义 Model
class CartModel extends ChangeNotifier {
  final List<Item> _items = [];

  List<Item> get items => List.unmodifiable(_items);
  int get totalCount => _items.length;
  double get totalPrice => _items.fold(0, (sum, item) => sum + item.price);

  void addItem(Item item) {
    _items.add(item);
    notifyListeners(); // 通知监听者
  }

  void removeItem(int index) {
    _items.removeAt(index);
    notifyListeners();
  }

  void clear() {
    _items.clear();
    notifyListeners();
  }
}

class Item {
  final String name;
  final double price;
  Item({required this.name, required this.price});
}

// 2. 在顶层提供 Provider
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CartModel(),
      child: const MyApp(),
    ),
  );
}

// 3. 使用 Consumer 消费数据
class CartPage extends StatelessWidget {
  const CartPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('购物车'),
        actions: [
          Consumer<CartModel>(
            builder: (context, cart, child) {
              return Badge(
                label: Text('${cart.totalCount}'),
                child: const Icon(Icons.shopping_cart),
              );
            },
          ),
        ],
      ),
      body: Consumer<CartModel>(
        builder: (context, cart, child) {
          if (cart.items.isEmpty) {
            return const Center(child: Text('购物车为空'));
          }
          return ListView.builder(
            itemCount: cart.items.length,
            itemBuilder: (context, index) {
              final item = cart.items[index];
              return ListTile(
                title: Text(item.name),
                trailing: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text(${item.price}'),
                    IconButton(
                      icon: const Icon(Icons.delete),
                      onPressed: () => cart.removeItem(index),
                    ),
                  ],
                ),
              );
            },
          );
        },
      ),
      bottomNavigationBar: Consumer<CartModel>(
        builder: (context, cart, child) {
          return Container(
            padding: const EdgeInsets.all(16),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text('总计: ¥${cart.totalPrice.toStringAsFixed(2)}',
                    style: const TextStyle(fontSize: 18)),
                ElevatedButton(
                  onPressed: cart.items.isEmpty ? null : () => cart.clear(),
                  child: const Text('结算'),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

// 4. 使用 context.read 修改数据(不监听)
class ProductPage extends StatelessWidget {
  const ProductPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: [
          ListTile(
            title: const Text('Flutter 书籍'),
            subtitle: const Text('¥99.9'),
            trailing: IconButton(
              icon: const Icon(Icons.add_shopping_cart),
              onPressed: () {
                context.read<CartModel>().addItem(
                  Item(name: 'Flutter 书籍', price: 99.9),
                );
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('已加入购物车')),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

Riverpod

Riverpod 是 Provider 的进化版,编译时安全,不依赖 BuildContext。

dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 1. 定义 Provider
// 简单值 Provider
final greetingProvider = Provider<String>((ref) => 'Hello Riverpod');

// StateProvider - 简单状态
final counterProvider = StateProvider<int>((ref) => 0);

// StateNotifierProvider - 复杂状态
class TodoList extends StateNotifier<List<Todo>> {
  TodoList() : super([]);

  void add(String title) {
    state = [...state, Todo(id: DateTime.now().toString(), title: title)];
  }

  void toggle(String id) {
    state = state.map((todo) {
      if (todo.id == id) return todo.copyWith(completed: !todo.completed);
      return todo;
    }).toList();
  }

  void remove(String id) {
    state = state.where((todo) => todo.id != id).toList();
  }
}

final todoListProvider = StateNotifierProvider<TodoList, List<Todo>>((ref) {
  return TodoList();
});

// FutureProvider - 异步数据
final userListProvider = FutureProvider<List<User>>((ref) async {
  final response = await http.get(Uri.parse('https://api.example.com/users'));
  final List data = json.decode(response.body);
  return data.map((e) => User.fromJson(e)).toList();
});

// 2. 在顶层提供
void main() {
  runApp(const ProviderScope(child: MyApp()));
}

// 3. 在 Widget 中使用
class TodoPage extends ConsumerWidget {
  const TodoPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final todos = ref.watch(todoListProvider); // 监听状态
    final count = ref.watch(counterProvider);  // 监听简单状态

    return Scaffold(
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          final todo = todos[index];
          return CheckboxListTile(
            value: todo.completed,
            title: Text(todo.title),
            onChanged: (_) => ref.read(todoListProvider.notifier).toggle(todo.id),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          ref.read(todoListProvider.notifier).add('新任务');
          ref.read(counterProvider.notifier).state++;
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

Bloc/Cubit

Bloc 是基于事件驱动的状态管理方案,适合复杂业务逻辑。

dart
import 'package:flutter_bloc/flutter_bloc.dart';

// Cubit - 简化版 Bloc
class CounterCubit extends Cubit<int> {
  CounterCubit() : super(0); // 初始状态

  void increment() => emit(state + 1); // 发射新状态
  void decrement() => emit(state - 1);
  void reset() => emit(0);
}

// Bloc - 事件驱动
abstract class CounterEvent {}
class IncrementPressed extends CounterEvent {}
class DecrementPressed extends CounterEvent {}
class ResetPressed extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<IncrementPressed>((event, emit) => emit(state + 1));
    on<DecrementPressed>((event, emit) => emit(state - 1));
    on<ResetPressed>((event, emit) => emit(0));
  }
}

// 使用 Cubit
class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => CounterCubit(),
      child: Scaffold(
        body: BlocBuilder<CounterCubit, int>(
          builder: (context, count) {
            return Center(
              child: Text('$count', style: const TextStyle(fontSize: 72)),
            );
          },
        ),
        floatingActionButton: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton(
              heroTag: 'inc',
              onPressed: () => context.read<CounterCubit>().increment(),
              child: const Icon(Icons.add),
            ),
            const SizedBox(height: 8),
            FloatingActionButton(
              heroTag: 'dec',
              onPressed: () => context.read<CounterCubit>().decrement(),
              child: const Icon(Icons.remove),
            ),
          ],
        ),
      ),
    );
  }
}

// 异步 Bloc 示例 - 登录
sealed class LoginState {}
class LoginInitial extends LoginState {}
class LoginLoading extends LoginState {}
class LoginSuccess extends LoginState {
  final User user;
  LoginSuccess(this.user);
}
class LoginFailure extends LoginState {
  final String error;
  LoginFailure(this.error);
}

abstract class LoginEvent {}
class LoginSubmitted extends LoginEvent {
  final String email;
  final String password;
  LoginSubmitted({required this.email, required this.password});
}

class LoginBloc extends Bloc<LoginEvent, LoginState> {
  final AuthService _authService;

  LoginBloc(this._authService) : super(LoginInitial()) {
    on<LoginSubmitted>(_onLoginSubmitted);
  }

  Future<void> _onLoginSubmitted(LoginSubmitted event, Emitter<LoginState> emit) async {
    emit(LoginLoading());
    try {
      final user = await _authService.login(event.email, event.password);
      emit(LoginSuccess(user));
    } catch (e) {
      emit(LoginFailure(e.toString()));
    }
  }
}

三、高级篇 - 深入与优化

3.1 自定义绘制

CustomPaint/CustomPainter

dart
class CircularProgressPainter extends CustomPainter {
  final double progress; // 0.0 ~ 1.0
  final Color color;
  final double strokeWidth;

  CircularProgressPainter({
    required this.progress,
    this.color = Colors.blue,
    this.strokeWidth = 8,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final radius = (size.width - strokeWidth) / 2;

    // 背景圆
    final bgPaint = Paint()
      ..color = Colors.grey.shade200
      ..style = PaintingStyle.stroke
      ..strokeWidth = strokeWidth
      ..strokeCap = StrokeCap.round;
    canvas.drawCircle(center, radius, bgPaint);

    // 进度弧
    final progressPaint = Paint()
      ..color = color
      ..style = PaintingStyle.stroke
      ..strokeWidth = strokeWidth
      ..strokeCap = StrokeCap.round;

    final sweepAngle = 2 * 3.14159 * progress;
    canvas.drawArc(
      Rect.fromCircle(center: center, radius: radius),
      -3.14159 / 2, // 从顶部开始
      sweepAngle,
      false,
      progressPaint,
    );

    // 中心文字
    final textPainter = TextPainter(
      text: TextSpan(
        text: '${(progress * 100).toInt()}%',
        style: TextStyle(
          color: color,
          fontSize: radius * 0.5,
          fontWeight: FontWeight.bold,
        ),
      ),
      textDirection: TextDirection.ltr,
    );
    textPainter.layout();
    textPainter.paint(
      canvas,
      Offset(center.dx - textPainter.width / 2, center.dy - textPainter.height / 2),
    );
  }

  @override
  bool shouldRepaint(covariant CircularProgressPainter oldDelegate) {
    return progress != oldDelegate.progress || color != oldDelegate.color;
  }
}

// 使用
class CircularProgressWidget extends StatelessWidget {
  final double progress;
  const CircularProgressWidget({super.key, required this.progress});

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: const Size(150, 150),
      painter: CircularProgressPainter(progress: progress),
    );
  }
}

Canvas 绘制

dart
class ChartPainter extends CustomPainter {
  final List<double> data;
  final Color lineColor;
  final Color fillColor;

  ChartPainter({
    required this.data,
    this.lineColor = Colors.blue,
    this.fillColor = Colors.blue,
  });

  @override
  void paint(Canvas canvas, Size size) {
    if (data.isEmpty) return;

    final maxVal = data.reduce((a, b) => a > b ? a : b);
    final stepX = size.width / (data.length - 1);
    final padding = 20.0;

    // 绘制网格线
    final gridPaint = Paint()
      ..color = Colors.grey.shade300
      ..strokeWidth = 0.5;
    for (int i = 0; i <= 4; i++) {
      final y = padding + (size.height - 2 * padding) * i / 4;
      canvas.drawLine(Offset(0, y), Offset(size.width, y), gridPaint);
    }

    // 生成路径点
    final points = <Offset>[];
    for (int i = 0; i < data.length; i++) {
      final x = i * stepX;
      final y = size.height - padding - (data[i] / maxVal) * (size.height - 2 * padding);
      points.add(Offset(x, y));
    }

    // 绘制填充区域
    final fillPath = Path()
      ..moveTo(points.first.dx, size.height - padding)
      ..lineTo(points.first.dx, points.first.dy);
    for (int i = 1; i < points.length; i++) {
      fillPath.lineTo(points[i].dx, points[i].dy);
    }
    fillPath.lineTo(points.last.dx, size.height - padding);
    fillPath.close();

    canvas.drawPath(
      fillPath,
      Paint()..color = fillColor.withOpacity(0.2),
    );

    // 绘制折线
    final linePath = Path()..moveTo(points.first.dx, points.first.dy);
    for (int i = 1; i < points.length; i++) {
      linePath.lineTo(points[i].dx, points[i].dy);
    }
    canvas.drawPath(
      linePath,
      Paint()
        ..color = lineColor
        ..style = PaintingStyle.stroke
        ..strokeWidth = 2
        ..strokeCap = StrokeCap.round,
    );

    // 绘制数据点
    for (final point in points) {
      canvas.drawCircle(point, 4, Paint()..color = lineColor);
      canvas.drawCircle(point, 2, Paint()..color = Colors.white);
    }
  }

  @override
  bool shouldRepaint(covariant ChartPainter oldDelegate) {
    return data != oldDelegate.data;
  }
}

RenderObject

RenderObject 是 Flutter 渲染管线中的核心对象,负责布局和绘制。

dart
// 自定义 RenderObject 实现一个简单的居中布局
class CenterRenderBox extends RenderBox
    with RenderObjectWithChildMixin<RenderBox> {
  @override
  void performLayout() {
    if (child != null) {
      child!.layout(const BoxConstraints(), parentUsesSize: true);
      final childParentData = child!.parentData as BoxParentData;
      childParentData.offset = Offset(
        (size.width - child!.size.width) / 2,
        (size.height - child!.size.height) / 2,
      );
    }
    size = constraints.biggest;
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      final childParentData = child!.parentData as BoxParentData;
      context.paintChild(child!, offset + childParentData.offset);
    }
  }
}

// 组合自定义 Widget
class CustomCenter extends SingleChildRenderObjectWidget {
  const CustomCenter({super.key, super.child});

  @override
  RenderObject createRenderObject(BuildContext context) {
    return CenterRenderBox();
  }
}

3.2 性能优化

const 构造函数

dart
// 使用 const 构造函数,Flutter 会复用 Widget 实例,减少重建开销
// 好 - const 复用
const Text('固定文本')
const SizedBox(height: 16)
const Icon(Icons.home)

// 差 - 每次创建新实例
Text('固定文本')
SizedBox(height: 16)

// const 组件列表
const items = [
  Text('项目1'),
  Text('项目2'),
  Text('项目3'),
];

// 在 build 方法中尽量使用 const
@override
Widget build(BuildContext context) {
  return const Column(
    children: [
      Text('标题'),
      SizedBox(height: 8),
      Text('内容'),
    ],
  );
}

RepaintBoundary

将频繁变化的区域隔离,避免整个页面重绘。

dart
class AnimationPage extends StatelessWidget {
  const AnimationPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          // 静态内容 - 不需要重绘
          const HeaderWidget(),

          // 频繁动画区域 - 用 RepaintBoundary 隔离
          RepaintBoundary(
            child: SpinningWidget(), // 只有这个区域重绘
          ),

          // 另一个静态区域
          const FooterWidget(),
        ],
      ),
    );
  }
}

ListView 优化

dart
// 1. 使用 itemExtent 固定项高度
ListView.builder(
  itemExtent: 72, // 固定高度,避免计算每项大小
  itemCount: 1000,
  itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
)

// 2. 使用 ListView 代替 Column + SingleChildScrollView
// 好 - 按需构建
ListView.builder(itemBuilder: ...)

// 差 - 一次性构建所有子项
SingleChildScrollView(
  child: Column(
    children: List.generate(1000, (i) => ListTile(title: Text('Item $i'))),
  ),
)

// 3. 避免在 itemBuilder 中创建 Widget 方法
// 差 - 每次调用都创建新函数
ListView.builder(
  itemBuilder: (context, index) => _buildItem(index),
)

// 好 - 内联构建或使用 const
ListView.builder(
  itemBuilder: (context, index) {
    return const ListTile(title: Text('Item'));
  },
)

// 4. 使用 cacheExtent 预缓存
ListView.builder(
  cacheExtent: 500, // 预缓存 500 像素
  itemCount: 1000,
  itemBuilder: (context, index) => ListItem(index: index),
)

图片优化

dart
// 1. 指定图片尺寸,避免解码过大图片
Image.network(
  'https://example.com/photo.jpg',
  width: 200,
  height: 200,
  fit: BoxFit.cover,
  cacheWidth: 400,  // 缓存尺寸(像素),减少内存占用
  cacheHeight: 400,
)

// 2. 使用 cached_network_image 缓存
CachedNetworkImage(
  imageUrl: 'https://example.com/photo.jpg',
  memCacheWidth: 400, // 内存缓存尺寸
  maxWidth: 800,      // 磁盘缓存最大宽度
)

// 3. 使用 precacheImage 预加载
class MyPage extends StatefulWidget {
  const MyPage({super.key});

  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    precacheImage(NetworkImage('https://example.com/hero.jpg'), context);
  }

  @override
  Widget build(BuildContext context) {
    return const Image(image: NetworkImage('https://example.com/hero.jpg'));
  }
}

性能分析工具(DevTools)

bash
# 启动 DevTools
flutter pub global activate devtools
flutter pub global run devtools

# 或者在运行应用时直接使用
flutter run --profile
# 然后在浏览器打开 Observatory 链接

在 Profile 模式下分析性能,Debug 模式的性能数据不准确。使用 DevTools 的 Performance 面板查看帧率、Widget 重建次数、CPU 时间等。


3.3 平台通道

MethodChannel

用于 Flutter 与原生平台之间的方法调用(双向通信)。

dart
// Flutter 端
class BatteryService {
  static const _channel = MethodChannel('com.example.app/battery');

  // 调用原生方法
  Future<int> getBatteryLevel() async {
    try {
      final level = await _channel.invokeMethod<int>('getBatteryLevel');
      return level!;
    } on PlatformException catch (e) {
      print('获取电量失败: ${e.message}');
      return -1;
    }
  }

  // 调用带参数的原生方法
  Future<void> showToast(String message) async {
    await _channel.invokeMethod('showToast', {'message': message});
  }
}
kotlin
// Android 端 (MainActivity.kt)
class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/battery")
            .setMethodCallHandler { call, result ->
                when (call.method) {
                    "getBatteryLevel" -> {
                        val level = getBatteryLevel()
                        if (level != -1) {
                            result.success(level)
                        } else {
                            result.error("UNAVAILABLE", "无法获取电量", null)
                        }
                    }
                    "showToast" -> {
                        val message = call.argument<String>("message") ?: ""
                        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
                        result.success(null)
                    }
                    else -> result.notImplemented()
                }
            }
    }

    private fun getBatteryLevel(): Int {
        val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
        return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
    }
}
swift
// iOS 端 (AppDelegate.swift)
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let controller = window?.rootViewController as! FlutterViewController

        let channel = FlutterMethodChannel(
            name: "com.example.app/battery",
            binaryMessenger: controller.binaryMessenger
        )

        channel.setMethodCallHandler { (call, result) in
            switch call.method {
            case "getBatteryLevel":
                let level = self.getBatteryLevel()
                if level != -1 {
                    result(level)
                } else {
                    result(FlutterError(code: "UNAVAILABLE", message: "无法获取电量", details: nil))
                }
            case "showToast":
                if let args = call.arguments as? [String: Any],
                   let message = args["message"] as? String {
                    let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
                    alert.addAction(UIAlertAction(title: "确定", style: .default))
                    controller.present(alert, animated: true)
                    result(nil)
                }
            default:
                result(FlutterMethodNotImplemented)
            }
        }

        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    private func getBatteryLevel() -> Int {
        UIDevice.current.isBatteryMonitoringEnabled = true
        let level = Int(UIDevice.current.batteryLevel * 100)
        return level >= 0 ? level : -1
    }
}

EventChannel

用于原生平台向 Flutter 持续发送事件流。

dart
// Flutter 端 - 监听原生事件流
class LocationService {
  static const _channel = EventChannel('com.example.app/location');

  Stream<LocationData>? _locationStream;

  Stream<LocationData> get locationStream {
    _locationStream ??= _channel.receiveBroadcastStream().map((event) {
      final data = event as Map;
      return LocationData(
        latitude: data['latitude'] as double,
        longitude: data['longitude'] as double,
      );
    });
    return _locationStream!;
  }
}

// 使用
StreamSubscription? _subscription;

void startListening() {
  _subscription = LocationService().locationStream.listen(
    (location) => print('位置: ${location.latitude}, ${location.longitude}'),
    onError: (error) => print('错误: $error'),
  );
}

void stopListening() {
  _subscription?.cancel();
}

BasicMessageChannel

用于双向消息通信,适合频繁的数据交换。

dart
// Flutter 端
class NativeMessenger {
  static const _channel = BasicMessageChannel<String>(
    'com.example.app/messenger',
    StringCodec(),
  );

  // 发送消息并接收回复
  Future<String> sendMessage(String message) async {
    final reply = await _channel.send(message);
    return reply ?? '';
  }

  // 接收原生消息
  void setupHandler() {
    _channel.setMessageHandler((message) async {
      print('收到原生消息: $message');
      return 'Flutter 已收到: $message';
    });
  }
}

3.4 测试体系

单元测试

测试独立的函数、方法和类。

dart
// counter.dart
class Counter {
  int value = 0;
  void increment() => value++;
  void decrement() => value--;
}

// counter_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'counter.dart';

void main() {
  group('Counter', () {
    test('初始值应该为 0', () {
      expect(Counter().value, 0);
    });

    test('increment 应该增加 1', () {
      final counter = Counter();
      counter.increment();
      expect(counter.value, 1);
    });

    test('decrement 应该减少 1', () {
      final counter = Counter();
      counter.decrement();
      expect(counter.value, -1);
    });

    test('多次操作', () {
      final counter = Counter();
      counter.increment();
      counter.increment();
      counter.decrement();
      expect(counter.value, 1);
    });
  });
}

Widget 测试

测试 Widget 的渲染和交互。

dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/counter_page.dart';

void main() {
  testWidgets('计数器页面测试', (WidgetTester tester) async {
    // 1. 构建 Widget
    await tester.pumpWidget(const MaterialApp(home: CounterPage()));

    // 2. 验证初始状态
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    // 3. 模拟点击增加按钮
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump(); // 触发重建

    // 4. 验证状态变化
    expect(find.text('1'), findsOneWidget);

    // 5. 再次点击
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();
    expect(find.text('2'), findsOneWidget);

    // 6. 点击减少按钮
    await tester.tap(find.byIcon(Icons.remove));
    await tester.pump();
    expect(find.text('1'), findsOneWidget);
  });

  testWidgets('列表滚动测试', (WidgetTester tester) async {
    await tester.pumpWidget(const MaterialApp(home: LongListPage()));

    // 验证第一项可见
    expect(find.text('Item 1'), findsOneWidget);

    // 滚动到底部
    await tester.fling(find.byType(ListView), const Offset(0, -500), 1000);
    await tester.pumpAndSettle();

    // 验证最后一项可见
    expect(find.text('Item 1'), findsNothing);
  });
}

集成测试

测试完整的应用流程,运行在真实设备或模拟器上。

dart
// integration_test/app_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group('端到端测试', () {
    testWidgets('完整登录流程', (tester) async {
      // 启动应用
      app.main();
      await tester.pumpAndSettle();

      // 输入邮箱
      await tester.enterText(find.byType(TextFormField).first, 'test@example.com');

      // 输入密码
      await tester.enterText(find.byType(TextFormField).last, 'password123');

      // 点击登录按钮
      await tester.tap(find.widgetWithText(ElevatedButton, '登录'));
      await tester.pumpAndSettle();

      // 验证跳转到首页
      expect(find.text('欢迎'), findsOneWidget);
    });
  });
}

Golden 测试

通过截图对比验证 UI 是否符合预期。

dart
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('按钮 Golden 测试', (tester) async {
    await tester.pumpWidget(
      const MaterialApp(
        home: Scaffold(
          body: ElevatedButton(
            onPressed: null,
            child: Text('提交'),
          ),
        ),
      ),
    );

    // 生成并对比截图
    await expectLater(
      find.byType(ElevatedButton),
      matchesGoldenFile('goldens/button.png'),
    );
  });
}
bash
# 更新 Golden 文件
flutter test --update-goldens

3.5 打包发布

Android 签名与打包

bash
# 1. 生成签名密钥
keytool -genkey -v -keystore release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias release

# 2. 创建 key.properties 文件
# android/key.properties
properties
# android/key.properties
storePassword=your_store_password
keyPassword=your_key_password
keyAlias=release
storeFile=/path/to/release-key.jks
gradle
// android/app/build.gradle - 配置签名
android {
    signingConfigs {
        release {
            def keystoreProperties = new Properties()
            def keystorePropertiesFile = rootProject.file('key.properties')
            if (keystorePropertiesFile.exists()) {
                keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
            }
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
bash
# 3. 打包 APK
flutter build apk --release

# 4. 打包 AAB(推荐上传 Google Play)
flutter build appbundle --release

# 5. 分渠道打包
flutter build apk --dart-define=CHANNEL=huawei
flutter build apk --dart-define=CHANNEL=xiaomi

iOS 打包与上架

bash
# 1. 确保 Xcode 签名配置正确
open ios/Runner.xcworkspace

# 2. 构建 iOS 发布版本
flutter build ios --release

# 3. 使用 Xcode 归档
# Xcode -> Product -> Archive

# 4. 上传到 App Store Connect
# Xcode Organizer -> Distribute App -> App Store Connect

Web 打包

bash
# 构建 Web 版本
flutter build web --release

# 自定义基础路径
flutter build web --base-href "/my-app/"

# 使用 CanvasKit 渲染器(默认)
flutter build web --web-renderer canvaskit

# 使用 HTML 渲染器
flutter build web --web-renderer html

# 自动选择
flutter build web --web-renderer auto

3.6 主题与国际化

ThemeData 配置

dart
class AppTheme {
  // 亮色主题
  static ThemeData lightTheme = ThemeData(
    brightness: Brightness.light,
    primaryColor: Colors.blue,
    colorScheme: ColorScheme.light(
      primary: Colors.blue,
      secondary: Colors.orange,
      surface: Colors.white,
      error: Colors.red,
    ),
    scaffoldBackgroundColor: Colors.grey[50],
    appBarTheme: const AppBarTheme(
      backgroundColor: Colors.blue,
      foregroundColor: Colors.white,
      elevation: 0,
      centerTitle: true,
    ),
    cardTheme: CardTheme(
      elevation: 2,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
    ),
    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
      ),
    ),
    inputDecorationTheme: InputDecorationTheme(
      border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
      filled: true,
      fillColor: Colors.grey[100],
    ),
    textTheme: const TextTheme(
      headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
      headlineMedium: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
      bodyLarge: TextStyle(fontSize: 16),
      bodyMedium: TextStyle(fontSize: 14),
    ),
  );

  // 暗色主题
  static ThemeData darkTheme = ThemeData(
    brightness: Brightness.dark,
    primaryColor: Colors.blue[700],
    colorScheme: ColorScheme.dark(
      primary: Colors.blue[700]!,
      secondary: Colors.orange[700]!,
      surface: Colors.grey[900]!,
      error: Colors.red[700]!,
    ),
    scaffoldBackgroundColor: Colors.grey[900],
    appBarTheme: AppBarTheme(
      backgroundColor: Colors.grey[850],
      foregroundColor: Colors.white,
      elevation: 0,
    ),
  );
}

// 使用
MaterialApp(
  theme: AppTheme.lightTheme,
  darkTheme: AppTheme.darkTheme,
  themeMode: ThemeMode.system, // 跟随系统
  home: const HomePage(),
)

暗黑模式

dart
class ThemeNotifier extends ChangeNotifier {
  ThemeMode _mode = ThemeMode.system;
  ThemeMode get mode => _mode;

  void toggle() {
    _mode = _mode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
    notifyListeners();
  }

  void setMode(ThemeMode mode) {
    _mode = mode;
    notifyListeners();
  }
}

// 在 Widget 中切换
SwitchListTile(
  title: const Text('暗黑模式'),
  value: Theme.of(context).brightness == Brightness.dark,
  onChanged: (value) {
    context.read<ThemeNotifier>().setMode(
      value ? ThemeMode.dark : ThemeMode.light,
    );
  },
)

国际化(flutter_localizations)

yaml
# pubspec.yaml
dependencies:
  flutter_localizations:
    sdk: flutter
  intl: ^0.18.0
dart
// 1. 配置 MaterialApp
MaterialApp(
  localizationsDelegates: const [
    AppLocalizations.delegate,
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: const [
    Locale('zh', 'CN'),
    Locale('en', 'US'),
    Locale('ja', 'JP'),
  ],
  locale: const Locale('zh', 'CN'),
  home: const HomePage(),
)

// 2. 使用 flutter_intl 插件或手动创建 ARB 文件
// l10n/app_zh.arb
{
  "@@locale": "zh_CN",
  "appTitle": "我的应用",
  "greeting": "你好, {name}!",
  "@greeting": {
    "placeholders": {
      "name": {"type": "String"}
    }
  }
}

// l10n/app_en.arb
{
  "@@locale": "en_US",
  "appTitle": "My App",
  "greeting": "Hello, {name}!"
}

// 3. 在代码中使用
Text(AppLocalizations.of(context)!.appTitle)
Text(AppLocalizations.of(context)!.greeting('张三'))

// 4. 动态切换语言
class LocaleNotifier extends ChangeNotifier {
  Locale _locale = const Locale('zh', 'CN');
  Locale get locale => _locale;

  void setLocale(Locale locale) {
    _locale = locale;
    notifyListeners();
  }
}

四、资深篇 - 架构与原理

4.1 架构设计模式

Clean Architecture

Clean Architecture 将应用分为三层:表现层(Presentation)、领域层(Domain)、数据层(Data),依赖关系从外向内。

lib/
├── core/                  # 核心公共
│   ├── errors/
│   ├── themes/
│   └── utils/
├── features/              # 按功能模块组织
│   └── auth/
│       ├── data/          # 数据层
│       │   ├── datasources/   # 数据源(远程/本地)
│       │   ├── models/        # 数据模型(DTO)
│       │   └── repositories/  # 仓库实现
│       ├── domain/        # 领域层
│       │   ├── entities/      # 实体
│       │   ├── repositories/  # 仓库接口
│       │   └── usecases/      # 用例
│       └── presentation/  # 表现层
│           ├── bloc/          # 状态管理
│           ├── pages/         # 页面
│           └── widgets/       # 组件
└── main.dart
dart
// 领域层 - 实体
class User {
  final String id;
  final String name;
  final String email;
  User({required this.id, required this.name, required this.email});
}

// 领域层 - 仓库接口(抽象类)
abstract class AuthRepository {
  Future<User> login(String email, String password);
  Future<User> register(String name, String email, String password);
  Future<void> logout();
  Future<User?> getCurrentUser();
}

// 领域层 - 用例
class LoginUseCase {
  final AuthRepository repository;
  LoginUseCase(this.repository);

  Future<User> call(String email, String password) {
    if (email.isEmpty) throw ArgumentError('邮箱不能为空');
    if (password.length < 6) throw ArgumentError('密码至少6位');
    return repository.login(email, password);
  }
}

// 数据层 - 数据模型
class UserModel extends User {
  final String token;
  UserModel({
    required super.id,
    required super.name,
    required super.email,
    required this.token,
  });

  factory UserModel.fromJson(Map<String, dynamic> json) {
    return UserModel(
      id: json['id'],
      name: json['name'],
      email: json['email'],
      token: json['token'],
    );
  }
}

// 数据层 - 仓库实现
class AuthRepositoryImpl implements AuthRepository {
  final AuthRemoteDataSource remoteDataSource;
  final AuthLocalDataSource localDataSource;

  AuthRepositoryImpl({
    required this.remoteDataSource,
    required this.localDataSource,
  });

  @override
  Future<User> login(String email, String password) async {
    final userModel = await remoteDataSource.login(email, password);
    await localDataSource.saveToken(userModel.token);
    return userModel;
  }

  @override
  Future<void> logout() async {
    await localDataSource.clearToken();
  }

  @override
  Future<User?> getCurrentUser() async {
    final token = await localDataSource.getToken();
    if (token == null) return null;
    return remoteDataSource.getCurrentUser(token);
  }

  @override
  Future<User> register(String name, String email, String password) async {
    final userModel = await remoteDataSource.register(name, email, password);
    await localDataSource.saveToken(userModel.token);
    return userModel;
  }
}

MVVM 模式

dart
// Model - 数据模型
class Article {
  final int id;
  final String title;
  final String content;
  final String author;
  Article({required this.id, required this.title, required this.content, required this.author});
}

// ViewModel - 业务逻辑与状态
class ArticleViewModel extends ChangeNotifier {
  final ArticleRepository _repository;

  List<Article> _articles = [];
  List<Article> get articles => _articles;

  bool _isLoading = false;
  bool get isLoading => _isLoading;

  String? _error;
  String? get error => _error;

  ArticleViewModel(this._repository);

  Future<void> loadArticles() async {
    _isLoading = true;
    _error = null;
    notifyListeners();

    try {
      _articles = await _repository.getArticles();
      _isLoading = false;
      notifyListeners();
    } catch (e) {
      _error = e.toString();
      _isLoading = false;
      notifyListeners();
    }
  }

  Future<void> refresh() async {
    await loadArticles();
  }
}

// View - UI
class ArticleListPage extends StatelessWidget {
  const ArticleListPage({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => ArticleViewModel(context.read<ArticleRepository>())..loadArticles(),
      child: Scaffold(
        appBar: AppBar(title: const Text('文章列表')),
        body: Consumer<ArticleViewModel>(
          builder: (context, vm, _) {
            if (vm.isLoading) return const Center(child: CircularProgressIndicator());
            if (vm.error != null) return Center(child: Text('错误: ${vm.error}'));
            if (vm.articles.isEmpty) return const Center(child: Text('暂无文章'));

            return RefreshIndicator(
              onRefresh: vm.refresh,
              child: ListView.builder(
                itemCount: vm.articles.length,
                itemBuilder: (context, index) {
                  final article = vm.articles[index];
                  return ListTile(
                    title: Text(article.title),
                    subtitle: Text(article.author),
                  );
                },
              ),
            );
          },
        ),
      ),
    );
  }
}

MVI 模式

MVI(Model-View-Intent)强调单向数据流,状态不可变。

dart
// State - 不可变状态
@immutable
class LoginState {
  final String email;
  final String password;
  final bool isLoading;
  final String? error;
  final bool isSuccess;

  const LoginState({
    this.email = '',
    this.password = '',
    this.isLoading = false,
    this.error,
    this.isSuccess = false,
  });

  LoginState copyWith({
    String? email,
    String? password,
    bool? isLoading,
    String? error,
    bool? isSuccess,
  }) {
    return LoginState(
      email: email ?? this.email,
      password: password ?? this.password,
      isLoading: isLoading ?? this.isLoading,
      error: error,
      isSuccess: isSuccess ?? this.isSuccess,
    );
  }
}

// Intent - 用户意图
sealed class LoginIntent {}
class EmailChanged extends LoginIntent { final String email; EmailChanged(this.email); }
class PasswordChanged extends LoginIntent { final String password; PasswordChanged(this.password); }
class LoginSubmitted extends LoginIntent {}

// ViewModel - 处理意图,产生新状态
class LoginViewModel extends StateNotifier<LoginState> {
  final AuthRepository _repository;

  LoginViewModel(this._repository) : super(const LoginState());

  void handle(LoginIntent intent) {
    switch (intent) {
      case EmailChanged(:final email):
        state = state.copyWith(email: email, error: null);
      case PasswordChanged(:final password):
        state = state.copyWith(password: password, error: null);
      case LoginSubmitted():
        _login();
    }
  }

  Future<void> _login() async {
    state = state.copyWith(isLoading: true, error: null);
    try {
      await _repository.login(state.email, state.password);
      state = state.copyWith(isLoading: false, isSuccess: true);
    } catch (e) {
      state = state.copyWith(isLoading: false, error: e.toString());
    }
  }
}

4.2 Flutter 引擎原理

架构三层(Framework/Engine/Embedder)

Flutter 的架构分为三层,每层职责明确:

┌─────────────────────────────────────────┐
│           Framework (Dart)              │  ← 开发者直接使用
│  ┌─────────┐ ┌──────────┐ ┌──────────┐ │
│  │ Material │ │ Cupertino│ │ Widgets  │ │
│  └─────────┘ └──────────┘ └──────────┘ │
│  ┌─────────────────────────────────────┐│
│  │        Rendering (渲染层)           ││
│  │  RenderObject / Element / Widget    ││
│  └─────────────────────────────────────┘│
│  ┌─────────────────────────────────────┐│
│  │     Foundation (基础层)              ││
│  │  animations / gestures / foundation ││
│  └─────────────────────────────────────┘│
├─────────────────────────────────────────┤
│           Engine (C/C++)                │  ← 核心引擎
│  ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│  │  Skia /   │ │  Dart VM │ │  Text   │ │
│  │ Impeller  │ │          │ │  ICU    │ │
│  └──────────┘ └──────────┘ └─────────┘ │
├─────────────────────────────────────────┤
│           Embedder (平台特定)            │  ← 平台适配
│  ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│  │ Android  │ │   iOS    │ │  Web /  │ │
│  │          │ │          │ │ Desktop │ │
│  └──────────┘ └──────────┘ └─────────┘ │
└─────────────────────────────────────────┘
  • Framework 层:使用 Dart 编写,提供 Widget、渲染、动画、手势等 API,是开发者直接接触的层
  • Engine 层:使用 C/C++ 编写,包含 Dart VM、Skia/Impeller 渲染引擎、文本排版引擎等
  • Embedder 层:平台特定代码,负责创建渲染表面、线程管理、事件循环等

Dart VM

Dart VM 支持两种编译模式:

  • JIT(Just-In-Time):开发时使用,支持热重载。Dart 代码在运行时编译为机器码
  • AOT(Ahead-Of-Time):发布时使用,代码在编译期生成高效机器码,启动快、运行流畅
bash
# JIT 模式(开发)
flutter run  # 支持 hot reload

# AOT 模式(发布)
flutter build apk --release  # 编译为机器码

Skia/Impeller 渲染

  • Skia:2D 图形渲染引擎,Google 开源,Flutter 一直使用。在复杂场景下可能出现着色器编译卡顿(Shader Compilation Jank)
  • Impeller:Flutter 3.7+ 引入的新渲染引擎,预编译着色器,解决 Skia 的着色器编译卡顿问题
bash
# 启用 Impeller(Android)
flutter run --enable-impeller

# Android Manifest 配置
<meta-data
    android:name="io.flutter.embedding.android.EnableImpeller"
    android:value="true" />

# iOS 已默认启用 Impeller

4.3 渲染管线

Widget -> Element -> RenderObject

Flutter 渲染管线的核心是三棵树:

Widget 树 (不可变配置)          Element 树 (管理生命周期)       RenderObject 树 (布局与绘制)
┌──────────┐                  ┌──────────┐                  ┌──────────┐
│ Container │ ──createElement→ │ Element  │ ──createRender→ │RenderBox │
│  (配置)   │                  │ (实例)   │                  │(布局绘制)│
└──────────┘                  └──────────┘                  └──────────┘
     ↓                              ↓                              ↓
  描述 UI 配置              管理 Widget 与 RenderObject      实际测量布局和绘制
  不可变,频繁重建          可复用,diff 算法优化            只在需要时重建
dart
// 三棵树的关系
// Widget 描述配置
class MyWidget extends StatelessWidget {
  final Color color;
  const MyWidget({required this.color});

  @override
  Widget build(BuildContext context) {
    return Container(color: color);
  }
}

// 当 color 变化时:
// 1. 创建新的 Widget 实例(不可变)
// 2. Element 对比新旧 Widget(canUpdate 方法)
// 3. 如果 canUpdate 返回 true(类型和 key 相同),Element 更新关联的 RenderObject
// 4. RenderObject 标记需要布局/绘制

// Widget.canUpdate 决定是否复用 Element
static bool canUpdate(Widget oldWidget, Widget newWidget) {
  return oldWidget.runtimeType == newWidget.runtimeType
      && oldWidget.key == newWidget.key;
}

布局约束传递

Flutter 的布局采用"约束向下传递,尺寸向上返回"的机制:

父组件传递约束 (Constraints)

子组件根据约束决定自身大小

子组件返回尺寸 (Size) 和位置 (Offset)

父组件根据返回的尺寸和位置放置子组件
dart
// 约束类型
// BoxConstraints
BoxConstraints(
  minWidth: 0,
  maxWidth: 400,
  minHeight: 0,
  maxHeight: 600,
)

// 常见约束
BoxConstraints.tight(Size(100, 100))          // 固定尺寸
BoxConstraints.tightFor(width: 100)           // 固定宽度
BoxConstraints.tightForFinite()               // 无限约束
const BoxConstraints()                        // 无约束(0 到 无穷大)

// 理解约束传递
// Container 的行为取决于父组件给的约束
Container(
  width: 100,  // 如果父组件约束允许,宽度为100
  height: 100, // 如果父组件约束允许,高度为100
  color: Colors.red,
)
// 在 Expanded 中,Container 会被拉伸
// 在 Center 中,Container 保持自身大小并居中

绘制流程

1. 标记阶段 (Mark)
   - setState -> markNeedsBuild
   - 属性变化 -> markNeedsLayout -> markNeedsPaint

2. 布局阶段 (Layout)
   - 父组件传递约束
   - 子组件计算大小
   - 确定子组件位置

3. 绘制阶段 (Paint)
   - 遍历 RenderObject 树
   - 生成 Layer 树
   - 提交给引擎渲染

4. 合成阶段 (Compositing)
   - Layer 树合成为最终画面
   - 发送到 GPU 显示

RepaintBoundary 原理

RepaintBoundary 创建一个独立的 Layer,使其子树的重绘不会影响父树。

dart
// 没有 RepaintBoundary
// 动画变化时,整个 RenderObject 树都需要重绘
Scaffold(
  body: Column(
    children: [
      StaticHeader(),      // 不需要重绘,但被迫重绘
      AnimatedSpinner(),   // 动画触发重绘
      StaticFooter(),      // 不需要重绘,但被迫重绘
    ],
  ),
)

// 有 RepaintBoundary
// 只有 AnimatedSpinner 的 Layer 重绘
Scaffold(
  body: Column(
    children: [
      StaticHeader(),
      RepaintBoundary(     // 创建独立 Layer
        child: AnimatedSpinner(), // 只有这里重绘
      ),
      StaticFooter(),
    ],
  ),
)

4.4 Isolate 与并发

Isolate 创建与通信

Dart 是单线程的,但通过 Isolate 实现真正的并行计算。每个 Isolate 有独立的内存堆,通过消息传递通信。

dart
import 'dart:isolate';

// 创建 Isolate
Future<void> runInIsolate() async {
  final receivePort = ReceivePort();

  // 生成 Isolate
  await Isolate.spawn(
    _isolateEntry,
    receivePort.sendPort,
  );

  // 接收 Isolate 发送的消息
  receivePort.listen((message) {
    print('主 Isolate 收到: $message');
    if (message is SendPort) {
      // 向子 Isolate 发送消息
      message.send('你好,子 Isolate');
    }
  });
}

// Isolate 入口函数
void _isolateEntry(SendPort mainSendPort) {
  final receivePort = ReceivePort();
  // 将自己的 SendPort 发送给主 Isolate
  mainSendPort.send(receivePort.sendPort);

  receivePort.listen((message) {
    print('子 Isolate 收到: $message');
    mainSendPort.send('收到: $message');
  });
}

Compute 函数

Compute 是 Isolate 的简化封装,适合一次性计算任务。

dart
import 'package:flutter/foundation.dart';

// 耗时计算函数(必须是顶层函数或静态方法)
int fibonacci(int n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

// 使用 compute 在后台计算
Future<void> calculateFib() async {
  final result = await compute(fibonacci, 40);
  print('斐波那契(40) = $result');
}

Isolate 池

对于频繁的并发任务,可以使用 Isolate 池避免重复创建销毁的开销。

dart
import 'dart:isolate';
import 'dart:async';

class IsolatePool {
  final int poolSize;
  final List<Isolate> _isolates = [];
  final List<SendPort> _sendPorts = [];
  final List<ReceivePort> _receivePorts = [];
  int _nextIndex = 0;

  IsolatePool(this.poolSize);

  Future<void> init() async {
    for (int i = 0; i < poolSize; i++) {
      final receivePort = ReceivePort();
      final isolate = await Isolate.spawn(
        _poolWorker,
        receivePort.sendPort,
      );
      final sendPort = await receivePort.first as SendPort;

      _isolates.add(isolate);
      _sendPorts.add(sendPort);
      _receivePorts.add(receivePort);
    }
  }

  // 轮询分配任务
  Future<T> execute<T>(Function function, dynamic argument) async {
    final index = _nextIndex % poolSize;
    _nextIndex++;

    final receivePort = ReceivePort();
    _sendPorts[index].send(_Task(function, argument, receivePort.sendPort));
    return await receivePort.first as T;
  }

  void dispose() {
    for (final isolate in _isolates) {
      isolate.kill(priority: Isolate.immediate);
    }
    for (final port in _receivePorts) {
      port.close();
    }
  }
}

class _Task {
  final Function function;
  final dynamic argument;
  final SendPort sendPort;
  _Task(this.function, this.argument, this.sendPort);
}

void _poolWorker(SendPort mainSendPort) {
  final receivePort = ReceivePort();
  mainSendPort.send(receivePort.sendPort);

  receivePort.listen((message) {
    if (message is _Task) {
      final result = Function.apply(message.function, [message.argument]);
      message.sendPort.send(result);
    }
  });
}

网络请求并发控制

dart
import 'dart:async';

class ConcurrencyLimiter {
  final int maxConcurrent;
  int _running = 0;
  final _queue = <_QueuedTask>[];

  ConcurrencyLimiter(this.maxConcurrent);

  Future<T> run<T>(Future<T> Function() task) async {
    if (_running >= maxConcurrent) {
      final completer = Completer<void>();
      _queue.add(_QueuedTask(completer));
      await completer.future;
    }

    _running++;
    try {
      return await task();
    } finally {
      _running--;
      if (_queue.isNotEmpty) {
        _queue.removeFirst().completer.complete();
      }
    }
  }
}

class _QueuedTask {
  final Completer<void> completer;
  _QueuedTask(this.completer);
}

// 使用 - 限制同时3个网络请求
final limiter = ConcurrencyLimiter(3);

Future<List<User>> fetchAllUsers(List<int> ids) async {
  final futures = ids.map((id) => limiter.run(() => fetchUser(id)));
  return Future.wait(futures);
}

4.5 CI/CD 与工程化

GitHub Actions

yaml
# .github/workflows/flutter-ci.yml
name: Flutter CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 安装 Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.x'
          channel: 'stable'

      - name: 安装依赖
        run: flutter pub get

      - name: 分析代码
        run: flutter analyze

      - name: 运行测试
        run: flutter test --coverage

      - name: 上传覆盖率
        uses: codecov/codecov-action@v3
        with:
          files: coverage/lcov.info

  build-android:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 安装 Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.x'

      - name: 构建 APK
        run: flutter build apk --release

      - name: 上传 APK
        uses: actions/upload-artifact@v4
        with:
          name: app-release.apk
          path: build/app/outputs/flutter-apk/app-release.apk

  build-ios:
    needs: test
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      - name: 安装 Flutter
        uses: subosito/flutter-action@v2

      - name: 安装 CocoaPods
        run: cd ios && pod install

      - name: 构建 iOS
        run: flutter build ios --release --no-codesign

Fastlane

ruby
# ios/fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "构建并上传到 TestFlight"
  lane :beta do
    build_app(
      workspace: "Runner.xcworkspace",
      scheme: "Runner",
      export_method: "app-store",
    )
    upload_to_testflight(
      skip_waiting_for_build_processing: true,
    )
  end

  desc "发布到 App Store"
  lane :release do
    build_app(
      workspace: "Runner.xcworkspace",
      scheme: "Runner",
      export_method: "app-store",
    )
    upload_to_app_store(
      force: true,
      submit_for_review: true,
    )
  end
end
ruby
# android/fastlane/Fastfile
default_platform(:android)

platform :android do
  desc "构建并上传到 Google Play 内部测试"
  lane :beta do
    sh("flutter build appbundle --release")
    upload_to_play_store(
      track: "internal",
      aab: "../build/app/outputs/bundle/release/app-release.aab",
    )
  end
end

代码规范与 lint

yaml
# analysis_options.yaml
include: package:flutter_lints/flutter.yaml

linter:
  rules:
    # 错误规则
    avoid_empty_else: true
    avoid_print: true
    avoid_return_types_on_setters: true
    avoid_types_as_parameter_names: true
    empty_statements: true
    no_duplicate_case_values: true
    prefer_is_not_empty: true
    prefer_is_not_operator: true
    unnecessary_new: true

    # 风格规则
    always_declare_return_types: true
    always_put_required_named_parameters_first: true
    annotate_overrides: true
    avoid_init_to_null: true
    avoid_renaming_method_parameters: true
    prefer_const_constructors: true
    prefer_const_declarations: true
    prefer_const_literals_to_create_immutables: true
    prefer_final_fields: true
    prefer_final_locals: true
    prefer_single_quotes: true
    sort_child_properties_last: true
    use_key_in_widget_constructors: true
bash
# 运行 lint 检查
flutter analyze

# 自动格式化
dart format lib/

4.6 混合开发(Add-to-App)

Flutter 模块集成到原生项目

bash
# 1. 创建 Flutter 模块
flutter create -t module --org com.example my_flutter_module

# 2. 编译 AAR(Android)
cd my_flutter_module
flutter build aar
gradle
// 3. 在 Android 项目中集成
// android/app/build.gradle
android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    // 方式1:本地模块依赖
    implementation project(':flutter')

    // 方式2:AAR 依赖
    debugImplementation 'com.example.my_flutter_module:flutter_debug:1.0'
    profileImplementation 'com.example.my_flutter_module:flutter_profile:1.0'
    releaseImplementation 'com.example.my_flutter_module:flutter_release:1.0'
}
ruby
# iOS 项目集成
# 1. 在 Podfile 中添加
pod 'FlutterModule', :path => '../my_flutter_module/.ios/Flutter.podspec'

# 2. 安装
pod install

FlutterEngine

kotlin
// Android 中使用 FlutterEngine
class MainActivity : AppCompatActivity() {
    private lateinit var flutterEngine: FlutterEngine

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 创建 FlutterEngine
        flutterEngine = FlutterEngine(this)
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )

        // 启动 FlutterActivity
        startActivity(
            FlutterActivity.withCachedEngine("my_engine_id").build(this)
        )
    }

    override fun onDestroy() {
        flutterEngine.destroy()
        super.onDestroy()
    }
}
swift
// iOS 中使用 FlutterEngine
class ViewController: UIViewController {
    var flutterEngine: FlutterEngine?

    override func viewDidLoad() {
        super.viewDidLoad()

        // 预热 FlutterEngine
        flutterEngine = FlutterEngine(name: "my_engine")
        flutterEngine?.run()

        // 跳转 Flutter 页面
        let flutterVC = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
        present(flutterVC, animated: true)
    }
}

通信机制

混合开发中 Flutter 与原生之间的通信通过 PlatformChannel 实现,详见 3.3 节。此外还可以使用 Pigeon 代码生成工具:

yaml
# pubspec.yaml
dev_dependencies:
  pigeon: ^17.0.0
dart
// pigeons/messages.dart
import 'package:pigeon/pigeon.dart';

class SearchRequest {
  final String query;
  SearchRequest({required this.query});
}

class SearchReply {
  final String result;
  SearchReply({required this.result});
}

@HostApi()
abstract class Api {
  SearchReply search(SearchRequest request);
}
bash
# 生成平台代码
dart run pigeon --input pigeons/messages.dart

4.7 插件开发

Plugin 架构

Flutter Plugin 由 Dart API 层和平台实现层组成:

my_plugin/
├── lib/
│   ├── my_plugin.dart        # Dart 公共 API
│   └── src/
│       ├── my_plugin_platform.dart  # 平台接口
│       └── method_channel_my_plugin.dart  # MethodChannel 实现
├── android/
│   └── src/main/java/com/example/my_plugin/
│       └── MyPlugin.kt       # Android 实现
├── ios/
│   └── Classes/
│       └── MyPlugin.swift    # iOS 实现
├── example/                  # 示例项目
├── test/                     # 测试
└── pubspec.yaml

平台接口设计

dart
// lib/src/my_plugin_platform.dart
abstract class MyPluginPlatform {
  static MyPluginPlatform get instance => _instance;
  static MyPluginPlatform _instance = MethodChannelMyPlugin();

  static set instance(MyPluginPlatform instance) {
    _instance = instance;
  }

  Future<String?> getPlatformVersion();
  Future<void> initialize(Map<String, dynamic> config);
  Future<bool> isSupported();
}

// lib/src/method_channel_my_plugin.dart
class MethodChannelMyPlugin extends MyPluginPlatform {
  final _channel = const MethodChannel('my_plugin');

  @override
  Future<String?> getPlatformVersion() async {
    return await _channel.invokeMethod<String>('getPlatformVersion');
  }

  @override
  Future<void> initialize(Map<String, dynamic> config) async {
    await _channel.invokeMethod('initialize', config);
  }

  @override
  Future<bool> isSupported() async {
    return await _channel.invokeMethod<bool>('isSupported') ?? false;
  }
}

// lib/my_plugin.dart - 公共 API
class MyPlugin {
  static Future<String?> get platformVersion =>
      MyPluginPlatform.instance.getPlatformVersion();

  static Future<void> initialize(Map<String, dynamic> config) =>
      MyPluginPlatform.instance.initialize(config);

  static Future<bool> get isSupported =>
      MyPluginPlatform.instance.isSupported();
}

FFI(Foreign Function Interface)

使用 dart:ffi 直接调用 C/C++ 库,无需平台通道。

dart
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';

// 定义 C 函数签名
typedef NativeAdd = Int32 Function(Int32 a, Int32 b);
typedef DartAdd = int Function(int a, int b);

class NativeLib {
  late final DynamicLibrary _lib;
  late final DartAdd _add;

  NativeLib() {
    // 加载动态库
    if (Platform.isAndroid) {
      _lib = DynamicLibrary.open('libnative.so');
    } else if (Platform.isIOS) {
      _lib = DynamicLibrary.process();
    } else if (Platform.isWindows) {
      _lib = DynamicLibrary.open('native.dll');
    } else if (Platform.isMacOS) {
      _lib = DynamicLibrary.open('native.dylib');
    } else {
      _lib = DynamicLibrary.open('native.so');
    }

    // 查找函数
    _add = _lib.lookupFunction<NativeAdd, DartAdd>('add');
  }

  int add(int a, int b) => _add(a, b);
}
c
// native.c
#include <stdint.h>

int32_t add(int32_t a, int32_t b) {
    return a + b;
}

发布到 pub.dev

bash
# 1. 检查插件
flutter pub publish --dry-run

# 2. 发布
flutter pub publish
yaml
# pubspec.yaml 关键配置
name: my_plugin
description: 我的 Flutter 插件
version: 1.0.0
homepage: https://github.com/username/my_plugin
repository: https://github.com/username/my_plugin
issue_tracker: https://github.com/username/my_plugin/issues

environment:
  sdk: '>=3.0.0 <4.0.0'
  flutter: '>=3.10.0'

flutter:
  plugin:
    platforms:
      android:
        package: com.example.my_plugin
        pluginClass: MyPlugin
      ios:
        pluginClass: MyPlugin
      web:
        pluginClass: MyPluginWeb
        fileName: my_plugin_web.dart

五、异常问题专题

5.1 环境配置问题

flutter doctor 失败

bash
# 问题:flutter doctor 报错 Android license status unknown
# 解决:接受 Android 许可
flutter doctor --android-licenses

# 问题:Xcode not found
# 解决:安装 Xcode 命令行工具
xcode-select --install

# 问题:CocoaPods not installed
# 解决:安装 CocoaPods
sudo gem install cocoapods
# 或使用 brew
brew install cocoapods

# 问题:Android SDK not found
# 解决:设置 ANDROID_HOME
# macOS/Linux
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools

# Windows
setx ANDROID_HOME "C:\Users\YourName\AppData\Local\Android\Sdk"

依赖下载慢

bash
# 问题:pub.dev 下载慢
# 解决1:使用国内镜像
# macOS/Linux
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

# Windows
setx PUB_HOSTED_URL https://pub.flutter-io.cn
setx FLUTTER_STORAGE_BASE_URL https://storage.flutter-io.cn

# 解决2:使用上海交大镜像
export PUB_HOSTED_URL=https://mirror.sjtu.edu.cn/dart-pub
export FLUTTER_STORAGE_BASE_URL=https://mirror.sjtu.edu.cn/flutter
gradle
// 解决3:Gradle 下载慢 - 修改 android/build.gradle
allprojects {
    repositories {
        // 使用阿里云镜像
        maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/public' }
        google()
        mavenCentral()
    }
}

SDK 版本冲突

bash
# 问题:Dart SDK 版本不兼容
# 解决:指定 SDK 版本约束
# pubspec.yaml
environment:
  sdk: '>=3.0.0 <4.0.0'

# 问题:Flutter SDK 版本与项目不兼容
# 解决:切换 Flutter 版本
flutter downgrade
# 或使用 fvm(Flutter Version Management)
fvm install 3.16.0
fvm use 3.16.0
fvm flutter run

CocoaPods 问题

bash
# 问题:pod install 失败
# 解决1:更新 CocoaPods 仓库
pod repo update

# 解决2:清理缓存重新安装
cd ios
rm -rf Pods Podfile.lock
pod install --repo-update

# 解决3:指定 iOS 最低版本
# ios/Podfile
platform :ios, '13.0'  # 确保版本足够

# 问题:CocoaPods 版本过低
sudo gem install cocoapods -v 1.14.0

5.2 布局渲染异常

RenderFlex overflowed

最常见的 Flutter 布局错误,表示子组件超出了父组件的边界。

dart
// 问题:Row/Column 中子组件溢出
Row(
  children: [
    Text('这是一段很长的文本内容,会导致溢出'),
    Icon(Icons.arrow_forward),
  ],
)

// 解决1:使用 Expanded/Flexible
Row(
  children: [
    Expanded(
      child: Text('这是一段很长的文本内容,不会溢出', overflow: TextOverflow.ellipsis),
    ),
    Icon(Icons.arrow_forward),
  ],
)

// 解决2:使用 Flexible 让子组件自适应
Row(
  children: [
    Flexible(
      child: Text('自适应文本', overflow: TextOverflow.ellipsis),
    ),
    Icon(Icons.arrow_forward),
  ],
)

// 解决3:使用 Wrap 替代 Row 实现自动换行
Wrap(
  spacing: 8,
  children: [
    Chip(label: Text('标签1')),
    Chip(label: Text('标签2')),
    Chip(label: Text('标签3')),
    Chip(label: Text('标签4')),
  ],
)
Unbounded height/width 错误
RenderFlex children have non-zero flex but incoming height constraints are unbounded.
dart
// 问题:在无界约束的父组件中使用 Expanded
ListView(
  children: [
    Expanded(child: Container(color: Colors.red)), // 错误!
  ],
)

// 解决1:给 Expanded 指定固定高度
ListView(
  children: [
    Container(
      height: 200,
      color: Colors.red,
    ),
  ],
)

// 解决2:使用 ShrinkWrap 让 ListView 自适应
Column(
  children: [
    ListView.builder(
      shrinkWrap: true,       // 让 ListView 自适应高度
      physics: NeverScrollableScrollPhysics(), // 禁用内部滚动
      itemCount: 10,
      itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
    ),
  ],
)

// 解决3:使用 Expanded 包裹外层 Column
Column(
  children: [
    Expanded(
      child: ListView.builder(
        itemCount: 10,
        itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
      ),
    ),
  ],
)
Text 文本溢出
dart
// 问题:长文本超出容器边界
Container(
  width: 100,
  child: Text('这是一段非常非常非常长的文本内容'),
)

// 解决1:单行截断
Container(
  width: 100,
  child: Text(
    '这是一段非常非常非常长的文本内容',
    overflow: TextOverflow.ellipsis,  // 末尾显示省略号
    maxLines: 1,
  ),
)

// 解决2:多行截断
Container(
  width: 100,
  child: Text(
    '这是一段非常非常非常长的文本内容',
    overflow: TextOverflow.ellipsis,
    maxLines: 2,
  ),
)

// 解决3:自适应字体大小
Container(
  width: 100,
  child: FittedBox(
    child: Text('这是一段很长的文本'),
  ),
)
键盘弹出导致溢出
dart
// 问题:键盘弹出时底部内容被顶出屏幕,出现溢出黄条

// 解决1:使用 resizeToAvoidBottomInset(Scaffold 默认为 true)
Scaffold(
  resizeToAvoidBottomInset: true,  // 默认值,自动调整布局
  body: Column(
    children: [
      Expanded(child: ListView(...)),
      TextField(),  // 键盘弹出时自动调整
    ],
  ),
)

// 解决2:使用 SingleChildScrollView 包裹
Scaffold(
  body: SingleChildScrollView(
    padding: EdgeInsets.only(
      bottom: MediaQuery.of(context).viewInsets.bottom,  // 键盘高度
    ),
    child: Column(
      children: [
        // ... 表单内容
        TextField(),
      ],
    ),
  ),
)

// 解决3:在 MaterialApp 中设置键盘处理
MaterialApp(
  builder: (context, child) {
    return Scaffold(
      body: GestureDetector(
        onTap: () => FocusScope.of(context).unfocus(),  // 点击空白收起键盘
        child: child,
      ),
    );
  },
)
布局约束错误排查
dart
// 常见约束错误:BoxConstraints forces an infinite width/height

// 问题1:Row/Column 中未给子组件约束
Row(
  children: [
    ListView(),  // 错误!ListView 需要有界约束
  ],
)

// 解决:使用 Expanded 或指定尺寸
Row(
  children: [
    Expanded(
      child: ListView.builder(
        itemCount: 20,
        itemBuilder: (context, index) => Text('Item $index'),
      ),
    ),
  ],
)

// 问题2:Stack 中 Positioned 超出边界
Stack(
  children: [
    Positioned(
      left: -10,  // 超出左边界
      child: Container(width: 50, height: 50),
    ),
  ],
)

// 解决:使用 clipBehavior 裁剪超出部分
Stack(
  clipBehavior: Clip.hardEdge,  // 裁剪超出内容
  children: [
    Positioned(
      left: -10,
      child: Container(width: 50, height: 50),
    ),
  ],
)

// 调试技巧:使用 debugPaintSizeEnabled 可视化布局
void main() {
  debugPaintSizeEnabled = true;  // 显示所有组件的尺寸和约束
  runApp(MyApp());
}

5.3 状态管理陷阱

setState after dispose

dart
// 问题:组件销毁后调用 setState 导致异常
// E/flutter: setState() called after dispose()

class _MyWidgetState extends State<MyWidget> {
  int _counter = 0;

  void _loadData() async {
    final result = await api.fetchData();
    if (mounted) {  // 关键:检查组件是否仍然挂载
      setState(() {
        _counter = result.count;
      });
    }
  }

  @override
  void dispose() {
    // 取消所有异步操作
    _subscription?.cancel();
    _timer?.cancel();
    super.dispose();
  }
}
dart
// 更安全的封装:使用 ChangeNotifier 自动管理

class SafeViewModel extends ChangeNotifier {
  bool _disposed = false;

  int _counter = 0;
  int get counter => _counter;

  Future<void> loadData() async {
    final result = await api.fetchData();
    if (!_disposed) {
      _counter = result.count;
      notifyListeners();  // 安全:不会在 dispose 后通知
    }
  }

  @override
  void dispose() {
    _disposed = true;
    super.dispose();
  }
}

Provider 跨页面更新无效

dart
// 问题:在页面 A 修改了 Provider 数据,页面 B 没有更新

// 原因1:Provider 作用域不同
// 页面 A 和页面 B 使用了不同作用域的 Provider

// 错误示例:每个页面都创建了新的 Provider
class PageA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => MyModel(),
      child: Scaffold(...),
    );
  }
}

// 正确示例:在共同父级提供 Provider
class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => MyModel(),  // 在顶层提供
      child: MaterialApp(
        home: PageA(),
        routes: {
          '/b': (_) => PageB(),
        },
      ),
    );
  }
}

// 原因2:使用 context.read() 不会监听变化
// 错误:在 build 中使用 read
Widget build(BuildContext context) {
  final model = context.read<MyModel>();  // 不会重建!
  return Text(model.value);
}

// 正确:使用 watch 监听变化
Widget build(BuildContext context) {
  final model = context.watch<MyModel>();  // 数据变化时重建
  return Text(model.value);
}

// 正确:使用 Selector 精确监听
Widget build(BuildContext context) {
  final value = context.select<MyModel, String>((m) => m.value);
  return Text(value);
}

InheritedWidget 数据不更新

dart
// 问题:InheritedWidget 数据变化但子组件不重建

// 原因:updateShouldNotify 返回 false
class MyInheritedWidget extends InheritedWidget {
  final int data;

  MyInheritedWidget({required this.data, required Widget child})
      : super(child: child);

  @override
  bool updateShouldNotify(MyInheritedWidget oldWidget) {
    return data != oldWidget.data;  // 必须正确比较!
  }
}

// 常见错误:每次都返回 false
@override
bool updateShouldNotify(covariant MyInheritedWidget oldWidget) {
  return false;  // 永远不会通知子组件更新
}

不必要的 rebuild

dart
// 问题:整个页面因局部状态变化而全部重建

// 错误示例:整个 Scaffold 因一个计数器重建
class _MyPageState extends State<MyPage> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('页面')),  // 不需要重建
      body: VeryExpensiveWidget(),           // 不需要重建
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => _counter++),
        child: Text('$_counter'),            // 只有这里需要重建
      ),
    );
  }
}

// 解决1:提取局部 Widget
class _MyPageState extends State<MyPage> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('页面')),
      body: VeryExpensiveWidget(),
      floatingActionButton: _CounterButton(counter: _counter),
    );
  }
}

class _CounterButton extends StatelessWidget {
  final int counter;
  const _CounterButton({required this.counter});

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {},  // 由父组件处理
      child: Text('$counter'),
    );
  }
}

// 解决2:使用 ValueNotifier + ValueListenableBuilder
class _MyPageState extends State<MyPage> {
  final _counter = ValueNotifier<int>(0);

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('页面')),
      body: VeryExpensiveWidget(),  // 不会重建
      floatingActionButton: ValueListenableBuilder<int>(
        valueListenable: _counter,
        builder: (context, value, child) {
          return FloatingActionButton(
            onPressed: () => _counter.value++,
            child: Text('$value'),
          );
        },
      ),
    );
  }
}

5.4 网络请求问题

HTTPS 证书校验失败

dart
// 问题:自签名证书或证书链不完整导致请求失败
// HandshakeException: Handshake error in client

// 解决1:开发环境跳过证书校验(仅用于调试!)
import 'dart:io';

HttpClient createHttpClient() {
  final client = HttpClient();
  client.badCertificateCallback = (cert, host, port) {
    // 仅在开发环境跳过
    return bool.fromEnvironment('dart.vm.product') == false;
  };
  return client;
}

// 解决2:使用 dio 的证书配置
import 'package:dio/dio.dart';
import 'package:dio/adapter.dart';

Dio createDio() {
  final dio = Dio();
  (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
      (client) {
    client.badCertificateCallback = (cert, host, port) => true;
    return client;
  };
  return dio;
}

// 解决3:生产环境使用自定义证书
SecurityContext getSecurityContext() {
  final context = SecurityContext.defaultContext;
  // 加载自定义 CA 证书
  context.setTrustedCertificatesBytes(
    rootBundle.load('assets/certs/my_ca.pem'),
  );
  return context;
}

iOS 阻止 HTTP 请求

xml
<!-- 问题:iOS 默认不允许 HTTP 明文请求 -->
<!-- 解决:在 ios/Runner/Info.plist 中添加 ATS 例外 -->

<key>NSAppTransportSecurity</key>
<dict>
    <!-- 允许所有 HTTP 请求(仅开发环境) -->
    <key>NSAllowsArbitraryLoads</key>
    <true/>

    <!-- 或仅允许特定域名(推荐生产环境) -->
    <key>NSExceptionDomains</key>
    <dict>
        <key>api.example.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
</dict>

Android 明文 HTTP 被阻止

xml
<!-- 问题:Android 9+ 默认不允许 HTTP 明文请求 -->
<!-- 解决1:在 android/app/src/main/AndroidManifest.xml 中配置 -->

<application
    android:usesCleartextTraffic="true"
    ...>
</application>

<!-- 解决2:使用网络安全配置(更精细控制) -->
<!-- 创建 android/app/src/main/res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">api.example.com</domain>
        <domain includeSubdomains="true">10.0.2.2</domain>  <!-- 模拟器本地 -->
    </domain-config>
</network-security-config>

<!-- 在 AndroidManifest.xml 中引用 -->
<application
    android:networkSecurityConfig="@xml/network_security_config"
    ...>
</application>

请求超时处理

dart
// 问题:网络请求无响应,一直等待

import 'package:dio/dio.dart';

class ApiClient {
  late final Dio _dio;

  ApiClient() {
    _dio = Dio(BaseOptions(
      connectTimeout: Duration(seconds: 10),   // 连接超时
      receiveTimeout: Duration(seconds: 30),   // 接收超时
      sendTimeout: Duration(seconds: 10),      // 发送超时
    ));

    _dio.interceptors.add(_RetryInterceptor(
      dio: _dio,
      retries: 3,
      retryDelays: [
        Duration(seconds: 1),
        Duration(seconds: 2),
        Duration(seconds: 4),
      ],
    ));
  }

  Future<T> safeRequest<T>(String path, {Options? options}) async {
    try {
      final response = await _dio.request(path, options: options);
      return response.data as T;
    } on DioException catch (e) {
      switch (e.type) {
        case DioExceptionType.connectionTimeout:
        case DioExceptionType.sendTimeout:
        case DioExceptionType.receiveTimeout:
          throw ApiException('请求超时,请检查网络连接');
        case DioExceptionType.connectionError:
          throw ApiException('网络连接失败');
        case DioExceptionType.badResponse:
          throw ApiException('服务器错误: ${e.response?.statusCode}');
        default:
          throw ApiException('未知网络错误');
      }
    }
  }
}

class ApiException implements Exception {
  final String message;
  ApiException(this.message);
  @override
  String toString() => message;
}

// 重试拦截器
class _RetryInterceptor extends Interceptor {
  final Dio dio;
  final int retries;
  final List<Duration> retryDelays;

  _RetryInterceptor({
    required this.dio,
    required this.retries,
    required this.retryDelays,
  });

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    int retryCount = err.requestOptions.extra['retryCount'] ?? 0;

    if (_shouldRetry(err) && retryCount < retries) {
      retryCount++;
      err.requestOptions.extra['retryCount'] = retryCount;

      final delay = retryDelays[retryCount - 1];
      await Future.delayed(delay);

      try {
        final response = await dio.fetch(err.requestOptions);
        handler.resolve(response);
      } on DioException catch (e) {
        handler.next(e);
      }
    } else {
      handler.next(err);
    }
  }

  bool _shouldRetry(DioException err) {
    return err.type == DioExceptionType.connectionTimeout ||
        err.type == DioExceptionType.receiveTimeout ||
        err.type == DioExceptionType.connectionError;
  }
}
dart
// 问题:多请求间 Cookie 不共享,登录状态丢失

import 'package:dio/dio.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:cookie_jar/cookie_jar.dart';

class CookieApiClient {
  late final Dio _dio;

  CookieApiClient() {
    _dio = Dio();

    // 使用持久化 CookieJar
    final cookieJar = PersistCookieJar(
      storage: FileStorage('./cookies'),  // 持久化到文件
    );

    _dio.interceptors.add(CookieManager(cookieJar));
  }

  Future<void> login(String username, String password) async {
    await _dio.post('/api/login', data: {
      'username': username,
      'password': password,
    });
    // Cookie 自动保存,后续请求自动携带
  }

  Future<void> getUserInfo() async {
    // 自动携带登录 Cookie
    final response = await _dio.get('/api/user/info');
  }

  Future<void> logout() async {
    await _dio.post('/api/logout');
    // 清除 Cookie
    final cookieJar = _dio.interceptors
        .whereType<CookieManager>()
        .first
        .cookieJar;
    await cookieJar.deleteAll();
  }
}

5.5 性能问题

ListView 卡顿优化

dart
// 问题:长列表滚动卡顿,帧率低于 60fps

// 错误示例:使用 ListView children 加载大量数据
ListView(
  children: List.generate(10000, (index) =>
    VeryComplexWidget(index: index),  // 一次性创建所有 Widget
  ),
)

// 解决1:使用 ListView.builder 懒加载
ListView.builder(
  itemCount: 10000,
  itemBuilder: (context, index) {
    return VeryComplexWidget(index: index);  // 按需创建
  },
)

// 解决2:使用 cacheExtent 预加载
ListView.builder(
  cacheExtent: 500,  // 预加载 500 逻辑像素范围外的项
  itemCount: 10000,
  itemBuilder: (context, index) {
    return VeryComplexWidget(index: index);
  },
)

// 解决3:使用 addAutomaticKeepAlives 保持状态
ListView.builder(
  addAutomaticKeepAlives: true,  // 默认 true,保持已滚动过的项状态
  addRepaintBoundaries: true,    // 默认 true,减少重绘范围
  itemCount: 10000,
  itemBuilder: (context, index) {
    return VeryComplexWidget(index: index);
  },
)

// 解决4:使用 AutomaticKeepAliveClientMixin 保持特定项
class _MyListItemState extends State<MyListItem>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;  // 滚动出视口后保持状态

  @override
  Widget build(BuildContext context) {
    super.build(context);  // 必须调用
    return ListTile(title: Text('Item ${widget.index}'));
  }
}

图片加载优化

dart
// 问题:大图片加载慢、内存占用高、滚动卡顿

// 解决1:使用 cached_network_image 缓存
import 'package:cached_network_image/cached_network_image.dart';

CachedNetworkImage(
  imageUrl: 'https://example.com/large_image.jpg',
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
  memCacheWidth: 300,  // 内存中缓存的最大宽度
  memCacheHeight: 300, // 内存中缓存的最大高度
)

// 解决2:使用 ResizeImage 减少内存占用
Image(
  image: ResizeImage(
    NetworkImage('https://example.com/large_image.jpg'),
    width: 300,
    height: 300,
  ),
)

// 解决3:使用 precacheImage 预加载
class _MyPageState extends State<MyPage> {
  final _imageProvider = NetworkImage('https://example.com/hero.jpg');

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    precacheImage(_imageProvider, context);  // 预加载到缓存
  }

  @override
  Widget build(BuildContext context) {
    return Image(image: _imageProvider);  // 直接从缓存读取
  }
}

// 解决4:列表中使用 ClipRect 限制绘制区域
ListView.builder(
  itemCount: images.length,
  itemBuilder: (context, index) {
    return ClipRect(
      child: CachedNetworkImage(
        imageUrl: images[index],
        fit: BoxFit.cover,
      ),
    );
  },
)

Jank 帧分析与修复

dart
// 问题:UI 线程卡顿导致掉帧

// 分析工具:使用 Flutter DevTools 的 Performance 面板
// 命令行启动:
// flutter run --profile
// 然后打开 DevTools -> Performance

// 常见原因1:build 方法中执行耗时操作
// 错误示例
Widget build(BuildContext context) {
  final sortedList = veryLargeList..sort();  // 在 build 中排序!
  return ListView(children: sortedList.map((e) => Text(e)).toList());
}

// 正确:将耗时操作移到 initState 或异步方法中
class _MyPageState extends State<MyPage> {
  List<String> _sortedList = [];

  @override
  void initState() {
    super.initState();
    _sortData();
  }

  void _sortData() {
    final sorted = List.of(veryLargeList)..sort();
    setState(() => _sortedList = sorted);
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _sortedList.length,
      itemBuilder: (context, index) => Text(_sortedList[index]),
    );
  }
}

// 常见原因2:过度使用 saveLayer
// 避免在不需要时使用 Opacity、Clip、ShaderMask 等
// 错误:大面积使用 Opacity 动画
AnimatedOpacity(
  opacity: _visible ? 1.0 : 0.0,
  duration: Duration(milliseconds: 300),
  child: VeryLargeWidget(),  // 每帧都创建 saveLayer
)

// 正确:使用 FadeTransition
FadeTransition(
  opacity: _animation,
  child: VeryLargeWidget(),
)

// 常见原因3:使用 RepaintBoundary 隔离重绘
Row(
  children: [
    RepaintBoundary(
      child: AnimatedWidget(),  // 动画部分隔离
    ),
    StaticWidget(),              // 不会因动画而重绘
  ],
)

GPU 性能问题

dart
// 问题:GPU 线程过载,大量阴影、裁剪、渐变导致卡顿

// 1. 减少阴影使用
// 错误:大量使用 BoxShadow
Container(
  decoration: BoxDecoration(
    boxShadow: [
      BoxShadow(color: Colors.black26, blurRadius: 20, spreadRadius: 5),
    ],
  ),
)

// 优化:使用预渲染阴影图片或减少 blurRadius
Container(
  decoration: BoxDecoration(
    boxShadow: [
      BoxShadow(color: Colors.black12, blurRadius: 4),  // 减小模糊半径
    ],
  ),
)

// 2. 减少裁剪操作
// 错误:嵌套 ClipPath
ClipPath(
  clipper: MyClipper(),
  child: ClipRRect(
    borderRadius: BorderRadius.circular(12),
    child: image,
  ),
)

// 优化:合并裁剪或使用 Container 的 decoration
Container(
  decoration: BoxDecoration(
    borderRadius: BorderRadius.circular(12),
    image: DecorationImage(image: imageProvider, fit: BoxFit.cover),
  ),
)

// 3. 检查 GPU 性能
// flutter run --profile
// 在 DevTools 中查看 GPU 帧耗时
// 目标:GPU 帧耗时 < 16ms(60fps)

内存占用过高

dart
// 问题:应用内存持续增长,最终 OOM 崩溃

// 1. 使用 DevTools Memory 面板分析
// flutter run --profile
// DevTools -> Memory -> 拍摄快照对比

// 2. 常见内存泄漏源:图片缓存
// 限制图片缓存大小
PaintingBinding.instance.imageCache.maximumSize = 100;       // 最多 100 张
PaintingBinding.instance.imageCache.maximumSizeBytes = 50 * 1024 * 1024; // 50MB

// 3. 清除不需要的图片缓存
void clearImageCache() {
  PaintingBinding.instance.imageCache.clear();
  PaintingBinding.instance.imageCache.clearLiveImages();
}

// 4. 大列表使用 dispose 回收资源
class _ImageItemState extends State<ImageItem> {
  ImageStreamListener? _listener;

  @override
  void dispose() {
    // 移除图片监听
    if (_listener != null) {
      final provider = NetworkImage(widget.url);
      provider.resolve(ImageConfiguration.empty).removeListener(_listener!);
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Image.network(widget.url);
  }
}

5.6 平台兼容问题

Android 权限配置

xml
<!-- 问题:Android 运行时权限未正确处理 -->

<!-- 1. 在 android/app/src/main/AndroidManifest.xml 声明权限 -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 存储权限 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- 相机权限 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- 定位权限 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <!-- 网络权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>
dart
// 2. 使用 permission_handler 请求运行时权限
import 'package:permission_handler/permission_handler.dart';

class PermissionHelper {
  // 请求单个权限
  static Future<bool> request(Permission permission) async {
    final status = await permission.request();
    return status.isGranted;
  }

  // 请求多个权限
  static Future<Map<Permission, PermissionStatus>> requestMultiple(
    List<Permission> permissions,
  ) async {
    return await permissions.request();
  }

  // 检查权限状态
  static Future<bool> check(Permission permission) async {
    final status = await permission.status;
    return status.isGranted;
  }

  // 打开设置页面让用户手动授权
  static Future<bool> openSettings() async {
    return await openAppSettings();
  }

  // 带引导的权限请求
  static Future<bool> requestWithRationale(
    Permission permission, {
    required String rationale,
  }) async {
    var status = await permission.status;

    if (status.isGranted) return true;

    if (status.isDenied) {
      // 首次拒绝,显示原因后再次请求
      final shouldRequest = await showDialog<bool>(
        context: navigatorKey.currentContext!,
        builder: (context) => AlertDialog(
          title: Text('需要权限'),
          content: Text(rationale),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context, false),
              child: Text('拒绝'),
            ),
            TextButton(
              onPressed: () => Navigator.pop(context, true),
              child: Text('允许'),
            ),
          ],
        ),
      );

      if (shouldRequest == true) {
        status = await permission.request();
      }
    }

    if (status.isPermanentlyDenied) {
      // 用户选择了"不再询问",引导到设置页
      await openAppSettings();
      return false;
    }

    return status.isGranted;
  }
}

iOS 权限与 Info.plist

xml
<!-- 问题:iOS 未在 Info.plist 中声明权限描述导致崩溃 -->

<!-- ios/Runner/Info.plist -->
<dict>
    <!-- 相机权限 -->
    <key>NSCameraUsageDescription</key>
    <string>需要访问相机以拍摄照片</string>

    <!-- 相册权限 -->
    <key>NSPhotoLibraryUsageDescription</key>
    <string>需要访问相册以选择照片</string>

    <!-- 定位权限 -->
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>需要获取位置信息以提供附近服务</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>需要持续获取位置信息以提供实时导航</string>

    <!-- 麦克风权限 -->
    <key>NSMicrophoneUsageDescription</key>
    <string>需要访问麦克风以录制语音</string>

    <!-- 通讯录权限 -->
    <key>NSContactsUsageDescription</key>
    <string>需要访问通讯录以选择联系人</string>

    <!-- 蓝牙权限 -->
    <key>NSBluetoothAlwaysUsageDescription</key>
    <string>需要蓝牙以连接外部设备</string>
</dict>

缺少权限描述字符串会导致 iOS 应用直接崩溃,这是 Apple 的强制要求。

屏幕适配

dart
// 问题:不同屏幕尺寸和分辨率下布局错乱

// 解决1:使用 MediaQuery 获取屏幕信息
class AdaptiveWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    final padding = MediaQuery.of(context).padding;
    final textScale = MediaQuery.of(context).textScaleFactor;

    if (size.width > 600) {
      // 平板布局
      return TabletLayout();
    } else {
      // 手机布局
      return MobileLayout();
    }
  }
}

// 解决2:使用 flutter_screenutil 适配
import 'package:flutter_screenutil/flutter_screenutil.dart';

// 初始化(在 MaterialApp 之前)
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: Size(375, 812),  // 设计稿尺寸
      minTextAdapt: true,
      builder: (context, child) {
        return MaterialApp(home: child);
      },
      child: HomePage(),
    );
  }
}

// 使用适配单位
Container(
  width: 200.w,    // 宽度适配
  height: 100.h,   // 高度适配
  padding: EdgeInsets.all(16.r),  // 圆角适配
  child: Text(
    '适配文字',
    style: TextStyle(fontSize: 14.sp),  // 字体适配
  ),
)

// 解决3:使用 LayoutBuilder 响应式布局
LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth > 600) {
      return Row(
        children: [
          Expanded(child: MenuPanel()),
          Expanded(flex: 3, child: ContentPanel()),
        ],
      );
    }
    return Column(
      children: [
        MenuPanel(),
        Expanded(child: ContentPanel()),
      ],
    );
  },
)

安全区域适配

dart
// 问题:刘海屏、底部横条遮挡内容

// 解决1:使用 SafeArea
Scaffold(
  body: SafeArea(
    top: true,     // 顶部安全区域(状态栏/刘海)
    bottom: true,  // 底部安全区域(Home Indicator)
    left: true,
    right: true,
    child: Content(),
  ),
)

// 解决2:使用 MediaQuery.padding 手动处理
class _MyPageState extends State<MyPage> {
  @override
  Widget build(BuildContext context) {
    final padding = MediaQuery.of(context).padding;

    return Padding(
      padding: EdgeInsets.only(
        top: padding.top,       // 状态栏高度
        bottom: padding.bottom, // 底部安全区域
      ),
      child: Content(),
    );
  }
}

// 解决3:Scaffold 自动处理安全区域
Scaffold(
  body: Column(
    children: [
      // 自定义 AppBar 需要手动处理顶部安全区域
      Container(
        padding: EdgeInsets.only(
          top: MediaQuery.of(context).padding.top,
        ),
        child: CustomAppBar(),
      ),
      // body 区域由 Scaffold 自动处理底部安全区域
      Expanded(child: Content()),
    ],
  ),
)

状态栏样式控制

dart
// 问题:状态栏颜色/图标样式不符合需求

import 'package:flutter/services.dart';

class StatusBarHelper {
  // 设置状态栏样式
  static void setStyle({
    Color? backgroundColor,
    Brightness iconBrightness = Brightness.dark,
    bool transparent = false,
  }) {
    SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
      statusBarColor: transparent ? Colors.transparent : backgroundColor,
      statusBarIconBrightness: iconBrightness,     // Android 图标颜色
      statusBarBrightness: iconBrightness == Brightness.dark
          ? Brightness.light                        // iOS 图标颜色(反向)
          : Brightness.dark,
    ));
  }

  // 隐藏/显示状态栏
  static void hide() {
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
  }

  static void show() {
    SystemChrome.setEnabledSystemUIMode(
      SystemUiMode.edgeToEdge,
    );
  }

  // 设置屏幕方向
  static void setOrientation(List<DeviceOrientation> orientations) {
    SystemChrome.setPreferredOrientations(orientations);
  }
}

// 使用 AnnotatedRegion 在不同页面设置不同状态栏样式
class DarkStatusBarPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AnnotatedRegion<SystemUiOverlayStyle>(
      value: SystemUiOverlayStyle.light,  // 白色状态栏图标
      child: Scaffold(
        backgroundColor: Colors.black,
        body: Content(),
      ),
    );
  }
}

5.7 内存泄漏

Image 资源未释放

dart
// 问题:大量加载图片后内存不下降

// 1. 限制图片缓存
void configureImageCache() {
  PaintingBinding.instance.imageCache.maximumSize = 200;
  PaintingBinding.instance.imageCache.maximumSizeBytes = 100 * 1024 * 1024; // 100MB
}

// 2. 在页面销毁时清除相关图片缓存
class _GalleryPageState extends State<GalleryPage> {
  final List<ImageProvider> _imageProviders = [];

  @override
  void dispose() {
    // 逐个清除图片缓存
    for (final provider in _imageProviders) {
      provider.evict();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      itemCount: widget.imageUrls.length,
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
      ),
      itemBuilder: (context, index) {
        final provider = NetworkImage(widget.imageUrls[index]);
        _imageProviders.add(provider);
        return Image(image: provider, fit: BoxFit.cover);
      },
    );
  }
}

// 3. 使用 AutoDispose 自动管理(Riverpod 示例)
final imageProvider = Provider.autoDispose<ImageProvider>((ref) {
  return NetworkImage('https://example.com/image.jpg');
  // 当没有监听者时自动释放
});

Stream 未取消订阅

dart
// 问题:Stream 订阅未取消,导致回调持续执行

// 错误示例
class _MyWidgetState extends State<MyWidget> {
  @override
  void initState() {
    super.initState();
    // 订阅后未保存引用,无法取消!
    eventBus.on<UserEvent>().listen((event) {
      setState(() {});
    });
  }
}

// 正确示例1:保存 StreamSubscription 引用
class _MyWidgetState extends State<MyWidget> {
  StreamSubscription? _subscription;

  @override
  void initState() {
    super.initState();
    _subscription = eventBus.on<UserEvent>().listen((event) {
      if (mounted) {
        setState(() {});
      }
    });
  }

  @override
  void dispose() {
    _subscription?.cancel();  // 必须取消!
    super.dispose();
  }
}

// 正确示例2:使用 StreamBuilder 声明式管理
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<UserEvent>(
      stream: eventBus.on<UserEvent>(),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text(snapshot.data!.name);
        }
        return CircularProgressIndicator();
      },
    );
  }
}

// 正确示例3:多个订阅统一管理
class _MyWidgetState extends State<MyWidget> {
  final List<StreamSubscription> _subscriptions = [];

  @override
  void initState() {
    super.initState();
    _subscriptions.add(
      eventBus.on<UserEvent>().listen((event) { setState(() {}); }),
    );
    _subscriptions.add(
      eventBus.on<OrderEvent>().listen((event) { setState(() {}); }),
    );
    _subscriptions.add(
      connectivity.onConnectivityChanged.listen((result) { setState(() {}); }),
    );
  }

  @override
  void dispose() {
    for (final sub in _subscriptions) {
      sub.cancel();
    }
    _subscriptions.clear();
    super.dispose();
  }
}

Timer 未清理

dart
// 问题:Timer 未取消,组件销毁后仍在执行

// 错误示例
class _MyWidgetState extends State<MyWidget> {
  @override
  void initState() {
    super.initState();
    Timer.periodic(Duration(seconds: 1), (timer) {
      setState(() {});  // 组件销毁后仍会调用!
    });
  }
}

// 正确示例
class _MyWidgetState extends State<MyWidget> {
  Timer? _timer;

  @override
  void initState() {
    super.initState();
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (mounted) {
        setState(() {});
      }
    });
  }

  @override
  void dispose() {
    _timer?.cancel();  // 必须取消!
    super.dispose();
  }
}

Controller 未 dispose

dart
// 问题:各种 Controller 未调用 dispose 导致资源泄漏

class _MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
  late final AnimationController _animController;
  late final ScrollController _scrollController;
  late final TextEditingController _textController;
  late final PageController _pageController;

  @override
  void initState() {
    super.initState();
    _animController = AnimationController(
      vsync: this,
      duration: Duration(seconds: 1),
    );
    _scrollController = ScrollController();
    _textController = TextEditingController();
    _pageController = PageController();
  }

  @override
  void dispose() {
    // 所有 Controller 都必须 dispose
    _animController.dispose();
    _scrollController.dispose();
    _textController.dispose();
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView(
        controller: _pageController,
        children: [
          ListView(
            controller: _scrollController,
            children: [
              TextField(controller: _textController),
              FadeTransition(
                opacity: _animController,
                child: FlutterLogo(size: 100),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

// 使用 GetX 自动管理 Controller
class MyController extends GetxController {
  final count = 0.obs;

  void increment() => count.value++;

  @override
  void onClose() {
    // 当 Controller 被移除时自动调用
    super.onClose();
  }
}

class MyPage extends StatelessWidget {
  final controller = Get.put(MyController());
  // 当页面销毁时,Controller 自动调用 onClose 并释放
}

5.8 构建打包问题

Gradle 构建失败

groovy
// 问题1:Gradle 版本不兼容
// 错误:Could not find method implementation()

// 解决:检查 android/build.gradle 和 android/app/build.gradle
// android/build.gradle
buildscript {
    ext.kotlin_version = '1.9.0'  // 确保 Kotlin 版本兼容
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.4.2'  // AGP 版本
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

// 问题2:依赖冲突
// 错误:Dependency resolution failed

// 解决:强制统一版本
configurations.all {
    resolutionStrategy {
        force 'androidx.core:core:1.10.0'
    }
}

// 问题3:内存不足
// 错误:Java heap space

// 解决:在 android/gradle.properties 中增加内存
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=1G
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.caching=true
bash
# 常用 Gradle 清理命令
cd android
./gradlew clean
./gradlew cleanBuildCache

# 查看依赖树
./gradlew app:dependencies

# 清除 Flutter 缓存
flutter clean
flutter pub get

CocoaPods 版本冲突

ruby
# 问题1:CocoaPods 版本不兼容
# 错误:Podfile.lock 版本与当前版本不一致

# 解决:更新 CocoaPods
sudo gem install cocoapods
cd ios
pod repo update
pod install

# 问题2:Pod 依赖冲突
# 错误:Specs satisfying the `xxx` dependency were not found

# 解决:在 ios/Podfile 中指定版本
target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))

  # 指定版本解决冲突
  pod 'Alamofire', '~> 5.6'
  pod 'Firebase/Core', '~> 10.0'
end

# 问题3:架构不匹配(模拟器 vs 真机)
# 错误:No such module 'xxx'

# 解决:在 Podfile 中排除模拟器架构
post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    target.build_configurations.each do |config|
      # 排除模拟器 arm64 架构(M1 Mac)
      config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64'
    end
  end
end
bash
# iOS 清理命令
cd ios
rm -rf Pods
rm Podfile.lock
pod deintegrate
pod install

# 清理 Xcode 缓存
rm -rf ~/Library/Developer/Xcode/DerivedData

ProGuard 混淆问题

proguard
# 问题:Release 包因 ProGuard 混淆导致反射调用失败

# android/app/proguard-rules.pro

# 保留 Flutter 引擎
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.**  { *; }
-keep class io.flutter.util.**  { *; }
-keep class io.flutter.view.**  { *; }
-keep class io.flutter.**  { *; }
-keep class io.flutter.plugins.**  { *; }

# 保留 JSON 序列化相关类
-keepclassmembers,allowobfuscation class * {
  @com.google.gson.annotations.SerializedName <fields>;
}

# 保留反射调用的类
-keep class com.example.myapp.models.** { *; }

# 保留 Platform Channel 相关
-keep class * extends java.lang.reflect.** { *; }

# 保留第三方 SDK
-keep class com.tencent.** { *; }
-keep class com.alipay.** { *; }
-keep class com.google.firebase.** { *; }
groovy
// 在 android/app/build.gradle 中启用 ProGuard
android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }
}

包体积优化

bash
# 分析包体积
flutter build apk --analyze-size
flutter build ios --analyze-size

# 查看详细大小分布
flutter build apk --analyze-size --target-platform android-arm64
dart
// 1. 使用 --split-per-abi 减小 APK 体积
// 不同 CPU 架构分别打包
bash
flutter build apk --split-per-abi
# 生成三个小 APK:
# app-armeabi-v7a-release.apk   (~5MB)
# app-arm64-v8a-release.apk     (~6MB)
# app-x86_64-release.apk        (~6MB)
# 而非一个通用 APK (~15MB)
yaml
# 2. 使用 App Bundle (AAB) 发布
# Google Play 推荐格式,自动按设备拆分
flutter build appbundle
dart
// 3. 优化资源文件

// 3.1 压缩图片资源
// 使用 tinypng 等工具压缩 assets 中的图片

// 3.2 使用矢量图替代位图
// 优先使用 IconData 而非图片
Icon(Icons.home)  // 矢量图标,体积小

// 3.3 按需加载字体
// pubspec.yaml
flutter:
  fonts:
    - family: MyIconFont
      fonts:
        - asset: fonts/MyIconFont.ttf
          weight: 400  # 只加载需要的字重

// 3.4 延迟加载大型资源
// 使用 flutter_assets 的 deferred 加载
bash
# 4. 使用 --tree-shake-icons 移除未使用的图标
flutter build apk --tree-shake-icons

# 5. 检查未使用的依赖
# 使用 dependency_validator
dart pub global activate dependency_validator
dart pub global run dependency_validator

# 6. 对比优化前后大小
flutter build apk --release
# 记录大小,逐步优化

包体积优化是一个持续过程,建议在 CI/CD 中集成大小监控,每次构建后自动记录包体积变化,及时发现体积回退。


总结

本教程从 Flutter 入门到资深级别,覆盖了以下核心内容:

级别核心内容适合人群
初级篇Dart 语法、Widget 体系、布局系统、路由导航、事件处理、本地存储、网络请求Flutter 初学者
中级篇状态管理、动画系统、自定义组件、主题国际化、文件操作、平台通道有一定经验的开发者
高级篇自定义绘制、性能优化、测试体系、架构模式、CI/CD、Add-to-App高级开发者
资深篇引擎原理、渲染管线、Isolate 并发、插件开发、Flutter Web架构师/资深工程师
异常专题环境问题、布局异常、状态陷阱、网络问题、性能问题、平台兼容、内存泄漏、构建打包所有开发者

学习建议:按照初级 → 中级 → 高级 → 资深的顺序循序渐进,每个阶段都要动手实践。遇到问题时,参考第五章异常专题的解决方案。Flutter 生态发展迅速,建议持续关注 Flutter 官方文档Flutter GitHub 获取最新动态。

基于 MIT 许可发布