Skip to content

主题定义

本篇介绍在实战项目中如何统一定义主题:把颜色、字体等抽成常量,封装亮色/暗色主题,并在 MaterialApp 和页面中使用。基础概念可参考 Flutter主题风格

为什么要在项目中集中定义主题

  • 统一风格:所有页面共用一套颜色和字体,避免到处写死 Colors.xxxfontSize: 16
  • 易维护:改品牌色或字体时只改一处即可全局生效。
  • 支持暗色:一套亮色、一套暗色,交给系统或用户切换。

第一步:定义颜色与字体常量

lib/constants/(或 lib/theme/)下新建 app_colors.dartapp_text_styles.dart(也可合并到一个文件),把设计稿中的主色、背景色、文字色、字号等抽成常量。

示例(颜色):

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

class AppColors {
  AppColors._();

  // 主色
  static const Color primary = Color(0xFF2196F3);
  static const Color primaryDark = Color(0xFF1976D2);

  // 背景
  static const Color backgroundLight = Color(0xFFF5F5F5);
  static const Color backgroundDark = Color(0xFF121212);

  // 文字
  static const Color textPrimary = Color(0xFF212121);
  static const Color textSecondary = Color(0xFF757575);
  static const Color textOnPrimary = Colors.white;
}

示例(文字样式,可按需增减):

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

class AppTextStyles {
  AppTextStyles._();

  static const double fontSizeTitle = 20.0;
  static const double fontSizeBody = 16.0;
  static const double fontSizeCaption = 12.0;

  static TextStyle title(Color color) => TextStyle(
        fontSize: fontSizeTitle,
        fontWeight: FontWeight.w600,
        color: color,
      );

  static TextStyle body(Color color) => TextStyle(
        fontSize: fontSizeBody,
        color: color,
      );

  static TextStyle caption(Color color) => TextStyle(
        fontSize: fontSizeCaption,
        color: color,
      );
}

这样后续改主色或字号时,只改这些常量即可。

第二步:封装 AppTheme(亮色 + 暗色)

lib/theme/app_theme.dart 中基于 ThemeData 封装亮色和暗色两套主题,内部引用上面的颜色和字体常量。

dart
import 'package:flutter/material.dart';
import '../constants/app_colors.dart';
import '../constants/app_text_styles.dart';

class AppTheme {
  AppTheme._();

  static ThemeData get lightTheme {
    return ThemeData(
      useMaterial3: true,
      brightness: Brightness.light,
      primaryColor: AppColors.primary,
      scaffoldBackgroundColor: AppColors.backgroundLight,
      colorScheme: ColorScheme.fromSeed(
        seedColor: AppColors.primary,
        brightness: Brightness.light,
      ),
      appBarTheme: AppBarTheme(
        backgroundColor: AppColors.primary,
        foregroundColor: AppColors.textOnPrimary,
        elevation: 0,
      ),
      textTheme: TextTheme(
        titleLarge: AppTextStyles.title(AppColors.textPrimary),
        bodyMedium: AppTextStyles.body(AppColors.textPrimary),
        bodySmall: AppTextStyles.caption(AppColors.textSecondary),
      ),
    );
  }

  static ThemeData get darkTheme {
    return ThemeData(
      useMaterial3: true,
      brightness: Brightness.dark,
      primaryColor: AppColors.primary,
      scaffoldBackgroundColor: AppColors.backgroundDark,
      colorScheme: ColorScheme.fromSeed(
        seedColor: AppColors.primary,
        brightness: Brightness.dark,
      ),
      appBarTheme: AppBarTheme(
        backgroundColor: AppColors.backgroundDark,
        foregroundColor: AppColors.textOnPrimary,
        elevation: 0,
      ),
      textTheme: TextTheme(
        titleLarge: AppTextStyles.title(Colors.white),
        bodyMedium: AppTextStyles.body(Colors.white70),
        bodySmall: AppTextStyles.caption(Colors.white54),
      ),
    );
  }
}

注意:Flutter 3.x 推荐用 colorSchemeuseMaterial3,组件会优先从 ThemeData.colorScheme 取色;若需兼容旧项目,可继续使用 primaryColoraccentColor 等。

第三步:在 MaterialApp 中挂载主题

在入口(如 lib/main.dartlib/app.dart)的 MaterialApp 中传入 themedarkTheme,系统会根据「系统是否深色」自动切换(无需额外代码):

dart
import 'package:flutter/material.dart';
import 'theme/app_theme.dart';
import 'screens/home/home_screen.dart';

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      theme: AppTheme.lightTheme,
      darkTheme: AppTheme.darkTheme,
      themeMode: ThemeMode.system, // 跟随系统;也可 ThemeMode.light / ThemeMode.dark 写死
      home: const HomeScreen(),
    );
  }
}

若希望用户手动切换主题,可把 themeMode 存到状态(如 Provider/GetX)或本地,再根据用户选择设为 ThemeMode.light / ThemeMode.dark

第四步:在页面中使用主题

  • 用主题颜色/文字:通过 Theme.of(context)Theme.of(context).colorSchemetextTheme 取,不要再次写死颜色(除非是设计稿明确只此一处的色值)。
dart
// 取主题色
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;

Container(
  color: colorScheme.primary,
  child: Text(
    '标题',
    style: theme.textTheme.titleLarge,
  ),
)
  • 取我们封装的常量:在需要和设计稿完全一致的地方,可直接用 AppColors.primaryAppTextStyles.body(AppColors.textPrimary) 等。

这样既保证全局一致,又能在个别页面做精细控制。

局部覆盖主题(可选)

某个页面或某块区域想临时换一套主题(例如深色弹窗里的按钮),可以用 Theme 包裹子组件,并用 Theme.of(context).copyWith(...) 在原有主题上只改部分属性:

dart
Theme(
  data: Theme.of(context).copyWith(
    colorScheme: Theme.of(context).colorScheme.copyWith(primary: Colors.orange),
  ),
  child: const SomeChild(),
)

小结

  • constants/theme/ 中定义 AppColorsAppTextStyles 等常量,避免魔法数字。
  • AppTheme 中封装 lightThemedarkTheme,并挂到 MaterialAppthemedarkThemethemeMode
  • 页面中优先使用 Theme.of(context).colorSchemetheme.textTheme,需要时再配合 AppColors/AppTextStyles

按以上步骤即可在项目中形成一套清晰、可维护的主题定义,并为后续 屏幕适配多环境配置 打好基础。