flutter架 构项目基础

This commit is contained in:
崔宝新
2026-09-04 15:22:20 +08:00
parent 70967229d0
commit 0c5aee59c0
265 changed files with 35484 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
# 【Demo】品牌换肤(两级主题模型)
> 源文件:`lib/pages/demo/theme_demo.dart` | 分类:基础布局
> 一句话:换品牌 = 一级换整套色板 + 二级换页面细节,一套代码全 App 变色。
---
## 一、这个 Demo 解决什么问题
教育 App 常有"多品牌运营"需求:不同机构定制不同主色、页面配色、甚至标题文案。
本 Demo 演示项目核心的**两级主题模型**:
```
一级 = 品牌:seedColor 生成整套 Material 色板(全 App 共享)
二级 = 页面细节(PageTheme):按页面层级(home/detail/result)
区分背景色 / 头部色 / 强调色 / 专属 banner / 标题文案
```
页面只声明"我是哪个层级",细节从品牌对象按层级查出来:
**换品牌 → 一级色板全换 + 二级细节全换(一套代码)**。
---
## 二、怎么运行 / 演示内容
```dart
runApp(const MaterialApp(home: ThemeDemoPage()));
```
- 顶部 ChoiceChip 切换品牌(从注册表 `brandThemes` 自动生成按钮,新增品牌自动出现);
- "一级"区:显示 seed 色与生成的 primary 对比 + 跟随主题的按钮/进度条;
- "二级"区:home/detail/result 三个层级各渲染一个页面骨架预览(背景/头部/强调色/文案全跟随品牌)。
---
## 三、核心知识点(新手向)
### 1. 主题的两层结构(模型已解耦,见 `core/theme/`)
| 层 | 内容 | 存放 |
|---|---|---|
| 模型 + 注册表 | `BrandTheme` / `PageTheme` / `PageLevel` + `brandThemes` | `core/theme/brand_theme.dart` |
| 每品牌配置 | 只有具体色值/文案 | `core/theme/brands/xxx_theme.dart` |
新增一个品牌 = 新建一个配置文件 + 注册表加一行,**业务代码零改动**。
### 2. 一级:colorSchemeSeed 的原理
```dart
ThemeData(colorSchemeSeed: _brand.seedColor)
```
传一个种子色,Material 自动生成整套 ColorScheme(primary/secondary/tertiary...),
**没写死颜色的 Material 组件全部自动跟随**(FilledButton/Checkbox/Switch/进度条/聚焦输入框)。
验证页见 [seed_palette_demo.md](seed_palette_demo.md)。
> ⚠ colorSchemeSeed 生成的 primary 不是精确色值(Material 会调和),
> 要求精确用 `ColorScheme.fromSeed(...)` 后手动覆盖 primary。
### 3. 二级:PageTheme 按层级查询
```dart
// 页面只声明层级(本 demo 直接取,真实项目从路由/参数拿)
final page = _brand.pages[PageLevel.home]!;
// 使用:背景 page.bg、头部 page.headerBg、强调 page.accent、banner page.banner
```
- banner 存资源路径(如 `assets/img/banner_sky.png`),页面 `Image.asset(page.banner!)` 即可跟随换图;
- 本 demo 没准备两套图片,用"强调色渐变块"模拟 banner 占位(代码里标注了替换点)。
### 4. 局部主题覆盖(不依赖全局 MaterialApp 的切换法)
```dart
return Theme(
data: ThemeData(colorSchemeSeed: _brand.seedColor),
child: Scaffold( ... ), // 页面内组件都从局部主题取色
);
```
> 真实项目把 `_brand` 换成 Riverpod Provider + MaterialApp 的 theme,切换点移到根层。
---
## 四、关键代码剖析
```dart
// 品牌切换(注册表驱动,新增品牌自动出现)
Wrap(
spacing: 8,
children: [
for (final b in brandThemes)
ChoiceChip(
label: Text(b.name),
selected: _brand.name == b.name,
onSelected: (_) => setState(() => _brand = b),
),
],
)
// 二级细节消费示例(页面骨架预览)
Container(
color: page.bg, // ← 二级:背景
child: Column(children: [
Container(
color: page.headerBg, // ← 二级:头部色
child: Text(page.title), // ← 二级:标题文案
),
// banner 占位:真实项目替换成 Image.asset(page.banner!)
Container(decoration: BoxDecoration(gradient: LinearGradient(colors: [page.accent, page.headerBg]))),
FilledButton(
style: FilledButton.styleFrom(backgroundColor: page.accent), // ← 二级:强调色
child: const Text('本页主按钮'),
),
]),
)
```
---
## 五、踩坑记录
1. **colorSchemeSeed 生成的 primary 不精确** —— 需要精确色值时手动覆盖;
2. **局部 `Theme(data:)` 必须包在 Scaffold 外面**,AppBar 才能取到(包在 body 里 AppBar 取不到);
3. **PNG 位图不跟主题着色** —— 能用矢量(IconData)就别放多份图;
4. 深色模式要单独配 `darkTheme` + `themeMode`,不是简单换个 seed;
5. `.w/.h/.sp` 的组件不能 const(`const_eval_extension_method`)。
---
## 六、扩展思考
- 持久化:品牌选择存 shared_preferences,启动时读出来传给 MaterialApp 的 theme;
- 动态切换动画:`AnimatedTheme` 包 MaterialApp,换主题有过渡动画;
- 与 [seed_palette_demo.md](seed_palette_demo.md) 对照:一个验证"seed 生效",一个演示"两级模型的消费";
- 更重的多主题需求可引入 `flex_color_scheme` 包,本项目的轻量模型够用且无依赖。