跳到主要内容

主题和样式

本章将介绍如何在 Flutter 中自定义应用的主题和样式。

ThemeData

ThemeData 定义了应用的整体视觉外观:

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
// 颜色方案
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
),

// 使用 Material 3
useMaterial3: true,

// AppBar 主题
appBarTheme: AppBarTheme(
centerTitle: true,
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
elevation: 0,
),

// 按钮主题
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),

// 输入框主题
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),

// 卡片主题
cardTheme: CardTheme(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
home: HomePage(),
);
}
}

深色主题

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
useMaterial3: true,
),
themeMode: ThemeMode.system, // 跟随系统
home: HomePage(),
);
}
}

动态切换主题

class ThemeProvider with ChangeNotifier {
ThemeMode _themeMode = ThemeMode.system;

ThemeMode get themeMode => _themeMode;

void setThemeMode(ThemeMode mode) {
_themeMode = mode;
notifyListeners();
}

void toggleTheme() {
_themeMode = _themeMode == ThemeMode.light
? ThemeMode.dark
: ThemeMode.light;
notifyListeners();
}
}

// 使用
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ThemeProvider(),
child: Consumer<ThemeProvider>(
builder: (context, themeProvider, _) {
return MaterialApp(
theme: ThemeData.light(useMaterial3: true),
darkTheme: ThemeData.dark(useMaterial3: true),
themeMode: themeProvider.themeMode,
home: HomePage(),
);
},
),
);
}
}

文本样式

预定义样式

Text('标题', style: Theme.of(context).textTheme.headlineLarge)
Text('正文', style: Theme.of(context).textTheme.bodyLarge)
Text('标签', style: Theme.of(context).textTheme.labelMedium)

自定义样式

Text(
'自定义文本',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
letterSpacing: 2,
height: 1.5,
decoration: TextDecoration.underline,
decorationColor: Colors.red,
decorationStyle: TextDecorationStyle.wavy,
),
)

文本主题扩展

extension TextThemeExtension on TextTheme {
TextStyle get price => TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.green,
);

TextStyle get error => TextStyle(
fontSize: 14,
color: Colors.red,
);
}

// 使用
Text('¥99.99', style: Theme.of(context).textTheme.price)

颜色

预定义颜色

Colors.red
Colors.blue
Colors.green
Colors.white
Colors.black
Colors.transparent

// 带透明度
Colors.red.withOpacity(0.5)
Colors.blue.shade100
Colors.blue.shade900

自定义颜色

// 从十六进制
Color(0xFF2196F3)

// ARGB
Color.fromARGB(255, 33, 150, 243)

// RGB
Color.fromRGBO(33, 150, 243, 1.0)

颜色方案

final colorScheme = Theme.of(context).colorScheme;

Container(
color: colorScheme.primary,
child: Text(
'主要颜色',
style: TextStyle(color: colorScheme.onPrimary),
),
)

间距和尺寸

EdgeInsets

// 内边距
Padding(
padding: EdgeInsets.all(16),
child: Text('四边相同'),
)

Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text('水平和垂直'),
)

Padding(
padding: EdgeInsets.only(left: 16, top: 8),
child: Text('指定方向'),
)

SizedBox 间距

Column(
children: [
Text('第一行'),
SizedBox(height: 16),
Text('第二行'),
SizedBox(height: 16),
Text('第三行'),
],
)

响应式样式

extension ResponsiveExtension on BuildContext {
double get screenWidth => MediaQuery.of(this).size.width;
double get screenHeight => MediaQuery.of(this).size.height;

bool get isMobile => screenWidth < 600;
bool get isTablet => screenWidth >= 600 && screenWidth < 1200;
bool get isDesktop => screenWidth >= 1200;

double responsiveValue({
required double mobile,
double? tablet,
double? desktop,
}) {
if (isDesktop && desktop != null) return desktop;
if (isTablet && tablet != null) return tablet;
return mobile;
}
}

// 使用
Container(
padding: EdgeInsets.all(
context.responsiveValue(mobile: 16, tablet: 24, desktop: 32),
),
)

小结

本章我们学习了:

  1. ThemeData:定义应用主题
  2. 深色主题:支持深色模式
  3. 动态主题:运行时切换主题
  4. 文本样式:预定义和自定义样式
  5. 颜色系统:颜色定义和颜色方案
  6. 间距和尺寸:布局间距
  7. 响应式样式:适配不同屏幕

练习

  1. 创建一个支持主题切换的应用
  2. 实现一套完整的自定义主题
  3. 创建响应式布局组件
  4. 实现文本样式扩展