253 lines
9.5 KiB
Dart
253 lines
9.5 KiB
Dart
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('本页主按钮'),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|