Flutter与原生混合开发:实现完美的暗夜模式同步方案

在 Flutter 与原生混合开发中保证暗夜模式一致性,需要从 Flutter 端和原生端(Android/iOS)同时进行配置和同步。以下是完整的解决方案:

  1. Flutter 端配置

1.1 使用 Provider 状态管理(推荐)

dart 复制代码
// 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 配置

dart 复制代码
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => ThemeProvider(),
      child: const MyApp(),
    ),
  );
}

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

  @override
  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(),
        );
      },
    );
  }
}
  1. Android 端配置

2.1 在 MainActivity 中同步主题

kotlin 复制代码
// MainActivity.kt
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class 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_MASK
                    val isDark = nightMode == android.content.res.Configuration.UI_MODE_NIGHT_YES
                    result.success(isDark)
                }
                "setAppTheme" -> {
                    val isDark = call.arguments as Boolean
                    setAppTheme(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 配置

xml 复制代码
<application
    android:name=".MainApplication"
    android:theme="@style/LaunchTheme">
    
    <activity
        android:name=".MainActivity"
        android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
        android:hardwareAccelerated="true"
        android:theme="@style/NormalTheme">
        <!-- 其他配置 -->
    </activity>
</application>
  1. iOS 端配置

3.1 在 AppDelegate 中同步主题

swift 复制代码
// 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! FlutterViewController
        let channel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: controller.binaryMessenger)
        
        channel.setMethodCallHandler({
            [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            switch 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 == .dark
            result(isDark)
        } else {
            result(false) // iOS 13以下默认亮色
        }
    }
    
    private func setAppTheme(isDark: Bool) {
        // 设置原生端主题
        if #available(iOS 13.0, *) {
            window?.overrideUserInterfaceStyle = isDark ? .dark : .light
        }
    }
}
  1. Flutter 与原生通信

4.1 创建通信工具类

dart 复制代码
// 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 在主题提供者中同步

dart 复制代码
// 修改 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();
  }
}
  1. 监听系统主题变化

5.1 Flutter 端监听

dart 复制代码
// 在 main.dart 或首页中添加
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _initTheme();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangePlatformBrightness() {
    // 系统亮度变化时同步
    context.read<ThemeProvider>().syncWithSystem();
    super.didChangePlatformBrightness();
  }

  void _initTheme() async {
    // 初始化时同步主题
    await context.read<ThemeProvider>().syncWithSystem();
  }

  @override
  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('主题同步示例'),
      ),
    );
  }
}
  1. 持久化存储
dart 复制代码
// 使用 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();
  }
}
  1. 使用注意事项

  2. 初始化顺序:在应用启动时先加载保存的主题设置

  3. 错误处理:妥善处理平台通信可能出现的异常

  4. 性能考虑:避免频繁的主题切换操作

  5. 测试:分别在亮色和暗色模式下测试所有界面

这样配置后,你的 Flutter 与原生混合应用就能在各个平台上保持暗夜模式的一致性了。

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone1 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui