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
+252
View File
@@ -0,0 +1,252 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:new_learn/core/theme/brand_theme.dart';
/// ============================================================
/// 【ThemeDemoPage】品牌换肤 Demo(两级主题模型)
///
/// 一、两级模型(模型与配置已解耦,见 core/theme/):
/// core/theme/brand_theme.dart ← 模型(BrandTheme/PageTheme/PageLevel)
/// + 注册表 brandThemes
/// core/theme/brands/xxx_theme.dart← 每品牌一个文件,只有具体配置
/// 一级 = 品牌:seedColor 生成整套 Material 色板(全 App 共享);
/// 二级 = 页面细节(PageTheme):按页面层级(home/detail/result)
/// 区分背景色 / 头部色 / 强调色 / 专属 banner / 标题文案。
/// 页面只声明"我是哪个层级",细节从品牌对象按层级查出来:
/// 换品牌 → 一级色板全换 + 二级细节全换(一套代码)
///
/// 二、本页怎么实现切换(不依赖全局 MaterialApp):
/// 页面根部用 Theme(data:) 局部覆盖——ThemeData 以全局为基底,
/// 局部 Theme 再套一层,页面内组件(AppBar/Button/进度条)都从
/// 局部主题取色。真实项目里把"_brand"换成 Riverpod Provider +
/// MaterialApp 的 theme,切换点就移到根层(见注释)。
///
/// 三、资源(图片)怎么做:
/// 真实项目 PageTheme.banner 存资源路径(如 assets/img/banner_sky.png),
/// 页面 Image.asset(page.banner) 即可跟随换图。本 demo 没有两套图片
/// 资源,用"强调色渐变块"模拟 banner 占位(代码里标注了替换点)。
///
/// 四、高频坑:
/// 1. colorSchemeSeed 生成的 primary 不是精确色值(Material 会调和),
/// 要求精确用 ColorScheme.fromSeed 后手动覆盖 primary;
/// 2. 局部 Theme(data:) 必须包在 Scaffold 外面,AppBar 才能取到;
/// 3. PNG 位图不跟主题着色,能矢量(IconData)就别放多份图。
/// ============================================================
class ThemeDemoPage extends StatefulWidget {
const ThemeDemoPage({super.key});
@override
State<ThemeDemoPage> createState() => _ThemeDemoPageState();
}
class _ThemeDemoPageState extends State<ThemeDemoPage> {
/// 当前品牌(真实项目 → Riverpod Provider + shared_preferences 持久化)
BrandTheme _brand = brandThemes.first;
@override
Widget build(BuildContext context) {
// 局部主题覆盖:colorSchemeSeed 换 seed = 页面内所有 Material
// 组件自动换色(真实项目这里换成 MaterialApp.theme)
return Theme(
data: ThemeData(colorSchemeSeed: _brand.seedColor),
child: Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
title: Text('品牌换肤 Demo(${_brand.name})'),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ---------- 品牌切换 ----------
Wrap(
spacing: 8,
children: [
for (final b in brandThemes) // 注册表:新增品牌自动出现
ChoiceChip(
label: Text(b.name),
selected: _brand.name == b.name,
onSelected: (_) => setState(() => _brand = b),
),
],
),
SizedBox(height: 16.h),
_buildGlobalCard(),
SizedBox(height: 16.h),
_buildSectionTitle('二级:页面细节(按层级,随品牌联动)'),
SizedBox(height: 8.h),
// 三个层级各渲染一个"页面骨架预览"
_PagePreview(
level: 'home(任务 tab)',
page: _brand.pages[PageLevel.home]!,
),
_PagePreview(
level: 'detail(子详情页)',
page: _brand.pages[PageLevel.detail]!,
),
_PagePreview(
level: 'result(答题结果页)',
page: _brand.pages[PageLevel.result]!,
),
],
),
),
),
);
}
/// 一级:全局色板效果区(Material 组件自动跟随 seed)
Widget _buildGlobalCard() {
final scheme = Theme.of(context).colorScheme;
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(color: const Color(0xFFEEF1F5)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle('一级:全局色板(seed: ${_brand.seedColor})'),
SizedBox(height: 8.h),
// seed 色与生成的 primary 对比
Row(
children: [
_colorChip('seed', _brand.seedColor),
SizedBox(width: 8.w),
_colorChip('primary', scheme.primary),
],
),
SizedBox(height: 12.h),
// Material 组件不用改代码,seed 一变自动换色
FilledButton(onPressed: () {}, child: const Text('跟随主题的按钮')),
SizedBox(height: 12.h),
LinearProgressIndicator(
value: 0.6,
backgroundColor: scheme.surfaceContainerHighest,
),
],
),
);
}
Widget _colorChip(String label, Color color) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 6.h),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(6.r),
),
child: Text(
label,
style: const TextStyle(color: Colors.white, fontSize: 12),
),
);
}
Widget _buildSectionTitle(String title) {
return Text(
title,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: const Color(0xFF3B82F6),
),
);
}
}
// ==================== 页面骨架预览(演示二级细节怎么被消费) ====================
class _PagePreview extends StatelessWidget {
const _PagePreview({required this.level, required this.page});
/// 本页属于哪个层级(页面只声明层级,细节全从主题查)
final String level;
final PageTheme page;
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: 12.h),
decoration: BoxDecoration(
color: page.bg, // ← 二级:背景
borderRadius: BorderRadius.circular(12.r),
border: Border.all(color: const Color(0xFFE5E7EB)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 模拟 AppBar 条(真实页面是 Scaffold.appBar)
Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h),
decoration: BoxDecoration(
color: page.headerBg, // ← 二级:头部色
borderRadius: BorderRadius.vertical(top: Radius.circular(12.r)),
),
child: Row(
children: [
const Icon(Icons.arrow_back_ios_new,
color: Colors.white, size: 14),
SizedBox(width: 6.w),
Text(
page.title, // ← 二级:标题文案
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
],
),
),
Padding(
padding: EdgeInsets.all(12.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// banner 占位:真实项目这里替换成 Image.asset(page.banner!)
// (banner 为 null 的层级显示"本层无专属图")
Container(
width: double.infinity,
height: 44.h,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [page.accent, page.headerBg],
),
borderRadius: BorderRadius.circular(8.r),
),
alignment: Alignment.center,
child: Text(
page.banner ?? '本层无专属 banner',
style: const TextStyle(color: Colors.white, fontSize: 12),
),
),
SizedBox(height: 8.h),
Text(
'层级:$level\n背景 ${page.bg} · 头部 ${page.headerBg} · 强调 ${page.accent}',
style: TextStyle(
fontSize: 12.sp,
height: 1.5,
color: const Color(0xFF666666),
),
),
SizedBox(height: 8.h),
FilledButton(
style: FilledButton.styleFrom(
backgroundColor: page.accent, // ← 二级:强调色
),
onPressed: () {},
child: const Text('本页主按钮'),
),
],
),
),
],
),
);
}
}