Flutter 主题管理:构建一致的用户界面

Flutter 主题管理:构建一致的用户界面

掌握 Flutter 主题管理的核心概念和最佳实践。

一、主题管理的重要性

作为一名追求像素级还原的 UI 匠人,我深知主题管理在 Flutter 开发中的重要性。良好的主题管理能够确保应用在不同设备和场景下保持一致的视觉风格,提高代码的可维护性和可扩展性。从浅色模式到深色模式,从品牌色彩到字体样式,主题管理为我们提供了统一的样式控制机制。

二、基础主题

1. 内置主题

dart 复制代码
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. 深色模式

dart 复制代码
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. 主题数据类

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,
    ),
  );

  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. 主题扩展

dart 复制代码
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. 主题状态管理

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final themeProvider = StateProvider<ThemeMode>((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. 保存主题设置

dart 复制代码
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class ThemeManager {
  static const String _themeKey = 'theme_mode';

  static Future<ThemeMode> 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 Future<void> 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 State<MyApp> {
  ThemeMode _themeMode = ThemeMode.system;
  bool _isLoaded = false;

  @override
  void initState() {
    super.initState();
    _loadTheme();
  }

  Future<void> _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. 完整的主题系统

dart 复制代码
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 = StateNotifierProvider<ThemeNotifier, ThemeMode>((ref) {
  return ThemeNotifier();
});

class ThemeNotifier extends StateNotifier<ThemeMode> {
  ThemeNotifier() : super(ThemeMode.system) {
    _loadTheme();
  }

  Future<void> _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;
    }
  }

  Future<void> 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: [
          PopupMenuButton<ThemeMode>(
            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)),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

六、最佳实践

  1. 统一管理:将主题相关的代码集中管理,便于维护
  2. 使用扩展:创建主题扩展,简化主题属性的访问
  3. 保存设置:使用 SharedPreferences 保存用户的主题偏好
  4. 响应式设计:确保主题在不同屏幕尺寸下都能正常显示
  5. 测试:在不同主题模式下测试应用的表现
  6. 性能优化:避免频繁的主题切换导致的重建

七、常见问题

1. 主题切换不生效

dart 复制代码
// 确保使用 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. 主题属性不应用

dart 复制代码
// 确保正确使用主题属性
Text('文本', style: Theme.of(context).textTheme.bodyText1);

// 或使用扩展
Text('文本', style: context.bodyText1);

3. 性能问题

dart 复制代码
// 避免在 build 方法中创建主题数据
// 正确的做法是在类级别定义主题数据
class AppTheme {
  static final lightTheme = ThemeData(...);
  static final darkTheme = ThemeData(...);
}

良好的主题管理能够确保应用的视觉一致性,提升用户体验,同时简化代码维护。

#flutter #theme #ui #dark-mode #dart

相关推荐
龙猫里的小梅啊2 小时前
CSS(八)CSS显示模式display属性
前端·css·css3
雨季mo浅忆2 小时前
第二项目重新梳理
前端·面试
李白的天不白2 小时前
webpack 与 vue-loader 版本冲突问题
前端·vue.js·webpack
sunneo2 小时前
专栏D-团队与组织-05-冲突与决策
前端·人工智能·产品运营·aigc·产品经理·ai-native
舟遥遥娓飘飘2 小时前
量化投资体系之二:为 Web 看板集成公众号/财经原始数据
前端·数据分析·自动化·ai编程
ZC跨境爬虫2 小时前
跟着 MDN 学 HTML day_13:多媒体嵌入 —— 视频与音频
前端·css·笔记·ui·html·音视频
之歆2 小时前
DAY12_CSS3选择器全攻略 + 盒子新特性完全指南(上)
前端·css·css3
之歆2 小时前
DAY13_CSS3进阶完全指南 —— 背景、边框、文本、渐变、滤镜与 Web 字体(上)
前端·c#·css3
幸福巡礼2 小时前
【 LangChain 1.2 实战(四)】构建一个模块化的天气查询 Agent
java·前端·langchain