713 lines
29 KiB
Markdown
713 lines
29 KiB
Markdown
# Flutter 学习全旅程讲解 — 从零到生产级项目
|
||
|
||
> **预计时长:40 分钟** | **项目:`new_learn`** | **Flutter 3.44.x + Dart 3.12**
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
| 章节 | 主题 | 预估时长 |
|
||
|------|------|----------|
|
||
| 一 | 环境安装与项目初始化 | ~4 min |
|
||
| 二 | Dart 3 语法核心要点 | ~4 min |
|
||
| 三 | Flutter 3 核心框架与分层架构 | ~5 min |
|
||
| 四 | 全局状态管理:Riverpod 方案 | ~4 min |
|
||
| 五 | Dio 网络请求生产级封装 | ~4 min |
|
||
| 六 | 屏幕适配:ScreenUtil 方案 | ~3 min |
|
||
| 七 | 多品牌主题配置体系 | ~3 min |
|
||
| 八 | 多环境变量配置体系 | ~3 min |
|
||
| 九 | 本地存储:Isar + 响应式 watch | ~3 min |
|
||
| 十 | Demo 组件封装思路汇总 | ~5 min |
|
||
| 十一 | 学习路径与心得总结 | ~2 min |
|
||
|
||
---
|
||
|
||
## 一、环境安装与项目初始化(~4 min)
|
||
|
||
### 1.1 工具链一览
|
||
|
||
| 工具 | 版本 | 用途 |
|
||
|------|------|------|
|
||
| Flutter SDK | 3.44.x | 编译、热重载、多端构建 |
|
||
| Dart SDK | 3.12 | 语言运行时 |
|
||
| Android Studio / VS Code | 最新 | IDE + 插件 |
|
||
| Android SDK | API 34+ | 安卓构建 |
|
||
| Xcode | 15+(Mac) | iOS 构建 |
|
||
|
||
> 本项目路径:`D:\dev\flutter_3.44.8\flutter`(见 [local.properties](file:///d:/flutter_learn/new_learn/android/local.properties))
|
||
|
||
### 1.2 创建项目
|
||
|
||
```bash
|
||
flutter create --org com.example --platforms=android,ios,web,windows new_learn
|
||
```
|
||
|
||
### 1.3 项目核心依赖
|
||
|
||
完整依赖见 [pubspec.yaml](file:///d:/flutter_learn/new_learn/pubspec.yaml),关键项:
|
||
|
||
```yaml
|
||
environment:
|
||
sdk: ^3.12.2
|
||
|
||
dependencies:
|
||
flutter: { sdk: flutter }
|
||
dio: ^5.10.0 # 网络请求
|
||
go_router: ^17.4.0 # 声明式路由
|
||
flutter_riverpod: ^3.3.2 # 状态管理
|
||
flutter_screenutil: ^5.9.0 # 屏幕适配
|
||
|
||
cached_network_image: ^3.4.1 # 网络图片缓存(ImageDemo)
|
||
infinite_scroll_pagination: ^5.1.1 # 分页列表
|
||
flutter_widget_from_html_core: ^0.17.2 # 富文本 HTML 渲染
|
||
|
||
shared_preferences: ^2.5.3 # 轻量本地存储
|
||
flutter_secure_storage: ^9.2.4 # 敏感凭据安全存储
|
||
isar_community: ^3.3.2 # 本地数据库
|
||
isar_community_flutter_libs: ^3.3.2
|
||
path_provider: ^2.1.5
|
||
|
||
wechat_assets_picker: ^9.8.0 # 相册多选/预览(原生端)
|
||
wechat_camera_picker: ^4.6.0 # 微信风格相机(原生端)
|
||
image_picker: ^1.2.3 # Web 兜底图片选择
|
||
permission_handler: ^11.3.1 # 权限管理
|
||
|
||
image: ^4.8.0 # 纯 Dart 图片处理(含 Web)
|
||
flutter_image_compress: ^2.5.1 # 原生压缩(Android/iOS)
|
||
photo_view: ^0.15.0 # 图片预览缩放
|
||
|
||
video_player: ^2.13.0 # 视频播放
|
||
chewie: ^1.13.1 # 视频 UI 封装
|
||
just_audio: ^0.10.6 # 音频播放
|
||
|
||
share_plus: ^10.1.4 # 系统分享
|
||
gal: ^2.3.3 # 保存到相册
|
||
package_info_plus: ^8.3.0 # 版本信息
|
||
url_launcher: ^6.3.1 # 外部跳转
|
||
smooth_sheets: ^1.1.1 # 常驻底部面板
|
||
|
||
crypto: ^3.0.6 # MD5 等加密
|
||
# 完整依赖与注释说明以 pubspec.yaml 为准(此处为学习旅程的“关键依赖速览”)
|
||
```
|
||
|
||
### 1.4 目录结构
|
||
|
||
```
|
||
lib/
|
||
├── main.dart # 启动入口(5步初始化)
|
||
├── core/ # 核心基础设施层
|
||
│ ├── app.dart # 根组件 TabApp
|
||
│ ├── config/ # 环境+品牌配置
|
||
│ ├── network/ # Dio 封装 + API
|
||
│ ├── providers/ # Riverpod 全局状态
|
||
│ ├── router/ # go_router 路由表
|
||
│ ├── theme/ # 多品牌主题
|
||
│ ├── storage/ # Isar + 安全存储
|
||
│ ├── services/ # 图片/选择/分享服务
|
||
│ └── components/ # 9个可复用组件
|
||
├── features/ # 业务功能层
|
||
│ ├── auth/ # 登录+隐私协议
|
||
│ ├── exam/ # 试卷练习
|
||
│ ├── my/ # 个人中心
|
||
│ └── ...
|
||
└── pages/demo/ # 28个独立学习Demo
|
||
```
|
||
|
||
> **讲解要点**:这套 `core / features / pages` 三层分离,对标前端 `utils / views / components`,让基础设施和业务逻辑彻底解耦。
|
||
|
||
---
|
||
|
||
## 二、Dart 3 语法核心要点(~4 min)
|
||
|
||
### 2.1 与 JavaScript/TypeScript 的关键差异
|
||
|
||
| 概念 | JS/TS | Dart 3 |
|
||
|------|-------|--------|
|
||
| 类型声明 | `let x: number` | `int x` / `var x`(类型推断)|
|
||
| 空安全 | 可选 `?` | 健全空安全 `String?`,编译期拦截 |
|
||
| 异步 | Promise | `Future` + `async/await` |
|
||
| 集合 | Array/Map | `List<T>` / `Map<K,V>` / `Set<T>` |
|
||
| 类 | class | class + `mixin` 混入 |
|
||
| 模式匹配 | 无 | `switch` 表达式 + 解构(Dart 3 新特性)|
|
||
|
||
### 2.2 空安全(Sound Null Safety)
|
||
|
||
```dart
|
||
String name = 'Flutter'; // 不可为 null
|
||
String? nickname; // 可为 null,使用前必须判空
|
||
|
||
// 安全调用
|
||
int len = nickname?.length ?? 0; // ?. + ?? 兜底
|
||
```
|
||
|
||
> **核心规则**:所有变量默认非空,`?` 才允许 null。编译期就帮你拦住空指针。
|
||
|
||
### 2.3 异步编程
|
||
`fromJson` 是构造函数,把字典转换成我们定义的 User 实体类对象,获得类型安全、点访问、编译检查。
|
||
|
||
```dart
|
||
Future<User> fetchUser() async {
|
||
final response = await dio.get('/user');
|
||
return User.fromJson(response.data);
|
||
}
|
||
```
|
||
|
||
### 2.4 模式匹配(Dart 3 特性)
|
||
|
||
```dart
|
||
// switch 表达式
|
||
String describe(Object obj) => switch (obj) {
|
||
int() => '整数',
|
||
String() => '字符串',
|
||
List<int>() => '整数列表',
|
||
_ => '未知',
|
||
};
|
||
```
|
||
|
||
### 2.5 类与混入
|
||
|
||
```dart
|
||
mixin Disposable {
|
||
void dispose();
|
||
}
|
||
|
||
class MyController with Disposable {
|
||
@override
|
||
void dispose() { /* 释放资源 */ }
|
||
}
|
||
```
|
||
|
||
> **讲解要点**:Dart 3 的空安全是「健全」的,编译期保证非空变量不会拿到 null,比 TS 的可选链更彻底。模式匹配让分支逻辑更简洁。
|
||
|
||
---
|
||
|
||
## 三、Flutter 3 核心框架与分层架构(~5 min)
|
||
|
||
### 3.1 Widget 是什么?
|
||
|
||
**核心认知**:Widget 是「配置对象」不是「渲染 View」。
|
||
|
||
```
|
||
Widget(配置蓝图)→ Element(实例化树节点)→ RenderObject(真正测量布局绘制)
|
||
```
|
||
|
||
- `StatelessWidget`:纯展示,props 不变 → 如 `Text`、`Icon`
|
||
- `StatefulWidget`:有内部状态,`setState` 触发重建 → 如 `Checkbox`、`TextField`
|
||
|
||
### 3.2 核心布局原理
|
||
|
||
> **Flutter 布局第一法则**:父组件向子组件传递**约束(Constraints)**,子组件根据约束**决定自身大小(Size)**,然后向上返回。
|
||
|
||
```
|
||
父 Widget ──Constraints──> 子 Widget
|
||
↓ 决定自身大小
|
||
父 Widget <──Size────────── 子 Widget
|
||
```
|
||
|
||
- **主轴**(MainAxis):Row 的水平方向 / Column 的垂直方向
|
||
- **交叉轴**(CrossAxis):与主轴垂直的方向
|
||
|
||
### 3.3 启动流程链路
|
||
|
||
入口文件 [main.dart](file:///d:/flutter_learn/new_learn/lib/main.dart):
|
||
|
||
```
|
||
main() 5步串行初始化
|
||
↓
|
||
1. WidgetsFlutterBinding.ensureInitialized() // 插件通道就绪
|
||
2. GoRouter.optionURLReflectsImperativeAPIs // 修复 web 地址栏同步
|
||
3. DioClient.instance.restoreToken() // 回填登录 Token
|
||
4. privacyAcceptedNotifier // 恢复隐私协议状态
|
||
5. AppDatabase.instance.init() // 初始化 Isar
|
||
↓
|
||
runApp(ProviderScope(child: TabApp())) // Riverpod 容器 + 根组件
|
||
```
|
||
|
||
根组件 [app.dart](file:///d:/flutter_learn/new_learn/lib/core/app.dart):
|
||
|
||
```
|
||
TabApp (StatelessWidget)
|
||
└─ ScreenUtilInit // 屏幕适配
|
||
└─ MaterialApp.router // Material3 + 品牌种子色
|
||
├─ builder: PrivacyStartupGate // 启动层隐私拦截
|
||
└─ routerConfig: appRouter // 全局路由表
|
||
```
|
||
|
||
### 3.4 路由体系
|
||
|
||
路由表 [app_router.dart](file:///d:/flutter_learn/new_learn/lib/core/router/app_router.dart):
|
||
|
||
```
|
||
GoRouter
|
||
├─ redirect: _guardLogin // 登录守卫(对标 vue-router beforeEach)
|
||
│ ├─ 未同意隐私 → /privacy-consent
|
||
│ ├─ 已同意+在隐私页 → /login 或 /
|
||
│ ├─ 未登录 → /login
|
||
│ └─ 已登录+在登录页 → /
|
||
├─ refreshListenable: authTick + privacyNotifier // 响应式守卫
|
||
├─ ShellRoute (4 tab 壳)
|
||
│ ├─ / → TaskPage
|
||
│ ├─ /recite → RecitePage
|
||
│ ├─ /class → ClassPage
|
||
│ └─ /my → MyPage
|
||
├─ /privacy-consent
|
||
├─ /login
|
||
└─ /myPoint
|
||
```
|
||
|
||
> **讲解要点**:go_router 的 `refreshListenable` 是核心 — 登录态变化时自动 `refresh()` 重算 redirect,不需要手动跳转,对标 Vue Router 的响应式 `beforeEach`。
|
||
|
||
---
|
||
|
||
## 四、全局状态管理:Riverpod 方案(~4 min)
|
||
|
||
### 4.1 为什么选 Riverpod?
|
||
|
||
| 维度 | Provider(旧) | Riverpod 3.x |
|
||
|------|----------------|--------------|
|
||
| 依赖注入 | `context.read()` 耦合 BuildContext | 全局容器,不依赖 context |
|
||
| 测试 | 难 mock | `ProviderContainer` 轻量覆盖 |
|
||
| 安全 | 运行时报错 | 编译期检查(Provider 不可缺)|
|
||
| 异步 | 手写 FutureBuilder | AsyncNotifier 内建 loading/error/data |
|
||
|
||
### 4.2 本项目的两种状态方案
|
||
|
||
#### 方案 A:ValueNotifier — 轻量全局信号
|
||
|
||
隐私协议状态 [privacy_provider.dart](file:///d:/flutter_learn/new_learn/lib/core/providers/privacy_provider.dart):
|
||
|
||
```dart
|
||
final ValueNotifier<bool> privacyAcceptedNotifier = ValueNotifier<bool>(false);
|
||
```
|
||
|
||
> **为什么不用 Riverpod?** go_router 的 `refreshListenable` 需要 `Listenable`,ValueNotifier 足够轻量,且 `main()` 中 `runApp` 前就能直接恢复状态。
|
||
|
||
#### 方案 B:Riverpod AsyncNotifier — 全局用户状态
|
||
|
||
用户状态 [user_provider.dart](file:///d:/flutter_learn/new_learn/lib/core/providers/user_provider.dart):
|
||
|
||
```dart
|
||
class CurrentUserNotifier extends AsyncNotifier<CurrentUserExtension?> {
|
||
@override
|
||
Future<CurrentUserExtension?> build() async {
|
||
// 冷启动异步从本地缓存回填
|
||
return await UserStorage.instance.readUser();
|
||
}
|
||
|
||
void set(user) {
|
||
state = AsyncData(user); // 有值
|
||
authTick.value++; // 触发路由守卫重算
|
||
}
|
||
}
|
||
|
||
// 全局 Provider
|
||
final currentUserProvider =
|
||
AsyncNotifierProvider<CurrentUserNotifier, CurrentUserExtension?>(...);
|
||
```
|
||
|
||
状态四态生命周期:
|
||
|
||
```
|
||
AsyncLoading() → 正在读缓存
|
||
AsyncData(user) → 有值(已登录)
|
||
AsyncData(null) → 明确未登录
|
||
AsyncError(e) → 异常
|
||
```
|
||
|
||
> **讲解要点**:`authTick`(ValueNotifier<int>)是登录态变化的信号源,Riverpod 写状态、ValueNotifier 通知路由,两者协作实现「响应式登录守卫」。
|
||
|
||
---
|
||
|
||
## 五、Dio 网络请求生产级封装(~4 min)
|
||
|
||
### 5.1 封装架构
|
||
|
||
核心文件 [dio_client.dart](file:///d:/flutter_learn/new_learn/lib/core/network/dio_client.dart):
|
||
|
||
```
|
||
DioClient (单例)
|
||
├─ BaseOptions
|
||
│ ├─ baseUrl → 来自 AppConfig(品牌×环境组合)
|
||
│ └─ 超时:连接 15s / 接收 15s / 发送 15s
|
||
├─ 拦截器三件套
|
||
│ ├─ onRequest → 自动加 Authorization: Bearer token
|
||
│ ├─ onResponse → 日志打印
|
||
│ └─ onError → 401清Token + DioException→ApiException
|
||
├─ 泛型请求方法
|
||
│ ├─ get<T>(path, {cancelToken, queryParameters})
|
||
│ ├─ post<T>(path, {data, cancelToken, onSendProgress})
|
||
│ └─ put<T>(path, {data})
|
||
└─ restoreToken() → 冷启动回填 Token
|
||
```
|
||
|
||
### 5.2 统一异常翻译
|
||
|
||
```dart
|
||
// UI 层只需要 catch ApiException,不用管 DioException
|
||
ApiException _mapError(DioException e) {
|
||
switch (e.type) {
|
||
case DioExceptionType.connectionTimeout:
|
||
return ApiException('网络连接超时,请检查网络');
|
||
case DioExceptionType.connectionError:
|
||
return ApiException('网络连接失败,请检查网络');
|
||
// ... badResponse / cancel / unknown
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.3 生产级能力(Demo 演示)
|
||
|
||
| 能力 | 实现方式 | Demo |
|
||
|------|----------|------|
|
||
| 下载进度 | `onReceiveProgress` 回调 | [dio_pro_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/dio_pro_demo.dart) |
|
||
| 取消请求 | `CancelToken` + dispose 自动取消 | 同上 |
|
||
| 失败重试 | 指数退避 `_canRetry`(仅网络层错误)| 同上 |
|
||
| Token 自动注入 | 拦截器 onRequest | dio_client.dart |
|
||
| 401 自动登出 | 拦截器 onError | dio_client.dart |
|
||
| OSS 直传 | 已有 authorization 头时跳过注入 | [oss_uploader.dart](file:///d:/flutter_learn/new_learn/lib/core/network/oss_uploader.dart) |
|
||
|
||
### 5.4 数据模型 fromJson
|
||
|
||
```dart
|
||
// user_model.dart — 防御性解析,兼容后端返回字符串数字
|
||
factory CurrentUserExtension.fromJson(Map<String, dynamic> json) {
|
||
return CurrentUserExtension(
|
||
userId: json['userId'] as int,
|
||
userNo: json['userNo']?.toString() ?? '',
|
||
level: _toInt(json['level']), // 兼容 "1" 或 1
|
||
balance: _toDouble(json['balance']),
|
||
// ... 17 个字段
|
||
);
|
||
}
|
||
```
|
||
|
||
> **讲解要点**:`fromJson` 用 `_toInt` / `_toDouble` 兜底,是真实项目中最常见的后端类型不稳定问题。UI 层永远只 catch `ApiException` 一个类型。
|
||
|
||
---
|
||
|
||
## 六、屏幕适配:ScreenUtil 方案(~3 min)
|
||
|
||
### 6.1 核心文件
|
||
|
||
适配模板 [screen_adapt_template.dart](file:///d:/flutter_learn/new_learn/lib/core/utils/screen_adapt_template.dart):
|
||
|
||
```
|
||
AppAdapt 工具类
|
||
├─ tabletBreakpoint = 600 // Material 平板断点
|
||
├─ phoneDesign = Size(375, 812) // iPhone 逻辑尺寸
|
||
├─ designSizeOf(context) // 方案B:平板用设备自身尺寸
|
||
└─ fontSizeResolver // 方案A:字体放大封顶 1.2 倍
|
||
```
|
||
|
||
### 6.2 双方案策略
|
||
|
||
| 方案 | 目标 | 实现 |
|
||
|------|------|------|
|
||
| 方案 A | 大屏字体不过分放大 | `fontSizeResolver` 封顶 `maxFontScale=1.2` |
|
||
| 方案 B | 平板布局不被拉伸 | 宽高都 ≥600dp 时用设备自身尺寸做 designSize,缩放回到 ~1 |
|
||
|
||
### 6.3 使用规则
|
||
|
||
```dart
|
||
// 字号:用 .spMin(大屏只缩不放)
|
||
Text('你好', style: TextStyle(fontSize: 16.spMin));
|
||
|
||
// 宽度/横向间距:用 .w
|
||
SizedBox(width: 16.w);
|
||
|
||
// 高度/纵向:用 .h
|
||
SizedBox(height: 48.h);
|
||
|
||
// 圆角:用 .r
|
||
BorderRadius.circular(8.r);
|
||
```
|
||
|
||
> **讲解要点**:字号必须用 `.spMin` 而非 `.sp` — `.sp` 在大屏会把字体放得过大,`.spMin` 确保只缩小不放大。
|
||
|
||
---
|
||
|
||
## 七、多品牌主题配置体系(~3 min)
|
||
|
||
### 7.1 三层解耦设计
|
||
|
||
```
|
||
环境(接口在哪) → app_env.dart 编译期 --dart-define 定死
|
||
品牌(谁家 App) → app_brand.dart 发布期定死,7个品牌
|
||
主题(长什么样) → brand_theme.dart 运行时可切换
|
||
配置聚合 → app_config.dart 全项目唯一取配置入口
|
||
```
|
||
|
||
### 7.2 两级主题模型
|
||
|
||
模型文件 [brand_theme.dart](file:///d:/flutter_learn/new_learn/lib/core/theme/brand_theme.dart):
|
||
|
||
```
|
||
BrandTheme(一级品牌)
|
||
├─ name: 'SkyBlue'
|
||
├─ seedColor: Color(0xFF4FC3F7) // 种子色 → Material3 自动生成整套色板
|
||
└─ pages: Map<PageLevel, PageTheme>
|
||
├─ home: PageTheme { bg, headerBg, accent, title, banner }
|
||
├─ detail: PageTheme { ... }
|
||
└─ result: PageTheme { ... }
|
||
```
|
||
|
||
品牌主题文件:
|
||
- [sky_blue_theme.dart](file:///d:/flutter_learn/new_learn/lib/core/theme/brands/sky_blue_theme.dart) — seedColor `0xFF4FC3F7`
|
||
- [red_theme.dart](file:///d:/flutter_learn/new_learn/lib/core/theme/brands/red_theme.dart) — seedColor `0xFFEF4444`
|
||
|
||
### 7.3 新增主题三步走
|
||
|
||
```
|
||
1. 在 brands/ 下新建 xxx_theme.dart → 定义 seedColor + 三层级配置
|
||
2. 在 brand_theme.dart 中 import + 添加到 brandThemes 列表
|
||
3. 在 app_brand.dart 中给品牌指定 themeId
|
||
```
|
||
|
||
> **讲解要点**:「模型文件不写颜色、品牌文件不写逻辑」— 新增主题零改页面代码。`colorSchemeSeed` 让 Material 3 自动从种子色生成完整配色。
|
||
|
||
---
|
||
|
||
## 八、多环境变量配置体系(~3 min)
|
||
|
||
### 8.1 编译期注入
|
||
|
||
环境配置 [app_env.dart](file:///d:/flutter_learn/new_learn/lib/core/config/app_env.dart):
|
||
|
||
```dart
|
||
// 编译期注入,默认 prod
|
||
static const String env = String.fromEnvironment('ENV', defaultValue: 'prod');
|
||
static const String brand = String.fromEnvironment('BRAND', defaultValue: 'aixue');
|
||
```
|
||
|
||
### 8.2 三套环境地址
|
||
|
||
| 环境 | baseUrl | 用途 |
|
||
|------|---------|------|
|
||
| dev | `k12-student-api-test.23544.com:16400` | 本地开发联调 |
|
||
| test | `axdb-test.cnzhyx.cn` | 测试环境 |
|
||
| prod | `ylax-api.cqbzyx.com`(被品牌专属地址覆盖)| 生产 |
|
||
|
||
### 8.3 运行命令
|
||
|
||
```bash
|
||
# 开发环境 + 爱学蝶变品牌
|
||
flutter run --dart-define=ENV=dev --dart-define=BRAND=aixue
|
||
|
||
# 生产环境 + 楚才品牌
|
||
flutter run --dart-define=ENV=prod --dart-define=BRAND=chucai
|
||
|
||
# 构建 APK
|
||
flutter build apk --dart-define=ENV=prod --dart-define=BRAND=chucai
|
||
```
|
||
|
||
### 8.4 配置聚合层
|
||
|
||
[app_config.dart](file:///d:/flutter_learn/new_learn/lib/core/config/app_config.dart) — 全项目唯一取配置入口:
|
||
|
||
```dart
|
||
// baseUrl 规则:prod 用品牌专属地址覆盖,否则环境通用地址
|
||
static String get baseUrl =>
|
||
AppEnv.isProd ? (currentBrand.baseUrl ?? AppEnv.baseUrl) : AppEnv.baseUrl;
|
||
|
||
// 主题:按品牌查主题注册表,未知兜底第一个
|
||
static BrandTheme get theme =>
|
||
brandThemes.firstWhere((t) => t.name == currentBrand.themeId,
|
||
orElse: () => brandThemes.first);
|
||
```
|
||
|
||
> **讲解要点**:`String.fromEnvironment` 是编译期常量,不占运行时开销。品牌 × 环境组合让一套代码出 7 个品牌的 App,只需改运行命令。
|
||
|
||
---
|
||
|
||
## 九、本地存储:Isar + 响应式 watch(~3 min)
|
||
|
||
### 9.1 存储组合
|
||
|
||
| 存储方式 | 用途 | 文件 |
|
||
|----------|------|------|
|
||
| `flutter_secure_storage` | Token 安全存储 | [user_storage.dart](file:///d:/flutter_learn/new_learn/lib/core/storage/user_storage.dart) |
|
||
| `shared_preferences` | 隐私协议同意状态 | [privacy_storage.dart](file:///d:/flutter_learn/new_learn/lib/core/storage/privacy_storage.dart) |
|
||
| `isar_community` | 题库/错题/播放记录 | [app_database.dart](file:///d:/flutter_learn/new_learn/lib/core/storage/database/app_database.dart) |
|
||
|
||
### 9.2 Isar 数据模型
|
||
|
||
```dart
|
||
@collection
|
||
class WrongQuestion {
|
||
Id id = Isar.autoIncrement;
|
||
late int questionId;
|
||
late int userId;
|
||
late String questionTitle;
|
||
late DateTime createdAt;
|
||
}
|
||
```
|
||
|
||
> 通过 `build_runner` 生成 `.g.dart`,提供类型安全的 CRUD。
|
||
|
||
### 9.3 平台分流实现
|
||
|
||
```
|
||
app_database.dart → 抽象接口
|
||
├─ app_database_io.dart → 原生平台 Isar 实现
|
||
└─ app_database_web.dart → Web 调试自动跳过(Isar 不支持 Web)
|
||
```
|
||
|
||
### 9.4 watch 响应式 — 错题自动刷新
|
||
|
||
Demo 演示 [isar_watch_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/isar_watch_demo.dart):
|
||
|
||
```dart
|
||
// Isar watch:数据变化自动通知 UI 刷新
|
||
Stream<List<WrongQuestion>> watchWrongQuestions() {
|
||
return database.wrongQuestions
|
||
.where()
|
||
.userIdEqualTo(userId)
|
||
.watch(fireImmediately: true);
|
||
}
|
||
```
|
||
|
||
> **讲解要点**:Isar 的 `watch()` 是真正的响应式 — 数据库写入后自动通知 Stream 监听者,UI 无需手动刷新。对标前端的「数据驱动视图」,但是是本地数据库级别的。
|
||
|
||
---
|
||
|
||
## 十、Demo 组件封装思路汇总(~5 min)
|
||
|
||
### 10.1 28 个 Demo 的分类学习路径
|
||
|
||
#### 第一阶段:基础组件
|
||
|
||
| Demo | 核心知识点 | 代码 |
|
||
|------|-----------|------|
|
||
| Text | 样式/截断/富文本/手势 | [text_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/text_demo.dart) |
|
||
| Image | 四种来源 + BoxFit + 错误兜底 | [image_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/image_demo.dart) |
|
||
| Input | controller + dispose + 密码显隐 | [input_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/input_demo.dart) |
|
||
| Align | 对齐 + Riverpod 全局信息 | [align_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/align_demo.dart) |
|
||
| Stack | 层叠 + 全 Positioned 尺寸坑 | [stack_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/stack_demo.dart) |
|
||
| Wrap | 自动换行 + Chip + 自绘 CustomTag | [wrap_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/wrap_demo.dart) |
|
||
|
||
#### 第二阶段:布局进阶
|
||
|
||
| Demo | 核心知识点 | 代码 |
|
||
|------|-----------|------|
|
||
| List | 懒加载/下拉刷新/触底加载 | [list_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/list_demo.dart) |
|
||
| Grid | count/builder/extent 三种构造 | [grid_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/grid_demo.dart) |
|
||
| Sliver | CustomScrollView + 折叠 AppBar | [sliver_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/sliver_demo.dart) |
|
||
| Responsive | 手机单列 vs Pad 多列 | [responsive_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/responsive_demo.dart) |
|
||
|
||
#### 第三阶段:路由与表单
|
||
|
||
| Demo | 核心知识点 | 代码 |
|
||
|------|-----------|------|
|
||
| Router | 命名路由表 + 三种跳转姿势 | [router_home_page.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/router_home_page.dart) |
|
||
| Detail | 构造传参 vs 命名路由 arguments | [detail_page.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/detail_page.dart) |
|
||
| Form | 7 个校验知识点/两段式提交 | [form_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/form_demo.dart) |
|
||
| Picker | pop 带值返回 | [picker_page.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/picker_page.dart) |
|
||
|
||
#### 第四阶段:网络与数据
|
||
|
||
| Demo | 核心知识点 | 代码 |
|
||
|------|-----------|------|
|
||
| Dio | GET/POST/错误兜底三态 UI | [dio_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/dio_demo.dart) |
|
||
| Dio Pro | 下载进度/CancelToken/重试 | [dio_pro_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/dio_pro_demo.dart) |
|
||
| Isar Watch | 错题本自动刷新 | [isar_watch_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/isar_watch_demo.dart) |
|
||
| Wrong Flow | 接口+Isar+watch 完整链路 | [wrong_flow_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/wrong_flow_demo.dart) |
|
||
| OSS Upload | 客户端签名+PUT直传 | [oss_upload_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/oss_upload_demo.dart) |
|
||
|
||
#### 第五阶段:主题与媒体
|
||
|
||
| Demo | 核心知识点 | 代码 |
|
||
|------|-----------|------|
|
||
| Theme | 两级主题模型 + 品牌换肤 | [theme_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/theme_demo.dart) |
|
||
| Seed Palette | colorSchemeSeed 自动生成配色 | [seed_palette_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/seed_palette_demo.dart) |
|
||
| Animation | 隐式/显式/Hero/CustomPaint | [animation_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/animation_demo.dart) |
|
||
| Rich Text | HTML 题目渲染 + audio 替换 | [rich_text_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/rich_text_demo.dart) |
|
||
| Audio | just_audio 控制条 | [audio_player_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/audio_player_demo.dart) |
|
||
| Share | 文本/图片/截图海报/存相册 | [share_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/share_demo.dart) |
|
||
| Smooth Sheet | 常驻多停靠底部面板 | [smooth_sheet_demo.dart](file:///d:/flutter_learn/new_learn/lib/pages/demo/smooth_sheet_demo.dart) |
|
||
|
||
### 10.2 核心组件封装思路(9个可复用组件)
|
||
|
||
| 组件 | 封装思路 | 代码 |
|
||
|------|----------|------|
|
||
| **PrivacyStartupGate** | 启动层隐私 Gate — 冷启动不闪登录页,视觉第一拦 | [privacy_startup_gate.dart](file:///d:/flutter_learn/new_learn/lib/core/components/privacy_startup_gate.dart) |
|
||
| **CommonPagedList** | 通用分页列表 — 基于 infinite_scroll_pagination,对标 z-paging | [common_paged_list.dart](file:///d:/flutter_learn/new_learn/lib/core/components/common_paged_list.dart) |
|
||
| **AudioPlayerCard** | 通用音频控制条 — 播放/进度/倍速/循环,切后台自动暂停 | [audio_player_card.dart](file:///d:/flutter_learn/new_learn/lib/core/components/audio_player_card.dart) |
|
||
| **VideoPlayerView** | 通用视频播放器内嵌版 — 无 Scaffold,可嵌入任意位置 | [video_player_view.dart](file:///d:/flutter_learn/new_learn/lib/core/components/video_player_view.dart) |
|
||
| **ImagePreviewViewer** | 通用图片预览 — photo_view 双指缩放 + Hero 动画 | [image_preview_viewer.dart](file:///d:/flutter_learn/new_learn/lib/core/components/image_preview_viewer.dart) |
|
||
| **RichTextContent** | 富文本渲染 — HTML + audio/video 自动替换为播放器 | [rich_text_content.dart](file:///d:/flutter_learn/new_learn/lib/core/components/rich_text_content.dart) |
|
||
| **PersistentSnapSheet** | 常驻底部面板 — smooth_sheets 封装 | [persistent_snap_sheet.dart](file:///d:/flutter_learn/new_learn/lib/core/components/persistent_snap_sheet.dart) |
|
||
| **AppUpdateDialog** | 版本更新弹窗 — 强制/可选 + 平台分流跳转 | [app_update_dialog.dart](file:///d:/flutter_learn/new_learn/lib/core/components/app_update_dialog.dart) |
|
||
|
||
### 10.3 服务层封装思路(3个核心服务)
|
||
|
||
| 服务 | 封装思路 | 代码 |
|
||
|------|----------|------|
|
||
| **PickerService** | 图片选择统一服务 — Web用image_picker / 原生用微信选择器 + 权限守卫 | [picker_service.dart](file:///d:/flutter_learn/new_learn/lib/core/services/picker_service.dart) |
|
||
| **ImageProcessor** | 图片压缩 — 原生走flutter_image_compress / Web走纯Dart image包(Isolate隔离)| [image_processor.dart](file:///d:/flutter_learn/new_learn/lib/core/services/image_processor.dart) |
|
||
| **ShareService** | 分享服务 — 截图Widget渲染树→PNG→分享/存相册 | [share_service.dart](file:///d:/flutter_learn/new_learn/lib/core/services/share_service.dart) |
|
||
|
||
### 10.4 封装设计原则
|
||
|
||
1. **平台分流**:同一接口,io/web 双实现,条件 import自动选择
|
||
2. **资源释放**:所有 `TextEditingController` / `StreamSubscription` / `CancelToken` / `AnimationController` 在 `dispose` 中销毁
|
||
3. **防御性解析**:`fromJson` 兜底后端类型不稳定(`_toInt` / `_toDouble`)
|
||
4. **单一职责**:组件只管渲染,数据逻辑下沉到 Provider/Service
|
||
5. **配置驱动**:环境/品牌/主题互不依赖,全项目统一走 `AppConfig` 取配置
|
||
|
||
---
|
||
|
||
## 十一、学习路径与心得总结(~2 min)
|
||
|
||
### 11.1 推荐学习顺序
|
||
|
||
```
|
||
第一阶段:Widget 基础
|
||
Text → Image → Input → Align → Stack → Wrap
|
||
↓
|
||
第二阶段:布局与列表
|
||
List → Grid → Sliver → Responsive
|
||
↓
|
||
第三阶段:路由与表单
|
||
Router → Detail → Form → Picker
|
||
↓
|
||
第四阶段:网络与数据
|
||
Dio → Dio Pro → Isar Watch → Wrong Flow → OSS Upload
|
||
↓
|
||
第五阶段:主题与媒体
|
||
Theme → Seed Palette → Animation → Rich Text → Audio → Share → Smooth Sheet
|
||
```
|
||
|
||
### 11.2 从前端转 Flutter 的核心思维转变
|
||
|
||
| 前端思维 | Flutter 思维 |
|
||
|----------|-------------|
|
||
| DOM 是真实节点 | Widget 是不可变配置,每帧重建 |
|
||
| 数据驱动视图(Vue/React)| setState 触发 Element 树 diff + 重建 |
|
||
| CSS 盒模型 | 约束传递模型(父给约束,子定大小)|
|
||
| 路由守卫 beforeEach | GoRouter redirect + refreshListenable |
|
||
| Pinia/Vuex 全局 store | Riverpod Provider + AsyncNotifier |
|
||
| axios 拦截器 | Dio 拦截器三件套 |
|
||
| rem/vw 适配 | ScreenUtil .w/.h/.spMin |
|
||
| 多环境 .env 文件 | --dart-define 编译期注入 |
|
||
|
||
### 11.3 本项目的技术亮点
|
||
|
||
1. **一套代码出 7 个品牌 App** — 编译期 `--dart-define` 注入品牌+环境
|
||
2. **响应式登录守卫** — Riverpod 写状态 + ValueNotifier 通知路由
|
||
3. **双方案屏幕适配** — 手机用 375 设计稿 + 平板回退原始尺寸 + 字体封顶
|
||
4. **两级主题模型** — 种子色自动生成色板 + 页面细节配置
|
||
5. **生产级 Dio 封装** — 拦截器/统一异常/重试/取消/进度
|
||
6. **OSS 客户端直传** — V4 签名 + PUT 直传 + 进度
|
||
7. **Isar 响应式数据库** — watch 驱动 UI 自动刷新
|
||
8. **28 个 Demo 全覆盖** — 从 Text 到 OSS,每个都有配套文档
|
||
|
||
### 11.4 配套文档
|
||
|
||
- Demo 文档索引:[docs/demos/README.md](file:///d:/flutter_learn/new_learn/docs/demos/README.md)
|
||
- 试卷架构设计:[docs/exam_architecture.md](file:///d:/flutter_learn/new_learn/docs/exam_architecture.md)
|
||
- 每个 Demo 都有六章结构文档:问题背景 / 运行方式 / 核心知识点 / 关键代码剖析 / 踩坑记录 / 扩展思考
|
||
|
||
---
|
||
|
||
> **结语**:Flutter 的学习不是背 API,而是理解「Widget 是配置、Element 是实例化、RenderObject 是渲染」这条主线。所有的高级用法 — 状态管理、路由守卫、主题换肤 — 都是建立在这条主线上的。本项目 28 个 Demo 覆盖了从基础组件到生产级封装的完整路径,建议大家按顺序跑一遍,每个 Demo 都有配套文档。
|