ThemeData 样式对象
常见属性分类
在 Flutter 中,ThemeData
是一个 全局样式配置对象,用于统一定义 App 的颜色、字体、按钮风格、图标大小、卡片样式等。它通常配合 MaterialApp
使用。
分类 | 属性 | 作用 |
---|---|---|
颜色相关 | primaryColor |
AppBar、TabBar、按钮等主要颜色 |
primarySwatch |
一组主色调(Material 设计推荐),会自动生成深浅变化 | |
accentColor (旧,改为 colorScheme.secondary ) |
强调色,用于 FloatingActionButton 等 | |
backgroundColor |
Scaffold 的背景色 | |
scaffoldBackgroundColor |
页面背景色 | |
dividerColor |
Divider 的颜色 | |
亮/暗模式 | brightness |
设置亮色(light)或暗色(dark)主题 |
字体与文本 | fontFamily |
全局字体 |
textTheme |
全局文字样式,如 headline1 、bodyText1 |
|
AppBar | appBarTheme |
定制 AppBar 的样式,如背景色、文字颜色 |
按钮 | buttonTheme (旧) |
按钮的全局样式 |
elevatedButtonTheme |
ElevatedButton 的样式 |
|
textButtonTheme |
TextButton 的样式 |
|
outlinedButtonTheme |
OutlinedButton 的样式 |
|
输入框 | inputDecorationTheme |
定制 TextField 的样式(边框、hint、label) |
卡片 | cardTheme |
全局卡片样式(阴影、圆角、颜色) |
图标 | iconTheme |
图标默认颜色和大小 |
底部导航栏 | bottomNavigationBarTheme |
BottomNavigationBar 的样式 |
primaryColor
vs primarySwatch
primaryColor
- 单一颜色 (
Color
) - 用来指定主题的主色调(AppBar、按钮、进度条等的主要颜色)。
- 只能表示一个固定的颜色,没有深浅变化。
primarySwatch
- 一组颜色 (
MaterialColor
) - 表示一个完整的“颜色调色板”,包含不同深浅的色阶(50、100 … 900)。
- Flutter 在生成主题(比如按钮按下、禁用、悬停时的效果)时,需要用到不同层级的颜色,所以它要求的是
MaterialColor
。
关系:
primaryColor
相当于primarySwatch[500]
。- 也就是说,如果你只设置了
primarySwatch
,primaryColor
会自动取它的中间色(通常是 500)。 - 如果你只设置了
primaryColor
,那么你只给了 Flutter 一个颜色,Flutter 就没法自动生成完整的色阶。
组件 / 场景 | 只设置 primaryColor |
设置 primarySwatch |
---|---|---|
AppBar | 使用固定的 primaryColor |
使用 primarySwatch[500] |
FAB (FloatingActionButton) | 颜色可能跟随 primaryColor ,但禁用/按下时只会加透明度 |
背景用 swatch[500] ,按下时 swatch[700] ,禁用时 swatch[100] |
ElevatedButton | 背景 = primaryColor ,按下时颜色基本不变,只是透明度变化 |
背景用 swatch[500] ,按下时自动变 swatch[700] ,禁用时自动变浅色 swatch[100] |
Switch / Slider / ProgressIndicator | 只会用 primaryColor ,状态变化不明显 |
自动根据 swatch 的不同深浅色阶适配各种状态 |
整体主题色层次 | 没有层次,所有地方都是同一个颜色 | 有 10 个色阶,能适配亮/暗/禁用/按下等场景,层次更丰富 |
primaryColor
:单一颜色 → 主题简单,状态变化弱。primarySwatch
:一整套色阶 → Flutter 能帮你自动适配按钮按下、禁用、悬浮等状态,看起来更专业。
一个案例
import 'package:flutter/material.dart';
import 'package:flutter_quickstart_learn/theme.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Quick Start',
// 样式
theme: ThemeData(
// 主题颜色
primarySwatch: Colors.brown,
// appBar颜色
appBarTheme: ThemeData.light().appBarTheme.copyWith(
// appBar背景色
backgroundColor: Colors.green,
// appBar前景色
// 前景色就是文字和图标的颜色
foregroundColor: Colors.white,
),
// 按钮颜色
elevatedButtonTheme: ElevatedButtonThemeData(
// 按钮样式
style: ElevatedButton.styleFrom(
// 按钮背景色
foregroundColor: Colors.white,
// 按钮前景色
backgroundColor: Colors.amber,
),
),
),
// page
home: const ThemePage(),
// 关闭 debug 标签
debugShowCheckedModeBanner: false,
);
}
}
import 'package:flutter/material.dart';
class ThemePage extends StatelessWidget {
const ThemePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('ThemePage')),
body: Center(
child: ElevatedButton(onPressed: () {}, child: const Text('Theme')),
),
);
}
}

使用 Theme.of(context)
在 Widget 内部,可以通过 Theme.of(context)
获取当前主题,然后使用里面的样式:
class ThemePage extends StatelessWidget {
const ThemePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('ThemePage')),
body: Center(
child: ElevatedButton(
onPressed: () {},
style: ButtonStyle(
foregroundColor: WidgetStateProperty.all(
// 这里使用Theme.of(context)获取了theme的相关配置
Theme.of(context).appBarTheme.backgroundColor,
),
),
child: const Text('Theme'),
),
),
);
}
}

Color 与 MaterialColor
Color
https://api.flutter.dev/flutter/material/Colors-class.html
Color
是 Flutter 中最基本的颜色类,用于表示一个具体的颜色值。
本质上就是一个 32 位 ARGB 整数值:
- A (Alpha, 透明度)
- R (Red, 红色分量)
- G (Green, 绿色分量)
- B (Blue, 蓝色分量)
Color(0xFF42A5F5) // 蓝色(0xAARRGGBB)
Colors.red // Flutter 内置常量(其实也是一个 Color)
Colors.black.withOpacity(0.5) // 半透明黑色
MaterialColor
https://api.flutter.dev/flutter/material/MaterialColor-class.html
MaterialColor
是 Material Design 颜色系统中的一种特殊颜色类。
它不仅包含一个主颜色,还包含 从浅到深的多个色阶(shade),比如 50、100、200 … 900。
常用于 primarySwatch
,因为 Flutter 需要一整套颜色来生成不同组件的效果。
Colors.blue // MaterialColor,蓝色的主色
Colors.blue[100] // 浅蓝
Colors.blue[900] // 深蓝
Colors.blue
并不是一个单一的 Color
,而是一个 MaterialColor
对象,里面内置了 10 个不同深浅的蓝色。
字符串转 Color
// 字符串转 Color
Color stringToColor(String source) {
return Color(int.parse(source, radix: 16) | 0xFF000000);
}
Color c = stringToColor("40c254");
int.parse(source, radix: 16)
source
是一个字符串,比如"40c254"
。radix: 16
表示把它按 16进制 解析成整数。"40c254"
→0x40C254
→ 十进制 = 4242260。
所以得到的数字是 0x0040C254
。
| 0xFF000000
- 在 Flutter 里,
Color
的构造函数需要一个 32位 ARGB 整数:- 格式是
0xAARRGGBB
AA
= Alpha(透明度)RR
= RedGG
= GreenBB
= Blue
- 格式是
- 但我们传进来的
"40c254"
只有 RGB 部分(6位),没有透明度信息。 | 0xFF000000
的意思是 按位或运算,强行把最高的AA
(透明度)位置成FF
,也就是不透明。
所以 0x0040C254 | 0xFF000000 = 0xFF40C254
。
Color(0xFF40C254)
- 最终生成的
Color
对象就是:- Alpha =
FF
(完全不透明) - Red =
40
(64) - Green =
C2
(194) - Blue =
54
(84)
- Alpha =
也就是一个绿色系的颜色。
字符串转 MaterialColor
// 字符串转 MaterialColor
// 字符串转 MaterialColor
static MaterialColor stringToMaterialColor(String source) {
Color color = stringToColor(source);
List<double> strengths = <double>[.05];
Map<int, Color> swatch = <int, Color>{};
// 'red' is deprecated and shouldn't be used. Use (*.r * 255.0).round() & 0xff.
// color.red 也过期了,应该使用 color.r
final int r = (color.r * 255.0).round() & 0xFF,
g = (color.g * 255.0).round() & 0xFF,
b = (color.b * 255.0).round() & 0xFF;
for (int i = 1; i < 10; i++) {
strengths.add(0.1 * i);
}
for (var strength in strengths) {
final double ds = 0.5 - strength;
swatch[(strength * 1000).round()] = Color.fromRGBO(
r + ((ds < 0 ? r : (255 - r)) * ds).round(),
g + ((ds < 0 ? g : (255 - g)) * ds).round(),
b + ((ds < 0 ? b : (255 - b)) * ds).round(),
1,
);
}
// color.value // 过期
// 官方提示:'value' is deprecated and shouldn't be used.
//Use component accessors like .r or .g, or toARGB32 for an explicit conversion.
//
return MaterialColor(color.toARGB32(), swatch);
}
MaterialColor mc = stringToColor("40c254");
Color color = stringToColor(source)
:调用你之前写的 stringToColor("40c254")
,把字符串转成 Color
对象。上面 "40c254"
会得到 Color(0xFF40C254)
。
List<double> strengths = <double>[.05]
:
.05
就是0.05
(Dart 允许省略小数点前的0
)。所以这行等价于:List<double> strengths = [0.05];
创建了一个列表,列表里面有一个值
0.05
final int r = (color.r * 255.0).round() & 0xFF
前面有一个字符串转成
Color
的类,然后获得这个类的 RGB 值在 Flutter 3.7+,
Color.red
/.green
/.blue
被标记为过期。取而代之的是.r
、.g
、.b
、.a
,它们 返回 0.0 ~ 1.0 的 double,而不是 0~255 的整数。* 255.0
:将0~1
的浮点数映射到0~255
的整数范围.round()
:四舍五入,得到最接近的整数& 0xFF
0xFF
是十六进制表示,等于 255。二进制形式:11111111
(8 位全 1)(x & 0xFF)
的作用:会把整数
x
的 低 8 位保留,高位全部清零。也就是说,只保留0~255
的部分。比如:int x = 300; // 二进制:1 0010 1100 int y = x & 0xFF; // 二进制:0010 1100 = 44
strengths.add(0.1 * i)
:生成 [0.05, 0.1, 0.2, ..., 0.9]
这些比例。
for (var strength in strengths) {
final double ds = 0.5 - strength;
swatch[(strength * 1000).round()] = Color.fromRGBO(
r + ((ds < 0 ? r : (255 - r)) * ds).round(),
g + ((ds < 0 ? g : (255 - g)) * ds).round(),
b + ((ds < 0 ? b : (255 - b)) * ds).round(),
1,
);
}
(strength * 1000).round()
:.round()
是取整函数,它会把一个double
四舍五入成最近的int
。最后得到:0.05 → 50 0.1 → 100 0.2 → 200 ... 0.9 → 900
Color.fromRGBO
:在 Flutter 里,Color
是一个用来表示颜色的类,它有一个工厂构造方法:Color.fromRGBO( int r, // 红色通道 (0~255) int g, // 绿色通道 (0~255) int b, // 蓝色通道 (0~255) double opacity, // 透明度 (0.0 ~ 1.0) )
r / g / b
- 红、绿、蓝的通道值,取值范围是
0 ~ 255
。 - 0 = 没有该通道,255 = 最强。
- 例如
(255, 0, 0)
就是纯红色。
opacity
- 透明度,取值范围
0.0 ~ 1.0
。 0.0
→ 完全透明1.0
→ 完全不透明
- 红、绿、蓝的通道值,取值范围是
ds < 0 ? r : (255 - r)
- 如果
ds < 0
,说明strength
> 0.5(比较偏深色),那么用r
(或者 g / b)去调暗; - 否则(
ds >= 0
,偏浅色),用255 - r
(或者 g / b)去调亮。 - 这样保证生成的色阶既有比原色更亮的版本,也有比原色更暗的版本。
- 如果
((ds < 0 ? r : (255 - r)) * ds)
- 这一步决定偏移的幅度。
ds
越大 → 偏移越明显(更亮 / 更暗)。ds
越小 → 偏移越接近原色。
r + (调整量)
- 调整量 =
((ds < 0 ? r : (255 - r)) * ds).round()
- 如果是调亮(
ds >= 0
),就会在原值上往255
方向靠近; - 如果是调暗(
ds < 0
),就会在原值上往0
方向靠近。
- 调整量 =
return MaterialColor(color.toARGB32(), swatch)
:构造 MaterialColor
:
color.toARGB32()
= 主色(比如0xFF40C254
)swatch
= 不同深浅的变体。
代码
class ColorPage extends StatelessWidget {
const ColorPage({super.key});
@override
Widget build(BuildContext context) {
var c = ColorUtils.stringToColor("FFB822");
var mc = ColorUtils.stringToMaterialColor("5C78FF");
return Scaffold(
body: SizedBox.expand(
child: Column(
children: [
// Color
Container(color: c, height: 50),
// MaterialColor
for (var i = 1; i < 10; i++)
Container(color: mc[i * 100], height: 50),
],
),
),
);
}
}
