Flutter 教程
Flutter 教程
Flutter 是 Google 推出的开源 UI 框架,使用 Dart 语言,一套代码即可构建 Android、iOS、Web、桌面多平台应用。
本教程包含
- 基础篇:安装、第一个应用、项目结构、Widget 概念
- 组件篇:布局、Container、ListView、用户输入、状态管理
- 进阶篇:网络、导航、动画、本地存储、测试、打包发布
为什么选择 Flutter
- 高性能:自绘引擎 Skia/Impeller,不依赖系统控件
- 热重载:修改代码秒级生效,开发效率高
- 一套代码多平台:移动端 + Web + 桌面 + 嵌入式
- 丰富的组件库:Material 与 Cupertino 双风格
适合人群
移动应用开发者、想低成本跨平台的团队、对 Dart 感兴趣的开发者。
Flutter 安装
Flutter 安装
安装 Flutter SDK 并配置开发环境是第一步。
系统要求
- Windows 10+ / macOS 10.14+ / Linux 64 位
- 磁盘空间 8GB+(含 IDE 和工具)
- Android Studio 或 VS Code(推荐)
下载 Flutter SDK
# macOS/Linux
cd ~/development
git clone https://github.com/flutter/flutter.git -b stable
# Windows
下载官方 zip 包解压到 C:\src\flutter
配置 PATH
# macOS/Linux 编辑 ~/.zshrc
export PATH="$PATH:$HOME/development/flutter/bin"
# 生效
source ~/.zshrc
运行 flutter doctor
flutter doctor
# 检查 Android toolchain、Xcode、Chrome 等依赖
安装 IDE 插件
# VS Code:安装 Flutter 和 Dart 扩展
# Android Studio:安装 Flutter 插件
验证
flutter --version第一个 Flutter 应用
第一个 Flutter 应用
创建并运行你的第一个 Flutter 应用,感受热重载的魔力。
创建项目
flutter create my_app
cd my_app
运行应用
flutter run
# 选择连接的设备(模拟器/真机/浏览器)
修改计数器示例
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(colorSchemeSeed: Colors.blue),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _increment() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('计数器')),
body: Center(
child: Text('点击次数: $_counter', style: const TextStyle(fontSize: 24)),
),
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: const Icon(Icons.add),
),
);
}
}
热重载
保存代码后按 r 键热重载(状态保留)或 R 完全重启,立即看到修改。
Flutter 项目结构
Flutter 项目结构
理解 Flutter 项目的目录结构,才能高效组织代码。
标准结构
my_app/
├── lib/ # Dart 源码(主要工作目录)
│ └── main.dart # 入口文件
├── test/ # 测试代码
├── android/ # Android 原生工程
├── ios/ # iOS 原生工程
├── web/ # Web 支持
├── linux/ macos/ windows/# 桌面平台工程
├── pubspec.yaml # 依赖和资源配置
├── analysis_options.yaml # 代码规范
└── README.md
pubspec.yaml 关键部分
name: my_app
description: 示例应用
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
http: ^1.1.0 # 第三方依赖
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
assets:
- assets/images/ # 资源文件
安装依赖
flutter pub get # 下载依赖
flutter pub add http # 添加新依赖Flutter Widget 基础
Flutter Widget 基础
Flutter 中一切都是 Widget(组件),UI 由 Widget 树构建而成。
核心概念
- Widget:描述 UI 的不可变配置
- Element:Widget 在树中的实例
- RenderObject:负责实际绘制和布局
StatelessWidget(无状态)
class MyText extends StatelessWidget {
const MyText({super.key});
@override
Widget build(BuildContext context) {
return const Text('你好, Flutter');
}
}
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'),
ElevatedButton(
onPressed: () => setState(() => count++),
child: const Text('加一'),
),
],
);
}
}
Widget 树结构
MaterialApp
└── Scaffold
├── AppBar
├── Body (Center → Column → Text/Button)
└── FloatingActionButton
setState 的作用
调用 setState 后 Flutter 会重新调用 build 方法重建 UI,是状态更新的核心。
Flutter StatelessWidget
Flutter StatelessWidget
StatelessWidget 是无状态组件:一旦构建完成,自身不再变化,适合展示静态内容。
使用场景
- 静态文本、图标、图片展示
- 纯展示型组件(标题、标签、分隔线)
- 数据来自外部且不会自变的组件
定义
class Greeting extends StatelessWidget {
const Greeting({super.key, required this.name});
final String name; // 外部传入的参数
@override
Widget build(BuildContext context) {
return Text('Hello, $name!');
}
}
// 使用
const Greeting(name: 'Flutter')
build 方法
- 唯一必须实现的方法
- 返回 Widget 描述 UI
- 不能在此方法中做耗时操作或修改状态
组合示例
class ProfileCard extends StatelessWidget {
const ProfileCard({super.key, required this.name, required this.avatar});
final String name;
final String avatar;
@override
Widget build(BuildContext context) {
return Card(
child: Row(
children: [
CircleAvatar(backgroundImage: NetworkImage(avatar)),
const SizedBox(width: 12),
Text(name, style: Theme.of(context).textTheme.titleLarge),
],
),
);
}
}Flutter 布局基础
Flutter 布局基础
Flutter 布局通过组合布局 Widget 实现,核心是"约束向下传递,尺寸向上回报"。
常用布局 Widget
- Row:水平排列子组件
- Column:垂直排列子组件
- Stack:叠加排列(层叠)
- Center:居中
- Padding:内边距
- Container:容器(装饰+约束)
- Expanded / Flexible:弹性分配空间
Row 与 Column
Column(
crossAxisAlignment: CrossAxisAlignment.start, // 交叉轴对齐
mainAxisAlignment: MainAxisAlignment.center, // 主轴对齐
children: [
Text('第一行'),
Row(
children: [
Icon(Icons.star, color: Colors.amber),
const SizedBox(width: 8),
Text('评分 4.8'),
],
),
],
)
Expanded 弹性布局
Row(
children: [
Expanded(flex: 2, child: Container(color: Colors.red)),
Expanded(flex: 1, child: Container(color: Colors.blue)),
],
)
// 红色占 2/3,蓝色占 1/3
Stack 层叠
Stack(
children: [
Container(width: 200, height: 200, color: Colors.grey),
Positioned(
top: 10, right: 10,
child: Icon(Icons.favorite, color: Colors.red),
),
],
)Flutter Container
Flutter Container
Container 是最常用的容器 Widget,集装饰、约束、对齐于一体。
常用属性
Container(
width: 200,
height: 100,
padding: const EdgeInsets.all(16), // 内边距
margin: const EdgeInsets.symmetric(horizontal: 20), // 外边距
alignment: Alignment.center, // 子组件对齐
decoration: BoxDecoration( // 装饰
color: Colors.blue,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(color: Colors.black26, blurRadius: 8),
],
),
child: const Text('内容'),
)
EdgeInsets 用法
EdgeInsets.all(8) // 四周 8
EdgeInsets.only(left: 10) // 仅左边
EdgeInsets.symmetric(horizontal: 16, vertical: 8) // 水平16垂直8
EdgeInsets.fromLTRB(1, 2, 3, 4) // 上右下左
BoxDecoration 常用
BoxDecoration(
color: Colors.white,
gradient: LinearGradient( // 渐变
colors: [Colors.blue, Colors.purple],
),
border: Border.all(color: Colors.grey, width: 1),
borderRadius: BorderRadius.circular(8),
image: DecorationImage(image: NetworkImage(url)),
)
替代方案
纯约束用 SizedBox,纯间距用 Padding,避免 Container 嵌套过深影响性能。
Flutter ListView
Flutter ListView
ListView 是滚动列表组件,移动应用最常用的布局之一。
简单列表
ListView(
children: [
ListTile(title: Text('项目 1')),
ListTile(title: Text('项目 2')),
ListTile(
leading: Icon(Icons.star),
title: Text('带图标'),
trailing: Icon(Icons.chevron_right),
onTap: () { /* 点击事件 */ },
),
],
)
大量数据:ListView.builder
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
onTap: () => print('点击 $index'),
);
},
)
// 惰性构建,只渲染可见项,适合长列表
分隔列表
ListView.separated(
itemCount: 10,
separatorBuilder: (_, __) => const Divider(),
itemBuilder: (context, index) => ListTile(title: Text('第 $index 项')),
)
横向列表
SizedBox(
height: 120,
child: ListView(
scrollDirection: Axis.horizontal, // 横向滚动
children: [/* ... */],
),
)
刷新与加载更多
RefreshIndicator(
onRefresh: () async { await loadData(); },
child: ListView(...),
)Flutter 用户输入
Flutter 用户输入
TextField 是用户文本输入的核心组件,配合控制器管理输入内容。
基础文本框
TextField(
controller: _controller, // 控制器
decoration: InputDecoration(
labelText: '用户名',
hintText: '请输入用户名',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
)
控制器与监听
final _controller = TextEditingController();
@override
void initState() {
super.initState();
// 监听输入变化
_controller.addListener(() {
print('当前输入: ${_controller.text}');
});
}
@override
void dispose() {
_controller.dispose(); // 必须释放
super.dispose();
}
密码与键盘类型
TextField(
obscureText: true, // 密码模式
keyboardType: TextInputType.emailAddress, // 邮箱键盘
textInputAction: TextInputAction.done,
onSubmitted: (value) => login(value), // 回车回调
)
表单验证(Form)
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return '不能为空';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// 验证通过
}
},
child: const Text('提交'),
),
],
),
)Flutter 状态管理
Flutter 状态管理
状态管理决定数据如何在组件间共享和更新,是 Flutter 应用架构的核心。
状态管理方案对比
| 方案 | 特点 | 适用 |
|---|---|---|
| setState | 最简单,局部状态 | 单组件状态 |
| InheritedWidget | 官方基础,自建框架 | 跨层共享 |
| Provider | 官方推荐,轻量好用 | 中小型应用 |
| Riverpod | Provider 进阶,编译安全 | 中大型应用 |
| Bloc | 事件驱动,结构清晰 | 大型复杂应用 |
| GetX | 轻量全能,入门快 | 快速开发 |
Provider 基本用法
// 1. 定义模型
class Counter extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
// 2. 顶层提供
runApp(
ChangeNotifierProvider(
create: (_) => Counter(),
child: const MyApp(),
),
);
// 3. 组件消费
final counter = context.watch<Counter>(); // 监听变化
Text('${counter.count}')
context.read<Counter>().increment(); // 触发操作
选择建议
初学者从 Provider 开始,业务复杂后迁移 Riverpod 或 Bloc。
Flutter HTTP 请求
Flutter HTTP 请求
Flutter 通过 http 或 dio 包发送网络请求,与后端 API 交互。
添加依赖
flutter pub add http
GET 请求
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<Map<String, dynamic>> fetchPost() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
);
if (response.statusCode == 200) {
return jsonDecode(response.body) as Map<String, dynamic>;
} else {
throw Exception('请求失败: ${response.statusCode}');
}
}
POST 请求
Future<void> createPost() async {
final response = await http.post(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'title': 'Flutter 教程',
'body': '正文内容',
}),
);
}
FutureBuilder 加载数据
FutureBuilder<Map>(
future: fetchPost(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const CircularProgressIndicator(); // 加载中
}
if (snapshot.hasError) {
return Text('错误: ${snapshot.error}'); // 错误
}
return Text(snapshot.data!['title']); // 成功
},
)
dio 进阶
flutter pub add dio
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 10),
));
// 拦截器、上传下载进度等高级功能Flutter 导航与路由
Flutter 导航与路由
路由(Route)管理页面跳转,Flutter 提供 Navigator 和命名路由。
基本跳转
// 跳转到新页面
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DetailPage(id: 1)),
);
// 返回上一页
Navigator.pop(context);
// 返回并传值
Navigator.pop(context, '结果数据');
接收返回值
final result = await Navigator.push<String>(
context,
MaterialPageRoute(builder: (context) => const ChoosePage()),
);
print('选择结果: $result');
命名路由
// 在 MaterialApp 中定义
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => const HomePage(),
'/detail': (context) => const DetailPage(),
},
);
// 跳转
Navigator.pushNamed(context, '/detail', arguments: 42);
// 接收参数
final args = ModalRoute.of(context)!.settings.arguments;
页面替换与清栈
Navigator.pushReplacement(...); // 替换当前页
Navigator.pushAndRemoveUntil(...); // 跳到新页并移除旧页(登录后常见)
go_router(推荐)
flutter pub add go_router
// 声明式路由,支持深链接、嵌套导航Flutter 表单与验证
Flutter 表单与验证
Form + TextFormField 提供完整的表单验证能力,是登录注册页面的核心。
完整示例
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _email = TextEditingController();
final _password = TextEditingController();
bool _loading = false;
Future<void> _submit() async {
if (_formKey.currentState!.validate()) {
setState(() => _loading = true);
// 调用登录 API
await login(_email.text, _password.text);
setState(() => _loading = false);
}
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _email,
decoration: const InputDecoration(labelText: '邮箱'),
keyboardType: TextInputType.emailAddress,
validator: (v) =>
!(v != null && v.contains('@')) ? '邮箱格式错误' : null,
),
TextFormField(
controller: _password,
obscureText: true,
decoration: const InputDecoration(labelText: '密码'),
validator: (v) =>
(v == null || v.length < 6) ? '密码至少6位' : null,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _loading ? null : _submit,
child: _loading
? const CircularProgressIndicator()
: const Text('登录'),
),
],
),
);
}
}
常用验证器
// 非空、邮箱、长度、自定义正则
validator: (v) => v!.isEmpty ? '必填' : null
表单重置
_formKey.currentState!.reset();Flutter 主题与样式
Flutter 主题与样式
通过 ThemeData 统一管理颜色、字体、组件样式,实现全局一致的设计。
定义主题
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
useMaterial3: true, // Material 3 风格
appBarTheme: const AppBarTheme(
centerTitle: true,
backgroundColor: Colors.teal,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
),
),
),
darkTheme: ThemeData.dark(), // 深色主题
themeMode: ThemeMode.system, // 跟随系统
)
使用主题
// 获取主题样式
Theme.of(context).colorScheme.primary
Theme.of(context).textTheme.titleLarge
Theme.of(context).textTheme.bodyMedium
Text(
'标题',
style: Theme.of(context).textTheme.headlineSmall,
)
局部主题覆盖
Text(
'特殊样式',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 20,
),
)
深色模式适配
final isDark = Theme.of(context).brightness == Brightness.dark;
// 根据 isDark 选择不同颜色/资源Flutter 动画基础
Flutter 动画基础
Flutter 动画分为隐式动画(简单)和显式动画(复杂可控)。
隐式动画
class FadeDemo extends StatefulWidget {
const FadeDemo({super.key});
@override
State<FadeDemo> createState() => _FadeDemoState();
}
class _FadeDemoState extends State<FadeDemo> {
bool _visible = true;
@override
Widget build(BuildContext context) {
return Column(
children: [
AnimatedOpacity( // 透明度动画
opacity: _visible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 500),
child: const Text('淡入淡出'),
),
AnimatedContainer( // 容器动画
duration: const Duration(seconds: 1),
width: _visible ? 200 : 100,
height: _visible ? 200 : 100,
color: _visible ? Colors.blue : Colors.red,
),
ElevatedButton(
onPressed: () => setState(() => _visible = !_visible),
child: const Text('切换'),
),
],
);
}
}
显式动画(AnimationController)
class FadeIn extends StatefulWidget {
const FadeIn({super.key});
@override
State<FadeIn> createState() => _FadeInState();
}
class _FadeInState extends State<FadeIn>
with SingleTickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);
@override
void initState() {
super.initState();
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _controller,
child: const Text('入场动画'),
);
}
}
常用动画组件
AnimatedOpacity / AnimatedContainer / AnimatedSwitcher
Hero(页面转场) / TweenAnimationBuilder / LottieFlutter 本地存储
Flutter 本地存储
Flutter 常用 shared_preferences(键值)和 sqflite(SQLite 数据库)做本地存储。
shared_preferences(简单键值)
flutter pub add shared_preferences
// 写入
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', 'laowang');
await prefs.setInt('score', 100);
await prefs.setBool('isLogin', true);
// 读取
final name = prefs.getString('username');
final score = prefs.getInt('score') ?? 0;
// 删除
await prefs.remove('username');
sqflite(SQLite 数据库)
flutter pub add sqflite path
// 打开数据库
final db = await openDatabase(
'my_app.db',
version: 1,
onCreate: (db, version) async {
await db.execute("""
CREATE TABLE users(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
age INTEGER
)
");
},
);
// 增删改查
await db.insert('users', {'name': 'Tom', 'age': 20});
final users = await db.query('users');
await db.update('users', {'age': 21}, where: 'name = ?', whereArgs: ['Tom']);
await db.delete('users', where: 'name = ?', whereArgs: ['Tom']);
其他方案
hive 高性能 NoSQL 本地库
isar Hive 作者新作,性能更强
path_provider 获取应用目录路径Flutter 图片与资源
Flutter 图片与资源
Flutter 支持本地资源图片、网络图片和内存图片等多种来源。
本地资源图片
# pubspec.yaml 声明资源
flutter:
assets:
- assets/images/logo.png
- assets/images/ # 整个目录
// 使用
Image.asset('assets/images/logo.png')
网络图片
Image.network(
'https://example.com/image.jpg',
loadingBuilder: (context, child, progress) {
return progress == null
? child
: const CircularProgressIndicator();
},
errorBuilder: (context, error, stack) {
return const Icon(Icons.broken_image); // 加载失败占位
},
)
缓存优化(cached_network_image)
flutter pub add cached_network_image
CachedNetworkImage(
imageUrl: 'https://example.com/image.jpg',
placeholder: (context, url) => const CircularProgressIndicator(),
errorWidget: (context, url, error) => const Icon(Icons.error),
)
图片填充模式
Image.asset(
'assets/images/bg.png',
fit: BoxFit.cover, // 覆盖填满(裁剪)
fit: BoxFit.contain, // 完整显示(留白)
fit: BoxFit.fill, // 拉伸填满(变形)
)
圆形头像
CircleAvatar(
radius: 40,
backgroundImage: NetworkImage(url),
)Flutter DevTools
Flutter DevTools
DevTools 是 Flutter 的性能调试工具套件,浏览器中运行,功能强大。
启动 DevTools
# 应用运行时,VS Code 按 F5 调试,或:
flutter run # 然后按 w 打开 DevTools
核心功能
- Widget Inspector:可视化查看 Widget 树,检查布局
- Performance:性能分析,帧率、耗时
- Memory:内存使用监控,发现泄漏
- Network:网络请求查看(需 Dart VM 服务)
- Debugger:断点调试
- Logging:日志查看
Widget Inspector 技巧
# 选择模式:点击界面元素查看对应 Widget
# 检查布局约束,快速定位溢出问题
性能分析
# 打开 Performance 页,录制并重放
# 关注:build 耗时、Raster 线程
# 常见问题:重建过多、图片过大
命令行模式
dart devtools
日志与调试输出
debugPrint('调试信息'); // 比 print 更友好
FlutterError.onError // 全局错误处理Flutter 平台特定代码
Flutter 平台特定代码
通过平台通道(Platform Channel),Flutter 可以调用 Android/iOS 原生功能。
MethodChannel 原理
Dart 代码 ⇄ MethodChannel ⇄ 原生代码(Java/Kotlin/Swift)
Dart 侧调用
import 'package:flutter/services.dart';
static const platform = MethodChannel('com.example/app');
Future<String> getBatteryLevel() async {
try {
final result = await platform.invokeMethod('getBatteryLevel');
return result as String;
} on PlatformException catch (e) {
return '获取失败: ${e.message}';
}
}
Android 原生侧(Kotlin)
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger,
"com.example/app").setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
val battery = getBattery()
if (battery != null) result.success(battery)
else result.error("UNAVAILABLE", "无法获取", null)
} else {
result.notImplemented()
}
}
}
}
iOS 原生侧(Swift)
override func viewDidLoad() {
super.viewDidLoad()
let channel = FlutterMethodChannel(
name: "com.example/app",
binaryMessenger: binaryMessenger)
channel.setMethodCallHandler { call, result in
if call.method == "getBatteryLevel" {
result.success(getBatteryLevel())
} else {
result.notImplemented()
}
}
}
常用原生插件
url_launcher 打开网页/电话
permission_handler 权限管理
camera / image_picker 相机相册
flutter_local_notifications 本地通知
geolocator 定位Flutter 测试
Flutter 测试
Flutter 支持单元测试、Widget 测试和集成测试三层测试体系。
单元测试
// test/calculator_test.dart
import 'package:flutter_test/flutter_test.dart';
int add(int a, int b) => a + b;
void main() {
test('加法测试', () {
expect(add(1, 2), 3);
expect(add(-1, 1), 0);
});
group('计算器', () {
test('加法', () => expect(add(2, 3), 5));
test('减法', () => expect(add(5, -2), 3));
});
}
Widget 测试
// test/widget_test.dart
testWidgets('计数器显示正确', (tester) async {
await tester.pumpWidget(const MyApp());
expect(find.text('0'), findsOneWidget);
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('1'), findsOneWidget);
});
运行测试
flutter test # 全部测试
flutter test test/widget_test.dart # 指定文件
flutter test --coverage # 覆盖率
集成测试(integration_test)
flutter test integration_test -d emulator-5554
测试最佳实践
- 纯逻辑(工具函数、模型)写单元测试
- 页面交互写 Widget 测试
- 关键流程(登录、下单)写集成测试
- 保持测试独立,避免依赖顺序
Flutter 打包与发布
Flutter 打包与发布
将 Flutter 应用打包成 APK/IPA 并发布到应用商店。
Android 打包
# 生成签名密钥
keytool -genkey -v -keystore upload.jks -keyalg RSA \
-keysize 2048 -validity 10000 -alias upload
# 配置 key.properties(android/key.properties)
storePassword=xxx
keyPassword=xxx
keyAlias=upload
storeFile=../upload.jks
# 构建 APK
flutter build apk # debug 验证
flutter build apk --release # 正式包
flutter build appbundle # AAB(Google Play 推荐)
iOS 打包
flutter build ios --release
# 用 Xcode 打开 ios/ 目录,配置签名
# Product → Archive → 上传 App Store
版本号管理
# pubspec.yaml
version: 1.2.3+45 # 1.2.3 版本号, 45 构建号
应用图标与名称
# Android: android/app/src/main/res/ 下的 mipmap 目录
# iOS: ios/Runner/Assets.xcassets/AppIcon.appiconset
# 可用 flutter_launcher_icons 一键生成
flutter pub add --dev flutter_launcher_icons
发布前检查清单
- 移除 debug 打印
- 检查权限是否合理
- 混淆与加固(Android)
- 隐私政策
- 真机全流程回归测试
Flutter 最佳实践
Flutter 最佳实践
总结 Flutter 开发中的架构与工程最佳实践,让项目健壮可维护。
项目架构
lib/
├── main.dart # 入口
├── models/ # 数据模型
├── services/ # API/网络层
├── providers/ # 状态管理
├── pages/ # 页面
├── widgets/ # 通用组件
├── utils/ # 工具函数
└── theme/ # 主题配置
代码规范
flutter analyze # 静态分析
flutter format . # 自动格式化
# 遵循 Effective Dart 风格指南
性能优化
- 长列表用 ListView.builder(惰性构建)
- 避免不必要的 setState 重建
- const 构造函数减少重建
- 图片加缓存与压缩(cached_network_image)
- 避免在 build 中做耗时操作
错误处理
try {
final data = await api.fetch();
} on TimeoutException {
// 超时处理
} on ApiException catch (e) {
// 业务错误
} catch (e) {
// 兜底
}
安全
- 密钥放后端或安全存储(flutter_secure_storage)
- 敏感信息不进 Git
- 网络请求校验证书
版本管理
git init
git add .
git commit -m "初始提交"
# 配合 CI:GitHub Actions 自动构建测试Firebase 集成
Firebase 集成
Firebase 是 Google 的 BaaS 平台,提供认证、数据库、推送、分析等能力,与 Flutter 集成方便。
开通与配置
# 1. 在 Firebase 控制台创建项目
# 2. 添加 Android/iOS 应用并下载配置文件
# 3. 安装 FlutterFire CLI
dart pub global activate flutterfire_cli
flutterfire configure
常用 Firebase 服务
flutter pub add firebase_core
flutter pub add firebase_auth # 认证
flutter pub add cloud_firestore # NoSQL 数据库
flutter pub add firebase_storage # 文件存储
flutter pub add firebase_messaging # 推送通知
flutter pub add firebase_analytics # 分析
初始化
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
邮箱密码认证
final auth = FirebaseAuth.instance;
// 注册
await auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
// 登录
await auth.signInWithEmailAndPassword(
email: email,
password: password,
);
// 监听登录状态
auth.authStateChanges().listen((user) {
// user != null 表示已登录
});
Firestore 读写
// 写入
await FirebaseFirestore.instance
.collection('users')
.doc('laowang')
.set({'name': '老王', 'age': 30});
// 读取(实时)
FirebaseFirestore.instance
.collection('posts')
.snapshots()
.listen((snapshot) {
// 数据变化自动更新
});
注意
国内网络访问 Firebase 可能不稳定,生产环境需评估合规与替代方案(自建后端)。
Flutter 进阶
Flutter 进阶
本教程最后一章,介绍 Flutter 进阶方向与成长路径。
进阶主题
- 渲染原理:深入 Widget/Element/RenderObject 三层
- 自定义绘制:CustomPainter 绘制图形、图表
- 手势系统:GestureDetector、缩放、拖拽
- 性能剖析:Profile 模式、帧耗时分析
- 隔离区(Isolate):多线程处理耗时任务
- 国际化(i18n):多语言支持
- 无障碍:语义、屏幕阅读器支持
Isolate 并发示例
import 'dart:isolate';
// 耗时计算放到后台隔离区
final result = await compute(heavyTask, data);
int heavyTask(int n) {
var sum = 0;
for (var i = 0; i < n; i++) {
sum += i;
}
return sum;
}
自定义绘制
class CirclePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
size.width / 2,
paint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
// 使用
CustomPaint(size: const Size(100, 100), painter: CirclePainter())
学习路线
- 掌握 Widget 体系与状态管理(Provider)
- 实战 2-3 个完整 App
- 深入渲染与性能优化
- 学习 Riverpod/Bloc 大型架构
- 关注 Flutter 新版本特性(Impeller、Wasm)
社区资源
- 官方文档:docs.flutter.dev
- 中文社区:Flutter 中文开发者社区
- 开源项目:GitHub 搜 awesome-flutter