Appearance
主题定义
本篇介绍在实战项目中如何统一定义主题:把颜色、字体等抽成常量,封装亮色/暗色主题,并在 MaterialApp 和页面中使用。基础概念可参考 Flutter主题风格。
为什么要在项目中集中定义主题
- 统一风格:所有页面共用一套颜色和字体,避免到处写死
Colors.xxx、fontSize: 16。 - 易维护:改品牌色或字体时只改一处即可全局生效。
- 支持暗色:一套亮色、一套暗色,交给系统或用户切换。
第一步:定义颜色与字体常量
在 lib/constants/(或 lib/theme/)下新建 app_colors.dart、app_text_styles.dart(也可合并到一个文件),把设计稿中的主色、背景色、文字色、字号等抽成常量。
示例(颜色):
dart
import 'package:flutter/material.dart';
class AppColors {
AppColors._();
// 主色
static const Color primary = Color(0xFF2196F3);
static const Color primaryDark = Color(0xFF1976D2);
// 背景
static const Color backgroundLight = Color(0xFFF5F5F5);
static const Color backgroundDark = Color(0xFF121212);
// 文字
static const Color textPrimary = Color(0xFF212121);
static const Color textSecondary = Color(0xFF757575);
static const Color textOnPrimary = Colors.white;
}示例(文字样式,可按需增减):
dart
import 'package:flutter/material.dart';
class AppTextStyles {
AppTextStyles._();
static const double fontSizeTitle = 20.0;
static const double fontSizeBody = 16.0;
static const double fontSizeCaption = 12.0;
static TextStyle title(Color color) => TextStyle(
fontSize: fontSizeTitle,
fontWeight: FontWeight.w600,
color: color,
);
static TextStyle body(Color color) => TextStyle(
fontSize: fontSizeBody,
color: color,
);
static TextStyle caption(Color color) => TextStyle(
fontSize: fontSizeCaption,
color: color,
);
}这样后续改主色或字号时,只改这些常量即可。
第二步:封装 AppTheme(亮色 + 暗色)
在 lib/theme/app_theme.dart 中基于 ThemeData 封装亮色和暗色两套主题,内部引用上面的颜色和字体常量。
dart
import 'package:flutter/material.dart';
import '../constants/app_colors.dart';
import '../constants/app_text_styles.dart';
class AppTheme {
AppTheme._();
static ThemeData get lightTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
primaryColor: AppColors.primary,
scaffoldBackgroundColor: AppColors.backgroundLight,
colorScheme: ColorScheme.fromSeed(
seedColor: AppColors.primary,
brightness: Brightness.light,
),
appBarTheme: AppBarTheme(
backgroundColor: AppColors.primary,
foregroundColor: AppColors.textOnPrimary,
elevation: 0,
),
textTheme: TextTheme(
titleLarge: AppTextStyles.title(AppColors.textPrimary),
bodyMedium: AppTextStyles.body(AppColors.textPrimary),
bodySmall: AppTextStyles.caption(AppColors.textSecondary),
),
);
}
static ThemeData get darkTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
primaryColor: AppColors.primary,
scaffoldBackgroundColor: AppColors.backgroundDark,
colorScheme: ColorScheme.fromSeed(
seedColor: AppColors.primary,
brightness: Brightness.dark,
),
appBarTheme: AppBarTheme(
backgroundColor: AppColors.backgroundDark,
foregroundColor: AppColors.textOnPrimary,
elevation: 0,
),
textTheme: TextTheme(
titleLarge: AppTextStyles.title(Colors.white),
bodyMedium: AppTextStyles.body(Colors.white70),
bodySmall: AppTextStyles.caption(Colors.white54),
),
);
}
}注意:Flutter 3.x 推荐用 colorScheme 和 useMaterial3,组件会优先从 ThemeData.colorScheme 取色;若需兼容旧项目,可继续使用 primaryColor、accentColor 等。
第三步:在 MaterialApp 中挂载主题
在入口(如 lib/main.dart 或 lib/app.dart)的 MaterialApp 中传入 theme 和 darkTheme,系统会根据「系统是否深色」自动切换(无需额外代码):
dart
import 'package:flutter/material.dart';
import 'theme/app_theme.dart';
import 'screens/home/home_screen.dart';
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ThemeMode.system, // 跟随系统;也可 ThemeMode.light / ThemeMode.dark 写死
home: const HomeScreen(),
);
}
}若希望用户手动切换主题,可把 themeMode 存到状态(如 Provider/GetX)或本地,再根据用户选择设为 ThemeMode.light / ThemeMode.dark。
第四步:在页面中使用主题
- 用主题颜色/文字:通过
Theme.of(context)或Theme.of(context).colorScheme、textTheme取,不要再次写死颜色(除非是设计稿明确只此一处的色值)。
dart
// 取主题色
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
Container(
color: colorScheme.primary,
child: Text(
'标题',
style: theme.textTheme.titleLarge,
),
)- 取我们封装的常量:在需要和设计稿完全一致的地方,可直接用
AppColors.primary、AppTextStyles.body(AppColors.textPrimary)等。
这样既保证全局一致,又能在个别页面做精细控制。
局部覆盖主题(可选)
某个页面或某块区域想临时换一套主题(例如深色弹窗里的按钮),可以用 Theme 包裹子组件,并用 Theme.of(context).copyWith(...) 在原有主题上只改部分属性:
dart
Theme(
data: Theme.of(context).copyWith(
colorScheme: Theme.of(context).colorScheme.copyWith(primary: Colors.orange),
),
child: const SomeChild(),
)小结
- 在
constants/或theme/中定义AppColors、AppTextStyles等常量,避免魔法数字。 - 在
AppTheme中封装lightTheme和darkTheme,并挂到MaterialApp的theme、darkTheme、themeMode。 - 页面中优先使用
Theme.of(context).colorScheme和theme.textTheme,需要时再配合AppColors/AppTextStyles。