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

相关推荐
Chengbei118 分钟前
云安全漏洞挖掘SKILL、一站式云漏洞挖掘工具,支持S3爆破、IMDS探测、K8s检测与AK/SK权限利用
前端·人工智能·网络安全·云原生·容器·kubernetes·系统安全
不简说36 分钟前
JS 代码技巧 vol.9 — 20 个设计模式在真实项目里的应用
前端·javascript·github
CoderLiu41 分钟前
Agent 工程的下一层:为什么「把流程写成图」还不够,还需要 Graph Engineering
前端·人工智能·后端
勇往直前plus1 小时前
Vue3(篇三) Element Plus
前端·javascript·typescript·vue
爱勇宝1 小时前
《道德经》第 8 章:真正高级的能力,像水一样成事
前端·后端·程序员
Revolution611 小时前
数组本身没有 map,为什么还能直接调用:原型链怎样查找属性
前端·javascript·面试
bonechips1 小时前
RAG实战(一):EPUB 加载、文本切割与向量入库
前端·数据库
swipe1 小时前
10|(前端转全栈)库存扣减为什么最容易出事故?SKU、并发与原子更新
前端·后端·全栈
cdcdhj1 小时前
vue3中的watchEffect()监听,什么时候监听,什么时候清理,什么时候停止
前端·javascript·vue.js
huabuyu1 小时前
几百 MB 的文件为什么等几分钟才能打开?文件分片下载与渐进预览原理
前端·javascript