Kodokon kodokon.com

主题:ThemeData、Material 3 与深色模式

在 ThemeData 中集中管理颜色和排版,用 ColorScheme.fromSeed 生成一套 Material 3 配色,并轻松提供深色模式。

9 分钟 · 3 题

在 Kodokon 中打开本课

不再逐个 widget 地设置样式,是一个专业上的里程碑:ThemeData 会为整个应用集中管理颜色、排版和形状。有了 Material 3(自 Flutter 3.16 起默认启用),一切都从 ColorScheme 开始:ColorScheme.fromSeed 会从单单一种种子色派生出一套完整、和谐且无障碍的配色。

DART
import 'package:flutter/material.dart';

void main() {
  runApp(const ShopApp());
}

class ShopApp extends StatelessWidget {
  const ShopApp({super.key});

  @override
  Widget build(BuildContext context) {
    const seed = Colors.teal;
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: seed,
        ),
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: seed,
          brightness: Brightness.dark,
        ),
      ),
      themeMode: ThemeMode.system,
      home: Scaffold(
        appBar: AppBar(title: const Text('Shop')),
        body: const Center(child: Text('Welcome')),
      ),
    );
  }
}
从同一种种子色生成的两套主题。

在你的 widget 内部,不要重新发明任何东西:用 Theme.of(context) 读取当前的主题。colorScheme 暴露出一批语义化的角色primaryonPrimaryprimaryContainersurface 等),textTheme 则暴露出一批具名样式(titleMediumbodyLarge 等)。这样写出来的 widget 会自动适配浅色主题和深色主题。

DART
import 'package:flutter/material.dart';

class PriceTag extends StatelessWidget {
  const PriceTag({super.key, required this.price});

  final double price;

  @override
  Widget build(BuildContext context) {
    final colors = Theme.of(context).colorScheme;
    final text = Theme.of(context).textTheme;
    return Container(
      padding: const EdgeInsets.symmetric(
        horizontal: 12,
        vertical: 6,
      ),
      decoration: BoxDecoration(
        color: colors.primaryContainer,
        borderRadius: BorderRadius.circular(8),
      ),
      child: Text(
        '$price €',
        style: text.titleMedium?.copyWith(
          color: colors.onPrimaryContainer,
        ),
      ),
    );
  }
}
没有硬编码的颜色:widget 会跟随当前生效的主题。

想更进一步,就配置组件主题:每一类 widget(应用栏、按钮、输入框)都能在 ThemeData 里接受一份专属主题。你只需一次性定义按钮的内边距或标题的居中方式,之后每个实例都会继承它,而无需任何一处局部样式。

DART
import 'package:flutter/material.dart';

final ThemeData appTheme = ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.teal,
  ),
  appBarTheme: const AppBarTheme(
    centerTitle: true,
    elevation: 0,
  ),
  filledButtonTheme: FilledButtonThemeData(
    style: FilledButton.styleFrom(
      padding: const EdgeInsets.symmetric(
        horizontal: 24,
        vertical: 12,
      ),
    ),
  ),
);
组件设置会应用到整个应用。

知识检测

确认你已牢记本课的重点内容。

  1. ColorScheme.fromSeed(seedColor: ...) 会产出什么?
    • 单单一种略微提亮的颜色
    • 一套从种子色派生而来、完整而协调的 Material 3 配色
    • 一段用于背景的渐变
    • 仅仅一套深色主题
  2. 怎样让应用跟随系统的浅色/深色设置?
    • 只提供 theme,剩下的会自动完成
    • 同时提供 theme 和 darkTheme,并把 themeMode 设为 ThemeMode.system
    • 在亮度改变时调用 setState
    • 在每个 widget 里读取 MediaQuery
  3. 为什么要写 color: Theme.of(context).colorScheme.primary 而不是 color: Colors.blue?
    • 它写起来更短
    • 这个颜色会适配当前生效的主题,并在整个应用中保持一致
    • Colors.blue 在 Material 3 中已被废弃