1. 项目配置
pubspec.yaml
添加依赖
yaml
dependencies:
flutter:
sdk: flutter
provider: ^6.0.5
2. Flutter 端实现
状态管理类
dart
// settings_provider.dart
import 'package:flutter/foundation.dart';
class SettingsProvider with ChangeNotifier {
String _themeColor = 'blue';
bool _darkMode = false;
String get themeColor => _themeColor;
bool get darkMode => _darkMode;
void updateSettings(String color, bool dark) {
_themeColor = color;
_darkMode = dark;
notifyListeners();
}
}
主界面
dart
// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter/services.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => SettingsProvider(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const MainPage(),
);
}
}
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
static const platform = MethodChannel('com.example.app/settings');
@override
void initState() {
super.initState();
_initMethodChannel();
}
void _initMethodChannel() {
platform.setMethodCallHandler((call) async {
if (call.method == 'updateSettings') {
final args = call.arguments as Map<dynamic, dynamic>;
Provider.of<SettingsProvider>(context, listen: false).updateSettings(
args['color'] as String,
args['darkMode'] as bool,
);
}
return null;
});
}
@override
Widget build(BuildContext context) {
final settings = Provider.of<SettingsProvider>(context);
return Scaffold(
appBar: AppBar(
title: const Text('主界面'),
backgroundColor: _getColor(settings.themeColor),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'当前主题色: ${settings.themeColor}',
style: TextStyle(
color: settings.darkMode ? Colors.white : Colors.black,
),
),
const SizedBox(height: 20),
Text(
'暗黑模式: ${settings.darkMode ? "开启" : "关闭"}',
style: TextStyle(
color: settings.darkMode ? Colors.white : Colors.black,
),
),
const SizedBox(height: 40),
ElevatedButton(
onPressed: _openNativeSettings,
child: const Text('打开原生设置页面'),
),
],
),
),
backgroundColor: settings.darkMode ? Colors.grey[800] : Colors.white,
);
}
Color _getColor(String colorName) {
return switch (colorName) {
'red' => Colors.red,
'green' => Colors.green,
_ => Colors.blue,
};
}
Future<void> _openNativeSettings() async {
try {
final settings = Provider.of<SettingsProvider>(context, listen: false);
await platform.invokeMethod('openSettings', {
'color': settings.themeColor,
'darkMode': settings.darkMode,
});
} on PlatformException catch (e) {
debugPrint("打开设置失败: ${e.message}");
}
}
}
3. Android 原生端实现 (Kotlin)
MainActivity.kt
kotlin
package com.example.myapp
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example.app/settings"
private var latestSettings: Map<String, Any>? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"openSettings" -> {
val color = call.argument<String>("color") ?: "blue"
val darkMode = call.argument<Boolean>("darkMode") ?: false
Intent(this, SettingsActivity::class.java).apply {
putExtra("color", color)
putExtra("darkMode", darkMode)
startActivityForResult(this, 101)
}
result.success(null)
}
else -> result.notImplemented()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 101 && resultCode == RESULT_OK) {
data?.extras?.let { extras ->
val settings = mapOf(
"color" to extras.getString("color", "blue"),
"darkMode" to extras.getBoolean("darkMode", false)
)
latestSettings = settings
Handler(Looper.getMainLooper()).post {
MethodChannel(flutterEngine?.dartExecutor, CHANNEL).invokeMethod(
"updateSettings",
settings
)
}
}
}
}
}
SettingsActivity.kt
kotlin
package com.example.myapp
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.myapp.databinding.ActivitySettingsBinding
class SettingsActivity : AppCompatActivity() {
private lateinit var binding: ActivitySettingsBinding
private var selectedColor = "blue"
private var darkMode = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
// 获取初始值
selectedColor = intent.getStringExtra("color") ?: "blue"
darkMode = intent.getBooleanExtra("darkMode", false)
// 初始化UI状态
when (selectedColor) {
"red" -> binding.colorGroup.check(R.id.red)
"green" -> binding.colorGroup.check(R.id.green)
else -> binding.colorGroup.check(R.id.blue)
}
binding.darkModeSwitch.isChecked = darkMode
// 保存按钮点击
binding.saveButton.setOnClickListener {
selectedColor = when (binding.colorGroup.checkedRadioButtonId) {
R.id.red -> "red"
R.id.green -> "green"
else -> "blue"
}
darkMode = binding.darkModeSwitch.isChecked
Intent().apply {
putExtra("color", selectedColor)
putExtra("darkMode", darkMode)
setResult(RESULT_OK, this)
}
finish()
}
}
}
activity_settings.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="主题颜色"
android:textSize="18sp"/>
<RadioGroup
android:id="@+id/colorGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/red"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="红色"/>
<RadioButton
android:id="@+id/green"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="绿色"
android:layout_marginStart="16dp"/>
<RadioButton
android:id="@+id/blue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="蓝色"
android:layout_marginStart="16dp"/>
</RadioGroup>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="暗黑模式"
android:textSize="18sp"/>
<Switch
android:id="@+id/darkModeSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"/>
</LinearLayout>
<Button
android:id="@+id/saveButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存设置"
android:layout_marginTop="32dp"/>
</LinearLayout>
AndroidManifest.xml
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application
android:label="MyApp"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:theme="@style/Theme.AppCompat.Light.DialogWhenLarge"
android:exported="false" />
</application>
</manifest>
4. 关键点总结
-
通信流程:
- Flutter → 通过
MethodChannel
调用原生方法 - Android → 通过
startActivityForResult
启动设置页 - 设置页 → 返回结果通过
MethodChannel
传回Flutter
- Flutter → 通过
-
Kotlin特性利用:
- 使用
lateinit
延迟初始化绑定 - 使用
apply
简化对象配置 - 使用
when
表达式替代 switch-case
- 使用
-
类型安全:
- 使用Kotlin的空安全操作符
?
- 明确指定泛型类型
Map<String, Any>
- 使用Kotlin的空安全操作符
-
线程安全:
- 通过
Handler(Looper.getMainLooper())
确保在主线程更新UI
- 通过
-
资源管理:
- 使用 View Binding (
ActivitySettingsBinding
) - 合理处理 Activity 生命周期
- 使用 View Binding (
这个实现完整展示了 Flutter 与 Android 原生页面之间的双向通信,所有代码均采用 Kotlin 编写,符合现代 Android 开发最佳实践。