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)

相关推荐
张元清4 小时前
5 分钟用 Vite SSR 搭建一个全栈 React 应用
前端·javascript·面试
踩着两条虫4 小时前
效率翻倍!AI智能体深度解析:自然语言 → DSL → Vue组件
前端·人工智能·低代码
北京智和信通4 小时前
基于IPMI的服务器集中监控与带外管理方案
服务器·服务器监控·ipmi·网管平台·带外监控
昔我往昔4 小时前
Linux修改静态ip
linux·服务器·tcp/ip
吴声子夜歌4 小时前
Vue3——条件判断指令
前端·es6
snow_yan4 小时前
AI 对话流式输出: 实现“逐字丝滑、不闪烁、不卡顿”的打字机效果
前端·react.js·openai
Devin_chen5 小时前
Pinia 渐进式学习指南
前端·vue.js
你听得到115 小时前
周下载60w,但是作者删库!我从本地 pub 缓存里把它救出来,顺手备份到了自己的 GitHub
前端·flutter
PeterMap5 小时前
Vue组合式API响应式状态声明:ref与reactive实战解析
前端·vue.js