Flutter 主题管理:构建一致的用户界面
Flutter 主题管理构建一致的用户界面掌握 Flutter 主题管理的核心概念和最佳实践。一、主题管理的重要性作为一名追求像素级还原的 UI 匠人我深知主题管理在 Flutter 开发中的重要性。良好的主题管理能够确保应用在不同设备和场景下保持一致的视觉风格提高代码的可维护性和可扩展性。从浅色模式到深色模式从品牌色彩到字体样式主题管理为我们提供了统一的样式控制机制。二、基础主题1. 内置主题import package:flutter/material.dart; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { override Widget build(BuildContext context) { return MaterialApp( title: Flutter 主题示例, theme: ThemeData( primaryColor: Colors.blue, accentColor: Colors.purple, brightness: Brightness.light, fontFamily: Roboto, textTheme: TextTheme( headline1: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), bodyText1: TextStyle(fontSize: 16), ), ), home: HomePage(), ); } } class HomePage extends StatelessWidget { override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(主题示例)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(Hello, Flutter!, style: Theme.of(context).textTheme.headline1), SizedBox(height: 20), ElevatedButton( onPressed: () {}, child: Text(按钮), ), ], ), ), ); } }2. 深色模式class MyApp extends StatelessWidget { override Widget build(BuildContext context) { return MaterialApp( title: Flutter 主题示例, theme: ThemeData( primaryColor: Colors.blue, accentColor: Colors.purple, brightness: Brightness.light, ), darkTheme: ThemeData( primaryColor: Colors.blueGrey, accentColor: Colors.purpleAccent, brightness: Brightness.dark, ), themeMode: ThemeMode.system, // 跟随系统设置 home: HomePage(), ); } }三、自定义主题1. 主题数据类class AppTheme { static final lightTheme ThemeData( primaryColor: Color(0xFF667eea), secondaryHeaderColor: Color(0xFF764ba2), backgroundColor: Colors.white, scaffoldBackgroundColor: Color(0xFFf5f5f5), textTheme: TextTheme( headline1: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black), bodyText1: TextStyle(fontSize: 16, color: Colors.black87), bodyText2: TextStyle(fontSize: 14, color: Colors.black54), ), buttonTheme: ButtonThemeData( buttonColor: Color(0xFF667eea), textTheme: ButtonTextTheme.primary, ), ); static final darkTheme ThemeData( primaryColor: Color(0xFF8b5cf6), secondaryHeaderColor: Color(0xFFa78bfa), backgroundColor: Color(0xFF121212), scaffoldBackgroundColor: Color(0xFF1e1e1e), textTheme: TextTheme( headline1: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white), bodyText1: TextStyle(fontSize: 16, color: Colors.white70), bodyText2: TextStyle(fontSize: 14, color: Colors.white54), ), buttonTheme: ButtonThemeData( buttonColor: Color(0xFF8b5cf6), textTheme: ButtonTextTheme.primary, ), ); } class MyApp extends StatelessWidget { override Widget build(BuildContext context) { return MaterialApp( title: Flutter 主题示例, theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, themeMode: ThemeMode.system, home: HomePage(), ); } }2. 主题扩展extension ThemeExtension on BuildContext { Color get primaryColor Theme.of(this).primaryColor; Color get secondaryColor Theme.of(this).secondaryHeaderColor; Color get backgroundColor Theme.of(this).backgroundColor; TextStyle? get headline1 Theme.of(this).textTheme.headline1; TextStyle? get bodyText1 Theme.of(this).textTheme.bodyText1; TextStyle? get bodyText2 Theme.of(this).textTheme.bodyText2; } class HomePage extends StatelessWidget { override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(主题示例)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(Hello, Flutter!, style: context.headline1), SizedBox(height: 20), Container( width: 200, height: 200, color: context.primaryColor, child: Center( child: Text(主题颜色, style: TextStyle(color: Colors.white)), ), ), SizedBox(height: 20), ElevatedButton( onPressed: () {}, child: Text(按钮), ), ], ), ), ); } }四、动态主题切换1. 主题状态管理import package:flutter/material.dart; import package:flutter_riverpod/flutter_riverpod.dart; final themeProvider StateProviderThemeMode((ref) ThemeMode.system); class MyApp extends StatelessWidget { override Widget build(BuildContext context) { return ProviderScope( child: Consumer( builder: (context, ref, child) { final themeMode ref.watch(themeProvider); return MaterialApp( title: Flutter 主题示例, theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, themeMode: themeMode, home: HomePage(), ); }, ), ); } } class HomePage extends StatelessWidget { override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(主题示例)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(Hello, Flutter!, style: context.headline1), SizedBox(height: 20), ElevatedButton( onPressed: () { context.read(themeProvider).state ThemeMode.light; }, child: Text(浅色模式), ), SizedBox(height: 10), ElevatedButton( onPressed: () { context.read(themeProvider).state ThemeMode.dark; }, child: Text(深色模式), ), SizedBox(height: 10), ElevatedButton( onPressed: () { context.read(themeProvider).state ThemeMode.system; }, child: Text(跟随系统), ), ], ), ), ); } }2. 保存主题设置import package:flutter/material.dart; import package:shared_preferences/shared_preferences.dart; class ThemeManager { static const String _themeKey theme_mode; static FutureThemeMode getThemeMode() async { final prefs await SharedPreferences.getInstance(); final themeModeString prefs.getString(_themeKey); switch (themeModeString) { case light: return ThemeMode.light; case dark: return ThemeMode.dark; default: return ThemeMode.system; } } static Futurevoid setThemeMode(ThemeMode themeMode) async { final prefs await SharedPreferences.getInstance(); String themeModeString; switch (themeMode) { case ThemeMode.light: themeModeString light; break; case ThemeMode.dark: themeModeString dark; break; default: themeModeString system; } await prefs.setString(_themeKey, themeModeString); } } class MyApp extends StatefulWidget { override _MyAppState createState() _MyAppState(); } class _MyAppState extends StateMyApp { ThemeMode _themeMode ThemeMode.system; bool _isLoaded false; override void initState() { super.initState(); _loadTheme(); } Futurevoid _loadTheme() async { final themeMode await ThemeManager.getThemeMode(); setState(() { _themeMode themeMode; _isLoaded true; }); } void _changeTheme(ThemeMode mode) { setState(() { _themeMode mode; }); ThemeManager.setThemeMode(mode); } override Widget build(BuildContext context) { if (!_isLoaded) { return Container(color: Colors.white); } return MaterialApp( title: Flutter 主题示例, theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, themeMode: _themeMode, home: HomePage( currentTheme: _themeMode, onThemeChange: _changeTheme, ), ); } } class HomePage extends StatelessWidget { final ThemeMode currentTheme; final Function(ThemeMode) onThemeChange; HomePage({required this.currentTheme, required this.onThemeChange}); override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(主题示例)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(当前主题: ${_getThemeName(currentTheme)}, style: context.headline1), SizedBox(height: 20), ElevatedButton( onPressed: () onThemeChange(ThemeMode.light), child: Text(浅色模式), ), SizedBox(height: 10), ElevatedButton( onPressed: () onThemeChange(ThemeMode.dark), child: Text(深色模式), ), SizedBox(height: 10), ElevatedButton( onPressed: () onThemeChange(ThemeMode.system), child: Text(跟随系统), ), ], ), ), ); } String _getThemeName(ThemeMode mode) { switch (mode) { case ThemeMode.light: return 浅色; case ThemeMode.dark: return 深色; default: return 系统; } } }五、实战案例1. 完整的主题系统import package:flutter/material.dart; import package:flutter_riverpod/flutter_riverpod.dart; import package:shared_preferences/shared_preferences.dart; // 主题数据 class AppTheme { static final lightTheme ThemeData( primaryColor: Color(0xFF667eea), secondaryHeaderColor: Color(0xFF764ba2), backgroundColor: Colors.white, scaffoldBackgroundColor: Color(0xFFf5f5f5), textTheme: TextTheme( headline1: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black), bodyText1: TextStyle(fontSize: 16, color: Colors.black87), bodyText2: TextStyle(fontSize: 14, color: Colors.black54), ), buttonTheme: ButtonThemeData( buttonColor: Color(0xFF667eea), textTheme: ButtonTextTheme.primary, ), cardTheme: CardTheme( elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), ); static final darkTheme ThemeData( primaryColor: Color(0xFF8b5cf6), secondaryHeaderColor: Color(0xFFa78bfa), backgroundColor: Color(0xFF121212), scaffoldBackgroundColor: Color(0xFF1e1e1e), textTheme: TextTheme( headline1: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white), bodyText1: TextStyle(fontSize: 16, color: Colors.white70), bodyText2: TextStyle(fontSize: 14, color: Colors.white54), ), buttonTheme: ButtonThemeData( buttonColor: Color(0xFF8b5cf6), textTheme: ButtonTextTheme.primary, ), cardTheme: CardTheme( elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), color: Color(0xFF2d2d2d), ), ); } // 主题扩展 extension ThemeExtension on BuildContext { Color get primaryColor Theme.of(this).primaryColor; Color get secondaryColor Theme.of(this).secondaryHeaderColor; Color get backgroundColor Theme.of(this).backgroundColor; TextStyle? get headline1 Theme.of(this).textTheme.headline1; TextStyle? get bodyText1 Theme.of(this).textTheme.bodyText1; TextStyle? get bodyText2 Theme.of(this).textTheme.bodyText2; CardTheme get cardTheme Theme.of(this).cardTheme; } // 主题状态管理 final themeProvider StateNotifierProviderThemeNotifier, ThemeMode((ref) { return ThemeNotifier(); }); class ThemeNotifier extends StateNotifierThemeMode { ThemeNotifier() : super(ThemeMode.system) { _loadTheme(); } Futurevoid _loadTheme() async { final prefs await SharedPreferences.getInstance(); final themeModeString prefs.getString(theme_mode); switch (themeModeString) { case light: state ThemeMode.light; break; case dark: state ThemeMode.dark; break; default: state ThemeMode.system; } } Futurevoid setThemeMode(ThemeMode mode) async { state mode; final prefs await SharedPreferences.getInstance(); String themeModeString; switch (mode) { case ThemeMode.light: themeModeString light; break; case ThemeMode.dark: themeModeString dark; break; default: themeModeString system; } await prefs.setString(theme_mode, themeModeString); } } // 应用入口 void main() { runApp(ProviderScope(child: MyApp())); } class MyApp extends StatelessWidget { override Widget build(BuildContext context) { return Consumer( builder: (context, ref, child) { final themeMode ref.watch(themeProvider); return MaterialApp( title: Flutter 主题示例, theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, themeMode: themeMode, home: HomePage(), ); }, ); } } // 首页 class HomePage extends StatelessWidget { override Widget build(BuildContext context) { final themeNotifier context.read(themeProvider.notifier); return Scaffold( appBar: AppBar( title: Text(主题管理示例), actions: [ PopupMenuButtonThemeMode( onSelected: (mode) { themeNotifier.setThemeMode(mode); }, itemBuilder: (context) [ PopupMenuItem( value: ThemeMode.light, child: Text(浅色模式), ), PopupMenuItem( value: ThemeMode.dark, child: Text(深色模式), ), PopupMenuItem( value: ThemeMode.system, child: Text(跟随系统), ), ], ), ], ), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(主题管理, style: context.headline1), SizedBox(height: 20), Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(卡片标题, style: context.bodyText1), SizedBox(height: 8), Text(这是卡片内容展示了主题在不同模式下的表现。, style: context.bodyText2), ], ), ), ), SizedBox(height: 20), ElevatedButton( onPressed: () {}, child: Text(测试按钮), ), SizedBox(height: 20), Container( width: double.infinity, height: 100, decoration: BoxDecoration( color: context.primaryColor, borderRadius: BorderRadius.circular(8), ), child: Center( child: Text(主题颜色, style: TextStyle(color: Colors.white, fontSize: 18)), ), ), ], ), ), ); } }六、最佳实践统一管理将主题相关的代码集中管理便于维护使用扩展创建主题扩展简化主题属性的访问保存设置使用 SharedPreferences 保存用户的主题偏好响应式设计确保主题在不同屏幕尺寸下都能正常显示测试在不同主题模式下测试应用的表现性能优化避免频繁的主题切换导致的重建七、常见问题1. 主题切换不生效// 确保使用 Consumer 或 ProviderListener 监听主题变化 Consumer( builder: (context, ref, child) { final themeMode ref.watch(themeProvider); return MaterialApp( theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, themeMode: themeMode, home: HomePage(), ); }, )2. 主题属性不应用// 确保正确使用主题属性 Text(文本, style: Theme.of(context).textTheme.bodyText1); // 或使用扩展 Text(文本, style: context.bodyText1);3. 性能问题// 避免在 build 方法中创建主题数据 // 正确的做法是在类级别定义主题数据 class AppTheme { static final lightTheme ThemeData(...); static final darkTheme ThemeData(...); }良好的主题管理能够确保应用的视觉一致性提升用户体验同时简化代码维护。#flutter #theme #ui #dark-mode #dart