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 构建打包问题扩展专题导航
- Flutter 面试题大全
- Flutter 疑难问题专题
- Flutter 接口封装示例
- Flutter 资深开发常用插件依赖大全
- Flutter 中大型项目 pubspec.yaml 完整示例
- Flutter 插件接入常见坑与排查手册
一、初级篇 - 从入门到会用
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
# 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/windows2. 运行 flutter doctor 检查环境
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 resources3. 创建并运行第一个项目
flutter create my_first_app
cd my_first_app
flutter run4. 开发工具选择
| 工具 | 特点 |
|---|---|
| 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)成为默认行为。
// 基本类型
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 函数是一等公民,支持箭头函数、可选参数、命名参数等特性。
// 基本函数
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]类与对象
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)
// 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)
// 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 触发重建
// 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 文本组件
// 基本文本
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 图片组件
// 网络图片
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 图标组件
// 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 按钮系列
// 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 输入框
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 选择组件
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 容器
// 基本容器
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 行列布局
// 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 层叠布局
// 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 列表视图
// 静态列表
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 网格视图
// 固定列数网格
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 流式布局
// 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 弹性布局
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 间距控制
// 固定间距
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 方法。
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 生命周期
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 方法获取祖先节点的数据,当数据变化时自动重建依赖的子组件。
// 定义 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 路由与导航
Navigator.push/pop 基本导航
// 跳转到新页面
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, '选中的选项');命名路由
// 在 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')),
);
}
}路由传参与返回结果
// 完整示例:商品列表 -> 商品详情 -> 确认购买
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 资源管理
图片资源管理
# pubspec.yaml
flutter:
assets:
- assets/images/
- assets/images/2x/
- assets/data/config.json// 加载资源图片
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);
}字体资源管理
# 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// 使用自定义字体
Text(
'自定义字体',
style: TextStyle(
fontFamily: 'Roboto',
fontSize: 16,
fontWeight: FontWeight.w700,
),
)
// 全局字体配置
MaterialApp(
theme: ThemeData(
fontFamily: 'Roboto',
),
)pubspec.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 基础
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('登录'),
),
),
],
),
);
}
}自定义验证器
// 通用验证器工具类
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
// 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 下拉刷新
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]));
},
),
);
}
}上拉加载更多
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
在列表中保持页面状态,防止切换后重建。
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 自动完成过渡动画。
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 并控制动画的播放。
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 动画实现页面间的共享元素过渡效果。
// 列表页
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),
),
),
),
),
);
}
}交错动画
多个动画按顺序依次执行,形成波浪式效果。
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
适合存储简单的键值对数据,如用户设置、登录状态等。
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 数据库
适合存储结构化数据,如聊天记录、离线数据等。
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、日志等。
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 客户端。
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 等。
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 序列化
// 手动序列化(适合小项目)
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 封装。
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。
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 是基于事件驱动的状态管理方案,适合复杂业务逻辑。
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
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 绘制
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 渲染管线中的核心对象,负责布局和绘制。
// 自定义 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 构造函数
// 使用 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
将频繁变化的区域隔离,避免整个页面重绘。
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 优化
// 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),
)图片优化
// 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)
# 启动 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 与原生平台之间的方法调用(双向通信)。
// 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});
}
}// 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)
}
}// 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 持续发送事件流。
// 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
用于双向消息通信,适合频繁的数据交换。
// 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 测试体系
单元测试
测试独立的函数、方法和类。
// 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 的渲染和交互。
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);
});
}集成测试
测试完整的应用流程,运行在真实设备或模拟器上。
// 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 是否符合预期。
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'),
);
});
}# 更新 Golden 文件
flutter test --update-goldens3.5 打包发布
Android 签名与打包
# 1. 生成签名密钥
keytool -genkey -v -keystore release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias release
# 2. 创建 key.properties 文件
# android/key.properties# android/key.properties
storePassword=your_store_password
keyPassword=your_key_password
keyAlias=release
storeFile=/path/to/release-key.jks// 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'
}
}
}# 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=xiaomiiOS 打包与上架
# 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 ConnectWeb 打包
# 构建 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 auto3.6 主题与国际化
ThemeData 配置
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(),
)暗黑模式
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)
# pubspec.yaml
dependencies:
flutter_localizations:
sdk: flutter
intl: ^0.18.0// 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// 领域层 - 实体
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 模式
// 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)强调单向数据流,状态不可变。
// 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):发布时使用,代码在编译期生成高效机器码,启动快、运行流畅
# JIT 模式(开发)
flutter run # 支持 hot reload
# AOT 模式(发布)
flutter build apk --release # 编译为机器码Skia/Impeller 渲染
- Skia:2D 图形渲染引擎,Google 开源,Flutter 一直使用。在复杂场景下可能出现着色器编译卡顿(Shader Compilation Jank)
- Impeller:Flutter 3.7+ 引入的新渲染引擎,预编译着色器,解决 Skia 的着色器编译卡顿问题
# 启用 Impeller(Android)
flutter run --enable-impeller
# Android Manifest 配置
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="true" />
# iOS 已默认启用 Impeller4.3 渲染管线
Widget -> Element -> RenderObject
Flutter 渲染管线的核心是三棵树:
Widget 树 (不可变配置) Element 树 (管理生命周期) RenderObject 树 (布局与绘制)
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Container │ ──createElement→ │ Element │ ──createRender→ │RenderBox │
│ (配置) │ │ (实例) │ │(布局绘制)│
└──────────┘ └──────────┘ └──────────┘
↓ ↓ ↓
描述 UI 配置 管理 Widget 与 RenderObject 实际测量布局和绘制
不可变,频繁重建 可复用,diff 算法优化 只在需要时重建// 三棵树的关系
// 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)
↓
父组件根据返回的尺寸和位置放置子组件// 约束类型
// 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,使其子树的重绘不会影响父树。
// 没有 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 有独立的内存堆,通过消息传递通信。
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 的简化封装,适合一次性计算任务。
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 池避免重复创建销毁的开销。
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);
}
});
}网络请求并发控制
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
# .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-codesignFastlane
# 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# 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
# 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# 运行 lint 检查
flutter analyze
# 自动格式化
dart format lib/4.6 混合开发(Add-to-App)
Flutter 模块集成到原生项目
# 1. 创建 Flutter 模块
flutter create -t module --org com.example my_flutter_module
# 2. 编译 AAR(Android)
cd my_flutter_module
flutter build aar// 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'
}# iOS 项目集成
# 1. 在 Podfile 中添加
pod 'FlutterModule', :path => '../my_flutter_module/.ios/Flutter.podspec'
# 2. 安装
pod installFlutterEngine
// 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()
}
}// 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 代码生成工具:
# pubspec.yaml
dev_dependencies:
pigeon: ^17.0.0// 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);
}# 生成平台代码
dart run pigeon --input pigeons/messages.dart4.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平台接口设计
// 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++ 库,无需平台通道。
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);
}// native.c
#include <stdint.h>
int32_t add(int32_t a, int32_t b) {
return a + b;
}发布到 pub.dev
# 1. 检查插件
flutter pub publish --dry-run
# 2. 发布
flutter pub publish# 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 失败
# 问题: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"依赖下载慢
# 问题: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// 解决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 版本冲突
# 问题: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 runCocoaPods 问题
# 问题: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.05.2 布局渲染异常
RenderFlex overflowed
最常见的 Flutter 布局错误,表示子组件超出了父组件的边界。
// 问题: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.// 问题:在无界约束的父组件中使用 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 文本溢出
// 问题:长文本超出容器边界
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('这是一段很长的文本'),
),
)键盘弹出导致溢出
// 问题:键盘弹出时底部内容被顶出屏幕,出现溢出黄条
// 解决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,
),
);
},
)布局约束错误排查
// 常见约束错误: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
// 问题:组件销毁后调用 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();
}
}// 更安全的封装:使用 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 跨页面更新无效
// 问题:在页面 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 数据不更新
// 问题: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
// 问题:整个页面因局部状态变化而全部重建
// 错误示例:整个 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 证书校验失败
// 问题:自签名证书或证书链不完整导致请求失败
// 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 请求
<!-- 问题: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 被阻止
<!-- 问题: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>请求超时处理
// 问题:网络请求无响应,一直等待
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;
}
}Cookie 管理问题
// 问题:多请求间 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 卡顿优化
// 问题:长列表滚动卡顿,帧率低于 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}'));
}
}图片加载优化
// 问题:大图片加载慢、内存占用高、滚动卡顿
// 解决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 帧分析与修复
// 问题: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 性能问题
// 问题: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)内存占用过高
// 问题:应用内存持续增长,最终 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 权限配置
<!-- 问题: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>// 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
<!-- 问题: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 的强制要求。
屏幕适配
// 问题:不同屏幕尺寸和分辨率下布局错乱
// 解决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()),
],
);
},
)安全区域适配
// 问题:刘海屏、底部横条遮挡内容
// 解决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()),
],
),
)状态栏样式控制
// 问题:状态栏颜色/图标样式不符合需求
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 资源未释放
// 问题:大量加载图片后内存不下降
// 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 未取消订阅
// 问题: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 未清理
// 问题: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
// 问题:各种 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 构建失败
// 问题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# 常用 Gradle 清理命令
cd android
./gradlew clean
./gradlew cleanBuildCache
# 查看依赖树
./gradlew app:dependencies
# 清除 Flutter 缓存
flutter clean
flutter pub getCocoaPods 版本冲突
# 问题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# iOS 清理命令
cd ios
rm -rf Pods
rm Podfile.lock
pod deintegrate
pod install
# 清理 Xcode 缓存
rm -rf ~/Library/Developer/Xcode/DerivedDataProGuard 混淆问题
# 问题: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.** { *; }// 在 android/app/build.gradle 中启用 ProGuard
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
}包体积优化
# 分析包体积
flutter build apk --analyze-size
flutter build ios --analyze-size
# 查看详细大小分布
flutter build apk --analyze-size --target-platform android-arm64// 1. 使用 --split-per-abi 减小 APK 体积
// 不同 CPU 架构分别打包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)# 2. 使用 App Bundle (AAB) 发布
# Google Play 推荐格式,自动按设备拆分
flutter build appbundle// 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 加载# 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 获取最新动态。