6.1 主题与暗色模式

Flutter 的主题系统(ThemeData)提供了统一的视觉风格管理,通过 Material 3 的颜色系统和深色模式支持,可以轻松构建专业的视觉体系。


一、ThemeData 动态切换

1.1 定义双主题

dart 复制代码
class AppTheme {
  // 亮色主题
  static ThemeData get lightTheme => ThemeData(
    useMaterial3: true,
    brightness: Brightness.light,
    colorScheme: ColorScheme.fromSeed(
      seedColor: const Color(0xFF6750A4), // 主色调
      brightness: Brightness.light,
    ),
    // 文字主题
    textTheme: _buildTextTheme(Brightness.light),
    // 组件主题
    appBarTheme: const AppBarTheme(
      centerTitle: true,
      elevation: 0,
    ),
    cardTheme: CardTheme(
      elevation: 2,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
    ),
    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        minimumSize: const Size.fromHeight(48),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      ),
    ),
    inputDecorationTheme: InputDecorationTheme(
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12),
      ),
      filled: true,
    ),
  );

  // 暗色主题
  static ThemeData get darkTheme => ThemeData(
    useMaterial3: true,
    brightness: Brightness.dark,
    colorScheme: ColorScheme.fromSeed(
      seedColor: const Color(0xFF6750A4),
      brightness: Brightness.dark,
    ),
    textTheme: _buildTextTheme(Brightness.dark),
    appBarTheme: const AppBarTheme(centerTitle: true, elevation: 0),
    cardTheme: CardTheme(
      elevation: 4,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
    ),
  );

  static TextTheme _buildTextTheme(Brightness brightness) {
    final color = brightness == Brightness.light
        ? const Color(0xFF1C1B1F)
        : const Color(0xFFE6E1E5);
    return TextTheme(
      headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: color),
      headlineMedium: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: color),
      titleLarge: TextStyle(fontSize: 20, fontWeight: FontWeight.w600, color: color),
      bodyLarge: TextStyle(fontSize: 16, color: color),
      bodyMedium: TextStyle(fontSize: 14, color: color.withOpacity(0.8)),
    );
  }
}

1.2 主题切换控制器

dart 复制代码
class ThemeController extends ChangeNotifier {
  ThemeMode _themeMode;

  ThemeController({ThemeMode initial = ThemeMode.system})
      : _themeMode = initial;

  ThemeMode get themeMode => _themeMode;

  bool get isDark => _themeMode == ThemeMode.dark;

  void setThemeMode(ThemeMode mode) {
    _themeMode = mode;
    PreferencesService.saveThemeMode(mode.index);
    notifyListeners();
  }

  void toggleTheme() {
    setThemeMode(isDark ? ThemeMode.light : ThemeMode.dark);
  }

  // 跟随系统
  void followSystem() => setThemeMode(ThemeMode.system);
}

// 在 App 入口
ChangeNotifierProvider(
  create: (_) => ThemeController(
    initial: ThemeMode.values[PreferencesService.getThemeMode()],
  ),
  child: Consumer<ThemeController>(
    builder: (context, themeController, _) => MaterialApp(
      theme: AppTheme.lightTheme,
      darkTheme: AppTheme.darkTheme,
      themeMode: themeController.themeMode,
      home: const HomePage(),
    ),
  ),
)

二、自定义颜色与字体系统

2.1 Material 3 颜色系统

dart 复制代码
// 从种子色生成完整颜色系统
final colorScheme = ColorScheme.fromSeed(
  seedColor: const Color(0xFF6750A4),
  brightness: Brightness.light,
);

// 颜色角色对应表
// primary         → 主要操作按钮背景
// onPrimary       → primary 上的文字/图标
// primaryContainer → 次要容器背景
// secondary       → 次要操作
// surface         → 卡片、对话框背景
// onSurface       → surface 上的文字
// error           → 错误状态

// 在 Widget 中使用
Widget build(BuildContext context) {
  final colorScheme = Theme.of(context).colorScheme;
  return Container(
    color: colorScheme.primaryContainer,
    child: Text(
      'Hello',
      style: TextStyle(color: colorScheme.onPrimaryContainer),
    ),
  );
}

2.2 扩展主题颜色(自定义颜色)

dart 复制代码
// 定义扩展颜色
@immutable
class AppColors extends ThemeExtension<AppColors> {
  final Color success;
  final Color warning;
  final Color info;
  final Color gradientStart;
  final Color gradientEnd;

  const AppColors({
    required this.success,
    required this.warning,
    required this.info,
    required this.gradientStart,
    required this.gradientEnd,
  });

  @override
  AppColors copyWith({
    Color? success,
    Color? warning,
    Color? info,
    Color? gradientStart,
    Color? gradientEnd,
  }) {
    return AppColors(
      success: success ?? this.success,
      warning: warning ?? this.warning,
      info: info ?? this.info,
      gradientStart: gradientStart ?? this.gradientStart,
      gradientEnd: gradientEnd ?? this.gradientEnd,
    );
  }

  @override
  AppColors lerp(AppColors? other, double t) {
    if (other == null) return this;
    return AppColors(
      success: Color.lerp(success, other.success, t)!,
      warning: Color.lerp(warning, other.warning, t)!,
      info: Color.lerp(info, other.info, t)!,
      gradientStart: Color.lerp(gradientStart, other.gradientStart, t)!,
      gradientEnd: Color.lerp(gradientEnd, other.gradientEnd, t)!,
    );
  }

  // 亮色配置
  static const light = AppColors(
    success: Color(0xFF4CAF50),
    warning: Color(0xFFFF9800),
    info: Color(0xFF2196F3),
    gradientStart: Color(0xFF6750A4),
    gradientEnd: Color(0xFF03DAC6),
  );

  // 暗色配置
  static const dark = AppColors(
    success: Color(0xFF81C784),
    warning: Color(0xFFFFB74D),
    info: Color(0xFF64B5F6),
    gradientStart: Color(0xFFD0BCFF),
    gradientEnd: Color(0xFF6FF7EC),
  );
}

// 注册到 ThemeData
ThemeData get lightTheme => ThemeData(
  extensions: const [AppColors.light],
  // ...
)

// 在 Widget 中使用
final appColors = Theme.of(context).extension<AppColors>()!;
Container(color: appColors.success)

2.3 自定义字体

yaml 复制代码
# pubspec.yaml
flutter:
  fonts:
    - family: Outfit
      fonts:
        - asset: assets/fonts/Outfit-Regular.ttf
          weight: 400
        - asset: assets/fonts/Outfit-Medium.ttf
          weight: 500
        - asset: assets/fonts/Outfit-Bold.ttf
          weight: 700
dart 复制代码
ThemeData get theme => ThemeData(
  fontFamily: 'Outfit', // 全局字体
  textTheme: const TextTheme(
    headlineLarge: TextStyle(
      fontFamily: 'Outfit',
      fontSize: 32,
      fontWeight: FontWeight.w700,
    ),
  ),
)

2.4 动态主题色(用户自定义)

dart 复制代码
class DynamicThemeController extends ChangeNotifier {
  Color _seedColor = const Color(0xFF6750A4);

  Color get seedColor => _seedColor;

  ThemeData get lightTheme => ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(
      seedColor: _seedColor,
      brightness: Brightness.light,
    ),
  );

  ThemeData get darkTheme => ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(
      seedColor: _seedColor,
      brightness: Brightness.dark,
    ),
  );

  void updateSeedColor(Color color) {
    _seedColor = color;
    notifyListeners();
  }
}

小结

知识点 核心要点
ThemeData 定义全局视觉风格,lightTheme + darkTheme
ThemeMode system / light / dark 三种切换模式
ColorScheme.fromSeed 从种子色自动生成 Material 3 完整颜色系统
ThemeExtension 扩展自定义颜色,支持亮/暗色自动切换
字体配置 pubspec.yaml 注册,fontFamily 全局应用

👉 下一节:6.2 国际化(i18n)

相关推荐
卸任1 小时前
Vibe coding工具使用心得
前端·ai编程
wuxianda10302 小时前
2026 苹果审核 4.3 (a) 被拒全解析|Guideline 4.3a spam 马甲包拒审原因 + 避坑方案
flutter·uni-app·苹果上架
brien92 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance2 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M2 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写2 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李2 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易2 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马3 小时前
前沿视界
前端
kyriewen3 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员