Flutter与原生混合开发:实现完美的暗夜模式同步方案
在 Flutter 与原生混合开发中保证暗夜模式一致性,需要从 Flutter 端和原生端(Android/iOS)同时进行配置和同步。以下是完整的解决方案:
- Flutter 端配置
1.1 使用 Provider 状态管理(推荐)
// theme_provider.dart
import 'package:flutter/material.dart';class ThemeProvider with ChangeNotifier {ThemeMode _themeMode = ThemeMode.system;ThemeMode get themeMode => _themeMode;bool get isDarkMode => _themeMode == ThemeMode.dark;void setThemeMode(ThemeMode mode) {_themeMode = mode;notifyListeners();}void toggleTheme() {_themeMode = _themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;notifyListeners();}
}
1.2 MaterialApp 配置
void main() {runApp(ChangeNotifierProvider(create: (context) => ThemeProvider(),child: const MyApp(),),);
}class MyApp extends StatelessWidget {const MyApp({super.key});Widget build(BuildContext context) {return Consumer<ThemeProvider>(builder: (context, themeProvider, child) {return MaterialApp(theme: ThemeData.light().copyWith(// 自定义亮色主题primaryColor: Colors.blue,scaffoldBackgroundColor: Colors.white,),darkTheme: ThemeData.dark().copyWith(// 自定义暗色主题primaryColor: Colors.blue[700],scaffoldBackgroundColor: Colors.grey[900],),themeMode: themeProvider.themeMode,home: const HomePage(),);},);}
}
- Android 端配置
2.1 在 MainActivity 中同步主题
// MainActivity.kt
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannelclass MainActivity: FlutterActivity() {private val CHANNEL = "theme_channel"override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {call, result ->when (call.method) {"getSystemTheme" -> {val nightMode = resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASKval isDark = nightMode == android.content.res.Configuration.UI_MODE_NIGHT_YESresult.success(isDark)}"setAppTheme" -> {val isDark = call.arguments as BooleansetAppTheme(isDark)result.success(null)}else -> result.notImplemented()}}}private fun setAppTheme(isDark: Boolean) {// 设置原生端主题if (isDark) {// 应用暗色主题setTheme(android.R.style.Theme_DeviceDefault_Dark)} else {// 应用亮色主题setTheme(android.R.style.Theme_DeviceDefault_Light)}recreate() // 重新创建Activity应用主题}
}
2.2 AndroidManifest.xml 配置
<applicationandroid:name=".MainApplication"android:theme="@style/LaunchTheme"><activityandroid:name=".MainActivity"android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"android:hardwareAccelerated="true"android:theme="@style/NormalTheme"><!-- 其他配置 --></activity>
</application>
- iOS 端配置
3.1 在 AppDelegate 中同步主题
// AppDelegate.swift
import UIKit
import Flutter@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {let CHANNEL = "theme_channel"override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {let controller : FlutterViewController = window?.rootViewController as! FlutterViewControllerlet channel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: controller.binaryMessenger)channel.setMethodCallHandler({[weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void inswitch call.method {case "getSystemTheme":self?.getSystemTheme(result: result)case "setAppTheme":if let arguments = call.arguments as? [String: Any],let isDark = arguments["isDark"] as? Bool {self?.setAppTheme(isDark: isDark)result(nil)} else {result(FlutterError(code: "INVALID_ARGUMENT", message: "Invalid arguments", details: nil))}default:result(FlutterMethodNotImplemented)}})GeneratedPluginRegistrant.register(with: self)return super.application(application, didFinishLaunchingWithOptions: launchOptions)}private func getSystemTheme(result: FlutterResult) {if #available(iOS 13.0, *) {let isDark = UITraitCollection.current.userInterfaceStyle == .darkresult(isDark)} else {result(false) // iOS 13以下默认亮色}}private func setAppTheme(isDark: Bool) {// 设置原生端主题if #available(iOS 13.0, *) {window?.overrideUserInterfaceStyle = isDark ? .dark : .light}}
}
- Flutter 与原生通信
4.1 创建通信工具类
// native_theme_handler.dart
import 'package:flutter/services.dart';class NativeThemeHandler {static const MethodChannel _channel = MethodChannel('theme_channel');// 获取系统主题static Future<bool> getSystemTheme() async {try {final bool isDark = await _channel.invokeMethod('getSystemTheme');return isDark;} on PlatformException {return false;}}// 设置原生端主题static Future<void> setNativeTheme(bool isDark) async {try {await _channel.invokeMethod('setAppTheme', {'isDark': isDark});} on PlatformException catch (e) {print('Failed to set native theme: ${e.message}');}}
}
4.2 在主题提供者中同步
// 修改 theme_provider.dart
class ThemeProvider with ChangeNotifier {// ... 其他代码Future<void> syncWithSystem() async {final bool isSystemDark = await NativeThemeHandler.getSystemTheme();_themeMode = isSystemDark ? ThemeMode.dark : ThemeMode.light;await NativeThemeHandler.setNativeTheme(isSystemDark);notifyListeners();}Future<void> setThemeAndSync(ThemeMode mode) async {_themeMode = mode;await NativeThemeHandler.setNativeTheme(mode == ThemeMode.dark);notifyListeners();}
}
- 监听系统主题变化
5.1 Flutter 端监听
// 在 main.dart 或首页中添加
class HomePage extends StatefulWidget {const HomePage({super.key});State<HomePage> createState() => _HomePageState();
}class _HomePageState extends State<HomePage> with WidgetsBindingObserver {void initState() {super.initState();WidgetsBinding.instance.addObserver(this);_initTheme();}void dispose() {WidgetsBinding.instance.removeObserver(this);super.dispose();}void didChangePlatformBrightness() {// 系统亮度变化时同步context.read<ThemeProvider>().syncWithSystem();super.didChangePlatformBrightness();}void _initTheme() async {// 初始化时同步主题await context.read<ThemeProvider>().syncWithSystem();}Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('主题演示'),actions: [IconButton(icon: const Icon(Icons.brightness_6),onPressed: () {context.read<ThemeProvider>().toggleTheme();NativeThemeHandler.setNativeTheme(context.read<ThemeProvider>().isDarkMode);},),],),body: const Center(child: Text('主题同步示例'),),);}
}
- 持久化存储
// 使用 shared_preferences 保存主题偏好
import 'package:shared_preferences/shared_preferences.dart';class ThemeProvider with ChangeNotifier {// ... 其他代码Future<void> loadTheme() async {final prefs = await SharedPreferences.getInstance();final themeIndex = prefs.getInt('themeMode') ?? ThemeMode.system.index;_themeMode = ThemeMode.values[themeIndex];// 如果是系统模式,同步系统主题if (_themeMode == ThemeMode.system) {await syncWithSystem();} else {await NativeThemeHandler.setNativeTheme(_themeMode == ThemeMode.dark);}notifyListeners();}Future<void> saveTheme() async {final prefs = await SharedPreferences.getInstance();await prefs.setInt('themeMode', _themeMode.index);}Future<void> setThemeAndSync(ThemeMode mode) async {_themeMode = mode;await NativeThemeHandler.setNativeTheme(mode == ThemeMode.dark);await saveTheme();notifyListeners();}
}
-
使用注意事项
-
初始化顺序:在应用启动时先加载保存的主题设置
-
错误处理:妥善处理平台通信可能出现的异常
-
性能考虑:避免频繁的主题切换操作
-
测试:分别在亮色和暗色模式下测试所有界面
这样配置后,你的 Flutter 与原生混合应用就能在各个平台上保持暗夜模式的一致性了。