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

发布于:2025-09-12 ⋅ 阅读:(22) ⋅ 点赞:(0)

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

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

2.1 在 MainActivity 中同步主题

// 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 配置

<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 中同步主题

// 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 创建通信工具类

// 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();
  }
}
  1. 监听系统主题变化

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('主题同步示例'),
      ),
    );
  }
}
  1. 持久化存储
// 使用 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 与原生混合应用就能在各个平台上保持暗夜模式的一致性了。


网站公告

今日签到

点亮在社区的每一天
去签到