📦 第一章:环境搭建
1.1 安装 Flutter SDK
Flutter SDK 是开发 Flutter 应用的核心工具包,包含 Dart SDK、Flutter 框架和各种命令行工具。
🍎 macOS
- 下载 Flutter SDK 压缩包
- 解压到
~/development/flutter - 添加到 PATH 环境变量
- 安装 Xcode 和 CocoaPods
🪟 Windows
- 下载 Flutter SDK zip 包
- 解压到
C:\flutter - 添加到系统环境变量 PATH
- 安装 Android Studio
🐧 Linux
- 下载 Flutter SDK tar 包
- 解压到
~/development/flutter - 在
.bashrc中添加 PATH - 安装依赖库和 Android Studio
bash # macOS / Linux 安装步骤 cd ~/development git clone https://github.com/flutter/flutter.git -b stable # 添加到 PATH(在 ~/.zshrc 或 ~/.bashrc 中) export PATH="$HOME/development/flutter/bin:$PATH" # 使配置生效 source ~/.zshrc # 验证安装 flutter --version # Flutter 3.x.x • channel stable # Dart SDK version: 3.x.x
1.2 使用 FVM 版本管理器(推荐)
FVM(Flutter Version Management)允许你在不同项目中使用不同版本的 Flutter SDK,非常适合团队协作和维护多个项目。
bash # 安装 FVM dart pub global activate fvm # 安装指定版本的 Flutter fvm install 3.22.0 # 在项目中使用指定版本 fvm use 3.22.0 # 查看已安装的版本列表 fvm list # 使用 fvm 运行 Flutter 命令 fvm flutter run fvm flutter build apk
.gitignore 中添加 .fvm/flutter_sdk,并在 VS Code 的 settings.json 中配置 "dart.flutterSdkPath": ".fvm/flutter_sdk"。
1.3 flutter doctor 检查环境
flutter doctor 会检查你的开发环境是否完整,并给出修复建议。
bash flutter doctor # 输出示例: # [✓] Flutter (Channel stable, 3.22.0) # Flutter SDK 版本和渠道信息 # [✓] Android toolchain - develop for Android devices # Android SDK 路径、版本、构建工具 # [✓] Xcode - develop for iOS and macOS # Xcode 版本、CocoaPods 状态 # [✓] Chrome - develop for the web # Chrome 浏览器路径 # [✓] Android Studio # Android Studio 版本、Flutter/Dart 插件 # [✓] VS Code # VS Code 版本、Flutter 扩展 # [✓] Connected device # 已连接的设备列表 # 查看更详细的信息 flutter doctor -v
1.4 IDE 设置
VS Code(推荐)
- Flutter 扩展(包含 Dart)
- Dart 扩展(语法高亮、补全)
- Awesome Flutter Snippets 代码片段
- Flutter Widget Snippets
- Error Lens 行内错误提示
Android Studio
- Flutter 插件
- Dart 插件
- 内置模拟器管理器
- 性能分析工具
- 布局检查器
1.5 模拟器 / 真机设置
开发时需要至少一个运行设备,可以使用模拟器或连接真机。
bash # iOS 模拟器(仅 macOS) open -a Simulator # Android 模拟器 —— 通过 Android Studio 的 AVD Manager 创建 # 或使用命令行: flutter emulators flutter emulators --launch <emulator_id> # 查看已连接的设备 flutter devices # 在指定设备上运行 flutter run -d <device_id> flutter run -d chrome # 在 Chrome 中运行 flutter run -d macos # 作为 macOS 桌面应用运行
1.6 创建并运行第一个项目
bash # 创建新项目 flutter create my_first_app # 进入项目目录 cd my_first_app # 运行项目 flutter run # 创建项目时指定组织名(影响包名) flutter create --org com.example my_app # 指定平台 flutter create --platforms=android,ios,web my_app
1.7 Hot Reload vs Hot Restart
| 特性 | Hot Reload(热重载) | Hot Restart(热重启) |
|---|---|---|
| 快捷键 | r 或保存文件 |
R |
| 速度 | 亚秒级(通常 < 1s) | 数秒(需重新执行 main) |
| 状态保留 | 保留应用状态(State 不丢失) | 重置所有状态 |
| 原理 | 注入更新后的代码到 Dart VM | 销毁并重建整个 Widget 树 |
| 适用场景 | 修改 UI、调整样式 | 修改 initState、全局变量、main() |
| 限制 | 不能处理静态字段、枚举变更 | 无限制,但更慢 |
main() 函数或全局状态),需要使用 Hot Restart。如果连 Hot Restart 也不行,就需要完全重新运行(flutter run)。
📂 第二章:项目结构详解
2.1 项目目录结构
一个标准的 Flutter 项目包含以下目录和文件:
项目结构 my_flutter_app/ ├── lib/ // 主要的 Dart 源代码目录 │ └── main.dart // 应用入口文件,包含 main() 函数 ├── test/ // 单元测试和 Widget 测试 │ └── widget_test.dart // 默认的 Widget 测试文件 ├── android/ // Android 平台原生代码和配置 │ ├── app/ │ │ ├── build.gradle // 应用级别的 Gradle 配置 │ │ └── src/main/ │ │ └── AndroidManifest.xml │ └── build.gradle // 项目级别的 Gradle 配置 ├── ios/ // iOS 平台原生代码和配置 │ ├── Runner/ │ │ ├── Info.plist // iOS 应用配置 │ │ └── AppDelegate.swift │ └── Runner.xcworkspace ├── web/ // Web 平台文件 │ └── index.html ├── build/ // 构建输出目录(自动生成,不要提交到 Git) ├── .dart_tool/ // Dart 工具缓存(自动生成) ├── .packages // 包路径映射(已弃用,由 package_config.json 替代) ├── pubspec.yaml // 项目配置文件(依赖、资源、字体等) ├── pubspec.lock // 锁定的依赖版本 ├── analysis_options.yaml // Dart 分析器配置 └── README.md // 项目说明文档
2.2 pubspec.yaml 详解
pubspec.yaml 是 Flutter 项目的核心配置文件,用于声明项目信息、依赖和资源。
pubspec.yaml name: my_flutter_app # 项目名称(必须为小写加下划线) description: 一个全新的 Flutter 项目 # 项目描述 version: 1.0.0+1 # 版本号 + 构建号 publish_to: 'none' # 不发布到 pub.dev # Dart SDK 版本约束 environment: sdk: '>=3.0.0 <4.0.0' # 项目依赖(运行时需要) dependencies: flutter: sdk: flutter http: ^1.1.0 # ^表示兼容版本(>=1.1.0 <2.0.0) provider: ^6.0.5 # 状态管理 shared_preferences: ^2.2.0 # 本地存储 intl: any # any 表示任意版本(不推荐) my_package: # Git 依赖 git: url: https://github.com/user/repo.git ref: main # 开发依赖(仅开发和测试时需要) dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.0 build_runner: ^2.4.0 mockito: ^5.4.0 # Flutter 特有配置 flutter: uses-material-design: true # 启用 Material Design 图标 # 静态资源声明 assets: - assets/images/ # 整个目录 - assets/data/config.json # 单个文件 # 自定义字体 fonts: - family: CustomFont fonts: - asset: assets/fonts/CustomFont-Regular.ttf - asset: assets/fonts/CustomFont-Bold.ttf weight: 700
2.3 包管理命令
bash # 获取依赖(根据 pubspec.yaml 下载包) flutter pub get # 升级依赖到最新兼容版本 flutter pub upgrade # 升级指定包 flutter pub upgrade http # 查看可升级的包 flutter pub outdated # 添加依赖 flutter pub add provider flutter pub add --dev build_runner # 添加到 dev_dependencies # 移除依赖 flutter pub remove provider
版本约束语法
| 语法 | 含义 | 示例 |
|---|---|---|
^1.2.3 |
兼容版本,等价于 >=1.2.3 <2.0.0 |
最常用的约束方式 |
>=1.0.0 <2.0.0 |
明确的版本范围 | 精确控制版本 |
any |
任意版本 | 不推荐,可能导致兼容问题 |
1.2.3 |
锁定精确版本 | 特殊场景下使用 |
2.4 pub.dev 使用
pub.dev 是 Dart 和 Flutter 的官方包仓库。选择包时请关注以下指标:
- Likes — 社区喜爱程度
- Pub Points — 代码质量评分(满分 160)
- Popularity — 使用广泛程度
- Flutter Favorite — Flutter 官方推荐标识
- 平台兼容性 — 支持 Android、iOS、Web、Desktop 等
🧩 第三章:Widget 核心概念
3.1 一切皆 Widget
在 Flutter 中,一切都是 Widget。按钮是 Widget,文本是 Widget,间距是 Widget,甚至整个应用也是一个 Widget。Widget 是 Flutter UI 的基本构建块,它描述了在给定配置和状态下视图应该长什么样。
3.2 三棵树详解
Flutter 框架内部维护三棵关键的树结构,理解它们对于掌握 Flutter 的渲染机制至关重要:
🌳 Widget Tree
配置信息树。由开发者编写的 Widget 代码构成,描述 UI 的结构和配置。Widget 是不可变的、轻量的,频繁重建开销很小。
🌲 Element Tree
生命周期管理树。Element 是 Widget 的实例化对象,管理 Widget 和 RenderObject 之间的关联。Element 是可变的,会被尽量复用。
🎄 RenderObject Tree
布局和绘制树。负责实际的布局计算(大小、位置)和绘制(将像素画到屏幕上)。这是最"重"的树,创建代价高。
渲染流程:Widget(描述配置)→ Element(管理生命周期,diff 算法比较)→ RenderObject(布局、绘制)→ 屏幕像素
渲染流程示意 // Widget 树 → Element 树 → RenderObject 树 // // MaterialApp MaterialApp Element (无 RenderObject) // └─ Scaffold └─ Scaffold Element RenderFlex // └─ Center └─ Center Element RenderPositionedBox // └─ Text └─ Text Element RenderParagraph // // 当 Widget 重建时: // 1. 新 Widget 与旧 Widget 比较(canUpdate: runtimeType + key) // 2. 如果可以更新 → 复用 Element,更新 RenderObject // 3. 如果不能更新 → 卸载旧 Element,创建新 Element 和 RenderObject
3.3 StatelessWidget — 无状态组件
StatelessWidget 用于不需要维护内部状态的 UI 组件。一旦创建,其显示内容完全由传入的参数决定。
Dart import 'package:flutter/material.dart'; // 自定义的无状态组件 —— 用户信息卡片 class UserCard extends StatelessWidget { // 通过构造函数接收数据(不可变) final String name; final String email; final String avatarUrl; // 使用 const 构造函数提升性能 const UserCard({ super.key, required this.name, required this.email, required this.avatarUrl, }); // build 方法 —— 描述该组件的 UI @override Widget build(BuildContext context) { return Card( elevation: 2, child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ // 头像 CircleAvatar( radius: 30, backgroundImage: NetworkImage(avatarUrl), ), const SizedBox(width: 16), // 用户信息 Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( name, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), Text( email, style: TextStyle(color: Colors.grey[600]), ), ], ), ], ), ), ); } } // 使用方式 const UserCard( name: '张三', email: 'zhangsan@example.com', avatarUrl: 'https://example.com/avatar.png', )
3.4 StatefulWidget — 有状态组件
StatefulWidget 用于需要维护和管理内部可变状态的组件。它由两个类组成:Widget 类(不可变)和 State 类(可变)。
Dart import 'package:flutter/material.dart'; // 有状态组件 —— 点赞按钮 class LikeButton extends StatefulWidget { final int initialCount; const LikeButton({super.key, this.initialCount = 0}); @override State<LikeButton> createState() => _LikeButtonState(); } class _LikeButtonState extends State<LikeButton> { // 可变状态 late int _count; bool _isLiked = false; @override void initState() { super.initState(); // 从 Widget 属性初始化状态 _count = widget.initialCount; } void _toggleLike() { // setState 通知框架状态已改变,需要重建 UI setState(() { _isLiked = !_isLiked; _count += _isLiked ? 1 : -1; }); } @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: Icon( _isLiked ? Icons.favorite : Icons.favorite_border, color: _isLiked ? Colors.red : Colors.grey, ), onPressed: _toggleLike, ), Text('$_count'), ], ); } }
3.5 Widget 生命周期
StatefulWidget 有完整的生命周期,理解每个阶段对于正确管理资源非常重要:
| 方法 | 调用时机 | 典型用途 |
|---|---|---|
createState() |
Widget 第一次插入树时 | 创建 State 对象 |
initState() |
State 对象创建后,仅调用一次 | 初始化数据、订阅、控制器 |
didChangeDependencies() |
依赖(如 InheritedWidget)变化时 | 获取 Theme、MediaQuery 等 |
build() |
每次需要渲染 UI 时 | 构建 Widget 树(必须是纯函数) |
didUpdateWidget() |
父 Widget 重建,传入新配置时 | 对比新旧 Widget,更新状态 |
deactivate() |
State 从树中移除时 | 清理与树相关的引用 |
dispose() |
State 永久销毁时 | 释放资源:controller、subscription |
生命周期流程:
生命周期 // 创建阶段: // 构造函数 → createState() → initState() → didChangeDependencies() → build() // // 更新阶段(setState 或父 Widget 重建): // didUpdateWidget() → build() // setState() → build() // // 依赖变化(InheritedWidget 更新): // didChangeDependencies() → build() // // 销毁阶段: // deactivate() → dispose()
3.6 完整生命周期代码示例
Dart import 'package:flutter/material.dart'; class LifecycleDemo extends StatefulWidget { final String title; const LifecycleDemo({super.key, required this.title}); @override State<LifecycleDemo> createState() { print('1. createState —— 创建 State 对象'); return _LifecycleDemoState(); } } class _LifecycleDemoState extends State<LifecycleDemo> { late int _counter; @override void initState() { super.initState(); _counter = 0; print('2. initState —— 初始化状态,仅调用一次'); // 适合在此处:初始化控制器、订阅流、发起网络请求 } @override void didChangeDependencies() { super.didChangeDependencies(); print('3. didChangeDependencies —— 依赖变化'); // 可以安全使用 context 获取 InheritedWidget // 例如:Theme.of(context), MediaQuery.of(context) } @override Widget build(BuildContext context) { print('4. build —— 构建 UI'); return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('${widget.title}: $_counter'), ElevatedButton( onPressed: () { setState(() { _counter++; print('setState —— 触发重建'); }); }, child: const Text('增加'), ), ], ); } @override void didUpdateWidget(LifecycleDemo oldWidget) { super.didUpdateWidget(oldWidget); print('5. didUpdateWidget —— 父 Widget 传入新配置'); if (oldWidget.title != widget.title) { print(' title 从 "${oldWidget.title}" 变为 "${widget.title}"'); } } @override void deactivate() { print('6. deactivate —— 从 Widget 树中暂时移除'); super.deactivate(); } @override void dispose() { print('7. dispose —— 永久销毁,释放资源'); // 在此处释放:controller.dispose(), subscription.cancel() super.dispose(); } }
🔍 第四章:BuildContext
4.1 什么是 BuildContext
BuildContext 是 Widget 在 Widget 树中位置的一个引用句柄。实际上,每个 BuildContext 就是对应 Element 树中的一个 Element 对象。它用于向上查找祖先 Widget 提供的数据和服务。
Dart // BuildContext 本质上就是 Element // abstract class Element implements BuildContext { ... } // 在 build 方法中,context 代表当前 Widget 在树中的位置 @override Widget build(BuildContext context) { // context 可以向上查找祖先提供的数据 return Container(); }
4.2 BuildContext 与 Element 树的关系
每个 Widget 的 build 方法接收的 context 参数,代表的是该 Widget 在 Element 树中对应 Element 的引用。通过这个引用,可以沿树向上查找祖先节点提供的信息。
4.3 使用 context 查找祖先数据
Dart @override Widget build(BuildContext context) { // 获取当前主题数据 final theme = Theme.of(context); final primaryColor = theme.colorScheme.primary; // 获取屏幕尺寸信息 final mediaQuery = MediaQuery.of(context); final screenWidth = mediaQuery.size.width; final isLandscape = mediaQuery.orientation == Orientation.landscape; // 获取 Navigator(用于页面导航) Navigator.of(context).push(...); // 获取 Scaffold 的状态(如打开 Drawer) Scaffold.of(context).openDrawer(); // 查找最近的指定类型祖先 Widget final scaffold = context.findAncestorWidgetOfExactType<Scaffold>(); // 查找最近的指定类型祖先 State final state = context.findAncestorStateOfType<ScaffoldState>(); return Text('屏幕宽度: $screenWidth'); }
4.4 Context 与 Widget 重建
当使用 of(context) 方法查找 InheritedWidget 时,当前 Widget 会自动注册为该 InheritedWidget 的依赖者。当 InheritedWidget 数据变化时,所有依赖它的 Widget 都会被重建。
Dart // 这会注册依赖关系 —— Theme 变化时此 Widget 会重建 final theme = Theme.of(context); // 如果只想读取一次,不注册依赖(不会自动重建) // 适合在 didChangeDependencies 中使用 final route = ModalRoute.of(context);
4.5 常见错误
在
Scaffold 的构建方法内部使用 Scaffold.of(context) 会失败,因为 context 指向的 Element 在 Scaffold 的上方,而不是下方。
Dart // ❌ 错误用法 —— context 在 Scaffold 上方 class MyPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: ElevatedButton( onPressed: () { // 这里的 context 是 MyPage 的,在 Scaffold 之上 // Scaffold.of(context) 找不到 Scaffold! Scaffold.of(context).openDrawer(); // 报错! }, child: const Text('打开抽屉'), ), ); } } // ✅ 正确用法 —— 使用 Builder 获取 Scaffold 下方的 context class MyPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( drawer: const Drawer(child: Text('抽屉内容')), body: Builder( builder: (BuildContext innerContext) { // innerContext 在 Scaffold 下方,可以正确找到 return ElevatedButton( onPressed: () { Scaffold.of(innerContext).openDrawer(); // 正确! }, child: const Text('打开抽屉'), ); }, ), ); } }
initState 执行时,Widget 还没有完全挂载到树中,此时通过 context 查找 InheritedWidget 可能不可靠。应该在 didChangeDependencies 中进行。
Dart // ❌ 不推荐 —— initState 中使用 context 获取 InheritedWidget @override void initState() { super.initState(); final theme = Theme.of(context); // 可能有问题 } // ✅ 推荐 —— 在 didChangeDependencies 中使用 @override void didChangeDependencies() { super.didChangeDependencies(); final theme = Theme.of(context); // 安全 }
🔑 第五章:Key 的使用
5.1 为什么 Key 很重要
Flutter 通过比较 Widget 的 runtimeType 和 key 来决定是否复用 Element。在列表重新排序、添加/删除元素等场景中,如果没有正确使用 Key,Flutter 可能会错误地复用 Element,导致状态混乱。
5.2 Key 的类型
| Key 类型 | 说明 | 使用场景 |
|---|---|---|
ValueKey |
基于一个值(如 ID、字符串)生成 Key | 列表项有唯一标识符时 |
ObjectKey |
基于对象的引用地址生成 Key | 用对象本身作为唯一标识 |
UniqueKey |
每次创建都生成唯一的 Key | 强制重建 Widget(不复用) |
GlobalKey |
全局唯一,可跨 Widget 树访问 State | 访问子 Widget 的 State、表单验证 |
5.3 何时使用 Key
Dart // 场景 1:列表重新排序 —— 必须使用 Key ListView( children: todos.map((todo) => TodoTile( key: ValueKey(todo.id), // 使用唯一 ID 作为 Key todo: todo, ), ).toList(), ) // 场景 2:条件渲染不同类型的 Widget —— 使用 ValueKey 区分 AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: isLoggedIn ? UserProfile(key: const ValueKey('profile')) : LoginForm(key: const ValueKey('login')), ) // 场景 3:强制重建(状态重置)—— 使用 UniqueKey MyWidget(key: UniqueKey()) // 每次都生成新 Key,强制重建
5.4 GlobalKey 跨 Widget 访问 State
Dart import 'package:flutter/material.dart'; // 使用 GlobalKey 访问子 Widget 的 State class ParentWidget extends StatefulWidget { const ParentWidget({super.key}); @override State<ParentWidget> createState() => _ParentWidgetState(); } class _ParentWidgetState extends State<ParentWidget> { // 创建 GlobalKey,泛型为子 Widget 的 State 类型 final _counterKey = GlobalKey<_CounterWidgetState>(); // 表单的 GlobalKey final _formKey = GlobalKey<FormState>(); @override Widget build(BuildContext context) { return Column( children: [ // 传递 GlobalKey 给子 Widget CounterWidget(key: _counterKey), ElevatedButton( onPressed: () { // 通过 GlobalKey 访问子 Widget 的 State _counterKey.currentState?.increment(); }, child: const Text('从父 Widget 增加计数'), ), // 表单验证示例 Form( key: _formKey, child: TextFormField( validator: (value) => value?.isEmpty ?? true ? '请输入内容' : null, ), ), ElevatedButton( onPressed: () { // 通过 GlobalKey 验证表单 if (_formKey.currentState!.validate()) { print('表单验证通过'); } }, child: const Text('提交'), ), ], ); } } // 子 Widget —— 带有公开方法的 StatefulWidget class CounterWidget extends StatefulWidget { const CounterWidget({super.key}); @override State<CounterWidget> createState() => _CounterWidgetState(); } class _CounterWidgetState extends State<CounterWidget> { int _count = 0; // 公开方法,可通过 GlobalKey 从外部调用 void increment() { setState(() => _count++); } @override Widget build(BuildContext context) { return Text('计数: $_count', style: const TextStyle(fontSize: 24)); } }
🏗️ 第六章:MaterialApp 与 Scaffold
6.1 MaterialApp 配置
MaterialApp 是 Material Design 应用的顶层 Widget,负责全局配置,包括主题、路由、本地化等。
Dart 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 应用', // 隐藏右上角 DEBUG 标识 debugShowCheckedModeBanner: false, // 全局主题配置 theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.light, ), useMaterial3: true, ), // 深色主题 darkTheme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.blue, brightness: Brightness.dark, ), useMaterial3: true, ), // 跟随系统切换明暗主题 themeMode: ThemeMode.system, // 首页 home: const HomePage(), // 命名路由表 routes: { '/settings': (context) => const SettingsPage(), '/profile': (context) => const ProfilePage(), }, ); } }
6.2 ThemeData 自定义
Dart // Material 3 推荐的主题配置方式 final theme = ThemeData( // 使用种子颜色自动生成配色方案 colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFF027DFD), ), useMaterial3: true, // 自定义文字主题 textTheme: const TextTheme( headlineLarge: TextStyle(fontSize: 28, fontWeight: FontWeight.bold), bodyLarge: TextStyle(fontSize: 16), bodyMedium: TextStyle(fontSize: 14), ), // 自定义 AppBar 主题 appBarTheme: const AppBarTheme( centerTitle: true, elevation: 0, ), // 自定义卡片主题 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), ), ), ), );
6.3 Scaffold 结构
Scaffold 提供了 Material Design 的基本页面布局结构,包括顶栏、底栏、浮动按钮、抽屉等。
Dart class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int _currentIndex = 0; final List<Widget> _pages = [ const Center(child: Text('首页内容')), const Center(child: Text('搜索页面')), const Center(child: Text('个人中心')), ]; @override Widget build(BuildContext context) { return Scaffold( // 顶部导航栏 appBar: AppBar( title: const Text('我的应用'), centerTitle: true, actions: [ IconButton( icon: const Icon(Icons.notifications), onPressed: () {}, ), IconButton( icon: const Icon(Icons.settings), onPressed: () { Navigator.pushNamed(context, '/settings'); }, ), ], ), // 左侧抽屉菜单 drawer: Drawer( child: ListView( padding: EdgeInsets.zero, children: [ const DrawerHeader( decoration: BoxDecoration(color: Colors.blue), child: Text('菜单', style: TextStyle(color: Colors.white, fontSize: 24)), ), ListTile( leading: const Icon(Icons.home), title: const Text('首页'), onTap: () => Navigator.pop(context), ), ListTile( leading: const Icon(Icons.person), title: const Text('个人资料'), onTap: () {}, ), ], ), ), // 页面主体内容 body: _pages[_currentIndex], // 浮动操作按钮 floatingActionButton: FloatingActionButton( onPressed: () { print('点击了 FAB'); }, child: const Icon(Icons.add), ), // 底部导航栏 bottomNavigationBar: NavigationBar( selectedIndex: _currentIndex, onDestinationSelected: (int index) { setState(() => _currentIndex = index); }, destinations: const [ NavigationDestination( icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home), label: '首页', ), NavigationDestination( icon: Icon(Icons.search), selectedIcon: Icon(Icons.search), label: '搜索', ), NavigationDestination( icon: Icon(Icons.person_outline), selectedIcon: Icon(Icons.person), label: '我的', ), ], ), ); } }
📱 第七章:第一个完整 App
7.1 构建一个增强版计数器应用
我们将在默认计数器的基础上,添加自定义样式、多种操作和简单的多页面概念,构建一个完整的学习示例。
第一步:创建项目
bash flutter create enhanced_counter cd enhanced_counter flutter run
第二步:编写完整代码
lib/main.dart import 'package:flutter/material.dart'; void main() { runApp(const EnhancedCounterApp()); } // 应用根组件 class EnhancedCounterApp extends StatelessWidget { const EnhancedCounterApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: '增强计数器', debugShowCheckedModeBanner: false, theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFF6750A4), ), useMaterial3: true, ), home: const CounterHomePage(), routes: { '/history': (context) => const HistoryPage(), }, ); } } // 主页 —— 有状态组件 class CounterHomePage extends StatefulWidget { const CounterHomePage({super.key}); @override State<CounterHomePage> createState() => _CounterHomePageState(); } class _CounterHomePageState extends State<CounterHomePage> { int _counter = 0; final List<String> _history = []; // 操作历史 // 增加计数 void _increment() { setState(() { _counter++; _history.add('增加到 $_counter'); }); } // 减少计数 void _decrement() { setState(() { _counter--; _history.add('减少到 $_counter'); }); } // 重置计数 void _reset() { setState(() { _counter = 0; _history.add('重置为 0'); }); } // 根据计数值返回不同颜色 Color _getCounterColor() { if (_counter > 0) return Colors.green; if (_counter < 0) return Colors.red; return Colors.grey; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('增强计数器'), backgroundColor: Theme.of(context).colorScheme.inversePrimary, actions: [ // 跳转到历史页面 IconButton( icon: const Icon(Icons.history), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => HistoryPage(history: _history), ), ); }, ), ], ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 提示文字 const Text('当前计数值', style: TextStyle(fontSize: 16)), const SizedBox(height: 8), // 计数显示 —— 带动画颜色变化 AnimatedDefaultTextStyle( duration: const Duration(milliseconds: 300), style: TextStyle( fontSize: 72, fontWeight: FontWeight.bold, color: _getCounterColor(), ), child: Text('$_counter'), ), const SizedBox(height: 32), // 操作按钮行 Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // 减少按钮 FilledButton.tonal( onPressed: _decrement, child: const Icon(Icons.remove), ), const SizedBox(width: 16), // 重置按钮 OutlinedButton( onPressed: _reset, child: const Text('重置'), ), const SizedBox(width: 16), // 增加按钮 FilledButton( onPressed: _increment, child: const Icon(Icons.add), ), ], ), ], ), ), // 浮动按钮也可以增加 floatingActionButton: FloatingActionButton.extended( onPressed: _increment, label: const Text('增加'), icon: const Icon(Icons.add), ), ); } } // 历史记录页面 —— 展示多页面跳转 class HistoryPage extends StatelessWidget { final List<String> history; const HistoryPage({super.key, this.history = const []}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('操作历史')), body: history.isEmpty ? const Center(child: Text('暂无操作记录')) : ListView.builder( itemCount: history.length, itemBuilder: (context, index) { // 倒序显示,最新的在最上面 final item = history[history.length - 1 - index]; return ListTile( leading: CircleAvatar(child: Text('${history.length - index}')), title: Text(item), ); }, ), ); } }
7.2 在设备上运行
bash # 查看可用设备 flutter devices # 在指定设备上运行 flutter run -d <device_id> # 构建发布版本 flutter build apk # Android APK flutter build ios # iOS(需要 macOS + Xcode) flutter build web # Web
7.3 使用 DevTools 调试
Flutter DevTools 是一套强大的性能和调试工具套件。
bash # 在应用运行时,打开 DevTools flutter run # 运行后在终端按 D 打开 DevTools # 或者直接启动 dart devtools
Widget Inspector
可视化查看 Widget 树结构,检查每个 Widget 的属性、约束和大小。类似浏览器的 Elements 面板。
Performance
查看帧率图表,识别 UI 卡顿(jank)。帮助发现不必要的重建和耗时操作。
Network
监控网络请求,查看请求和响应详情。调试 API 调用问题。
Logging
查看应用日志输出,包括 print 语句和框架日志。支持过滤和搜索。
p 可以显示性能覆盖层(Performance Overlay),直观查看 UI 线程和 GPU 线程的帧渲染时间。按 i 可以切换 Widget Inspector。
✏️ 第八章:实践练习
练习 1:个人名片 App
创建一个展示个人信息的应用,练习 StatelessWidget 和基本布局。
- 使用
flutter create创建新项目 - 设计一个个人名片界面,包含头像(CircleAvatar)、姓名、职业、联系方式
- 使用
Card、Column、Row、Icon等 Widget 组织布局 - 通过
ThemeData自定义应用主题颜色 - 确保界面在不同屏幕尺寸下合理显示
练习 2:温度转换器
构建一个摄氏度和华氏度互相转换的工具,练习 StatefulWidget 和用户输入。
- 使用
TextField接收用户输入的温度值 - 使用
StatefulWidget管理输入状态和转换结果 - 实现摄氏度 ↔ 华氏度的双向转换(公式:°F = °C × 9/5 + 32)
- 添加一个切换按钮,切换转换方向
- 使用
TextEditingController管理输入框,并在dispose中释放
练习 3:待办清单(带生命周期日志)
构建一个简单的待办事项清单,练习 Key 的使用和生命周期管理。
- 实现添加、删除、标记完成功能
- 使用
ListView.builder展示列表,为每个项目添加ValueKey - 支持拖拽排序(
ReorderableListView),观察 Key 的作用 - 在 StatefulWidget 的每个生命周期方法中添加
print,观察调用顺序 - 添加一个 Drawer,使用
Navigator实现从 Drawer 跳转到"关于"页面