前言
文章主要講解Flutter頁面如何使用Android原生View,但用到了Flutter 和 Android原生 相互通信知識,建議先看完這篇講解通信的文章
Flutter 與 Android原生 相互通信:BasicMessageChannel、MethodChannel、EventChannel-CSDN博客
數(shù)據(jù)觀察監(jiān)聽,F(xiàn)lutter使用ValueNotifier,Android原生使用LiveData,在實體數(shù)據(jù)發(fā)生改變時,自動刷新。
效果圖
圖解
1、Android原生端
1.0 PlatformView
Android:ComputeLayoutPlatform.kt
package com.example.flutter_mix_android.ui.flutterplugin.platform;
import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.lifecycle.ViewModelProvider
import com.example.flutter_mix_android.R
import com.example.flutter_mix_android.bean.CountBean
import com.example.flutter_mix_android.databinding.LayoutComputeBinding
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView
/**
* 封裝成PlatformView
*/
class ComputeLayoutPlatform(
context: Context,
rootContext: Context,
messenger: BinaryMessenger,
viewId: Int,
args: Any?,
) : FrameLayout(context), PlatformView, MethodChannel.MethodCallHandler {
private lateinit var mChannel: MethodChannel
private lateinit var bind: LayoutComputeBinding
private lateinit var viewModel: CountBean
companion object {
// Android原生View 在Flutter引擎上注冊的唯一標(biāo)識,在Flutter端使用時必須一樣
private const val ANDROID_SEND_FLUTTER_DATA_NOTICE: String = "androidSendFlutterDataNotice" // Android端 向 Flutter端 發(fā)送數(shù)據(jù)
private const val ANDROID_GET_FLUTTER_DATA_NOTICE: String = "androidGetFlutterDataNotice" // Android端 獲取 Flutter端 數(shù)據(jù)
private const val FLUTTER_SEND_ANDROID_DATA_NOTICE: String = "flutterSendAndroidDataNotice" // Flutter端 向 Android端 發(fā)送數(shù)據(jù)
private const val FLUTTER_GET_ANDROID_DATA_NOTICE: String = "flutterGetAndroidDataNotice" // Flutter端 獲取 Android端 數(shù)據(jù)
}
init {
initChannel(messenger, viewId)
initView()
initData(rootContext, args)
}
/**
* 初始化消息通道
*/
private fun initChannel(messenger: BinaryMessenger, viewId: Int) {
// 創(chuàng)建 Android端和Flutter端的,相互通信的通道
// 通道名稱,兩端必須一致
mChannel = MethodChannel(messenger, "flutter.mix.android/compute/$viewId")
// 監(jiān)聽來自 Flutter端 的消息通道
// Flutter端調(diào)用了函數(shù),這個handler函數(shù)就會被觸發(fā)
mChannel.setMethodCallHandler(this)
}
/**
* 初始化視圖
*/
private fun initView() {
LayoutInflater.from(context).inflate(R.layout.layout_compute, this, true)
bind = LayoutComputeBinding.bind(getChildAt(0))
bind.add.setOnClickListener {
val count: Int = viewModel.curNum.value ?: 0
viewModel.curNum.value = count + 1
}
bind.androidSendFlutterData.setOnClickListener {
androidSendFlutterData()
}
bind.androidGetFlutterData.setOnClickListener {
androidGetFlutterData()
}
}
/**
* Android端 向 Flutter端 發(fā)送數(shù)據(jù),PUT 操作
*/
private fun androidSendFlutterData() {
val map: MutableMap<String, Int> = mutableMapOf<String, Int>()
map["androidNum"] = viewModel.curNum.value ?: 0
mChannel.invokeMethod(
ANDROID_SEND_FLUTTER_DATA_NOTICE,
map,
object : MethodChannel.Result {
override fun success(result: Any?) {
Log.d("TAG", "success:$result")
updateFlutterNum((result as? Int) ?: 0)
}
override fun error(
errorCode: String,
errorMessage: String?,
errorDetails: Any?
) {
Log.d(
"TAG",
"errorCode:$errorCode --- errorMessage:$errorMessage --- errorDetails:$errorDetails"
)
}
/**
* Flutter端 未實現(xiàn) Android端 定義的接口方法
*/
override fun notImplemented() {
Log.d("TAG", "notImplemented")
}
})
}
/**
* Android端 獲取 Flutter端 數(shù)據(jù),GET 操作
*/
private fun androidGetFlutterData() {
// 說一個坑,不傳參數(shù)可以寫null,
// 但不能這樣寫,目前它沒有這個重載方法,invokeMethod第二個參數(shù)是Object類型,所以編譯器不會提示錯誤
// mChannel.invokeMethod(ANDROID_GET_FLUTTER_DATA_NOTICE, object : MethodChannel.Result {
// public void invokeMethod(@NonNull String method, @Nullable Object arguments)
mChannel.invokeMethod(
ANDROID_GET_FLUTTER_DATA_NOTICE,
null,
object : MethodChannel.Result {
override fun success(result: Any?) {
Log.d("TAG", "success:$result")
updateGetFlutterNum((result as? Int) ?: 0)
}
override fun error(
errorCode: String,
errorMessage: String?,
errorDetails: Any?
) {
Log.d(
"TAG",
"errorCode:$errorCode --- errorMessage:$errorMessage --- errorDetails:$errorDetails"
)
}
/**
* Flutter端 未實現(xiàn) Android端 定義的接口方法
*/
override fun notImplemented() {
Log.d("TAG", "notImplemented")
}
})
}
/**
* 初始化數(shù)據(jù)
*/
private fun initData(rootContext: Context, args: Any?) {
val owner = rootContext as FlutterFragmentActivity
viewModel = ViewModelProvider(owner)[CountBean::class.java]
bind.countBean = viewModel
bind.lifecycleOwner = owner
// 獲取初始化時 Flutter端 向 Android 傳遞的參數(shù)
val map: Map<String, Int> = args as Map<String, Int>
viewModel.getFlutterNum.value = map["flutterNum"]
}
/**
* 監(jiān)聽來自 Flutter端 的消息通道
*
* call: Android端 接收到 Flutter端 發(fā)來的 數(shù)據(jù)對象
* result:Android端 給 Flutter端 執(zhí)行回調(diào)的接口對象
*/
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
// 獲取調(diào)用函數(shù)的名稱
val methodName: String = call.method
when (methodName) {
FLUTTER_SEND_ANDROID_DATA_NOTICE -> {
// 回調(diào)結(jié)果對象
// 獲取Flutter端傳過來的數(shù)據(jù)
val flutterCount: Int? = call.argument<Int>("flutterNum")
updateFlutterNum(flutterCount ?: 0)
result.success("success")
// 回調(diào)狀態(tài)接口對象,里面有三個回調(diào)方法
// result.success(result: Any?)
// result.error(errorCode: String, errorMessage: String?, errorDetails: Any?)
// result.notImplemented()
}
FLUTTER_GET_ANDROID_DATA_NOTICE -> {
result.success(viewModel.curNum.value)
}
else -> {
result.notImplemented()
}
}
}
fun updateFlutterNum(flutterCount: Int) {
viewModel.flutterNum.value = flutterCount
}
fun updateGetFlutterNum(flutterCount: Int) {
viewModel.getFlutterNum.value = flutterCount
}
override fun getView(): View? {
return this
}
override fun dispose() {}
}
1.1 PlatformViewFactory
Android:ComputeLayoutPlatformFactory.kt
package com.example.flutter_mix_android.ui.flutterplugin.factory
import android.content.Context
import com.example.flutter_mix_android.ui.flutterplugin.platform.ComputeLayoutPlatform
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
/**
* 通過PlatformView工廠,創(chuàng)建PlatformView
*/
class ComputeLayoutPlatformFactory(
private val rootContext: Context,
private val messenger: BinaryMessenger, // 二進(jìn)制信使
) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { // 消息編解碼器
private lateinit var computeLayoutPlatform: ComputeLayoutPlatform
override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
computeLayoutPlatform = ComputeLayoutPlatform(context, rootContext, messenger, viewId, args)
return computeLayoutPlatform
}
}
1.2 FlutterPlugin
Android:FlutterPlugin.kt
package com.example.flutter_mix_android.ui.flutterplugin.plugin;
import android.content.Context
import com.example.flutter_mix_android.ui.flutterplugin.factory.ComputeLayoutPlatformFactory
import io.flutter.embedding.engine.plugins.FlutterPlugin
/**
* 將AndroidView 注冊為 Flutter插件
*
* rootContext:這個context,我是用來作ViewModel觀察的,setLifecycleOwner
*/
class ComputeLayoutPlugin(private val rootContext: Context) : FlutterPlugin {
companion object {
// Android原生View 在Flutter引擎上注冊的唯一標(biāo)識,在Flutter端使用時必須一樣
private const val viewType: String = "com.example.flutter_mix_android.ui.flutterplugin.platform/ComputeLayoutPlatform"
}
/**
* 連接到flutter引擎時調(diào)用
*/
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
// 將Android原生View 在Flutter引擎上注冊
binding.platformViewRegistry.registerViewFactory(
viewType,
ComputeLayoutPlatformFactory(rootContext, binding.binaryMessenger)
)
}
/**
* 與flutter引擎分離時調(diào)用
*/
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {}
}
1.3 注冊插件
Android:MainActivity.kt
Ps:建議大家直接使用FlutterFragmentActivity平替掉FlutterActivity,因為
FlutterActivity繼承于Activity;
FlutterFragmentActivity繼承于FragmentActivity,它實現(xiàn)了 LifecycleOwner 和 ViewModelStoreOwner;文章來源:http://www.zghlxwxcb.cn/news/detail-810635.html
package com.example.flutter_mix_android.ui.activity
import com.example.flutter_mix_android.ui.flutterplugin.plugin.ComputeLayoutPlugin
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity: FlutterFragmentActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// 注冊為Flutter插件
flutterEngine.plugins.add(ComputeLayoutPlugin(this))
}
}
1.4?實體 + LiveData
package com.example.flutter_mix_android.bean
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class CountBean : ViewModel() {
var curNum: MutableLiveData<Int> = MutableLiveData<Int>() // Android端點擊次數(shù)
var flutterNum: MutableLiveData<Int> = MutableLiveData<Int>() // Flutter端點擊次數(shù)(接收到的)
var getFlutterNum: MutableLiveData<Int> = MutableLiveData<Int>() // Flutter端點擊次數(shù)(主動獲取的)
}
2、Flutter端
1.0 頁面完整代碼
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mix_android/bean/count_bean.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final CountBean countBean = CountBean();
late MethodChannel channel;
// Android原生View 在Flutter引擎上注冊的唯一標(biāo)識,在Flutter端使用時必須一樣
final String viewType = 'com.example.flutter_mix_android.ui.flutterplugin.platform/ComputeLayoutPlatform';
static const String FLUTTER_SEND_ANDROID_DATA_NOTICE = 'flutterSendAndroidDataNotice'; // Flutter端 向 Android端 發(fā)送數(shù)據(jù)
static const String FLUTTER_GET_ANDROID_DATA_NOTICE = 'flutterGetAndroidDataNotice'; // Flutter端 獲取 Android端 數(shù)據(jù)
static const String ANDROID_SEND_FLUTTER_DATA_NOTICE = 'androidSendFlutterDataNotice'; // Android端 向 Flutter端 發(fā)送數(shù)據(jù)
static const String ANDROID_GET_FLUTTER_DATA_NOTICE = 'androidGetFlutterDataNotice'; // Android端 獲取 Flutter端 數(shù)據(jù)
/// 初始化消息通道
initChannel(int viewId) {
channel = MethodChannel('flutter.mix.android/compute/$viewId'); // 創(chuàng)建 Flutter端和Android端的,相互通信的通道
// 監(jiān)聽來自 Android端 的消息通道
// Android端調(diào)用了函數(shù),這個handler函數(shù)就會被觸發(fā)
channel.setMethodCallHandler(handler);
}
/// 監(jiān)聽來自 Android端 的消息通道
/// Android端調(diào)用了函數(shù),這個handler函數(shù)就會被觸發(fā)
Future<dynamic> handler(MethodCall call) async {
// 獲取調(diào)用函數(shù)的名稱
final String methodName = call.method;
switch (methodName) {
case ANDROID_SEND_FLUTTER_DATA_NOTICE:
{
int androidCount = call.arguments['androidNum'];
countBean.androidNum.value = androidCount;
return '$ANDROID_SEND_FLUTTER_DATA_NOTICE ---> success';
}
case ANDROID_GET_FLUTTER_DATA_NOTICE:
{
return countBean.curNum.value ?? 0;
}
default:
{
return PlatformException(
code: '-1', message: '未找到Flutter端具體實現(xiàn)函數(shù)', details: '具體描述');
}
}
}
/// Flutter端 向 Android端 發(fā)送數(shù)據(jù),PUT 操作
flutterSendAndroidData() {
Map<String, int> map = {'flutterNum': countBean.curNum.value};
channel.invokeMethod(FLUTTER_SEND_ANDROID_DATA_NOTICE, map).then((value) {
debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Result:$value');
}).catchError((e) {
if (e is MissingPluginException) {
debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Error:notImplemented --- 未找到Android端具體實現(xiàn)函數(shù)');
} else {
debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Error:$e');
}
});
}
/// Flutter端 獲取 Android端 數(shù)據(jù),GET 操作
flutterGetAndroidData() {
channel.invokeMethod(FLUTTER_GET_ANDROID_DATA_NOTICE).then((value) {
debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Result:$value');
countBean.getAndroidNum.value = value ?? 0;
}).catchError((e) {
if (e is MissingPluginException) {
debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Error:notImplemented --- 未找到Android端具體實現(xiàn)函數(shù)');
} else {
debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Error:$e');
}
});
}
/// 累計點擊次數(shù)
computeCount() {
countBean.curNum.value += 1;
}
Widget computeWidget() {
final ButtonStyle btnStyle = ElevatedButton.styleFrom(
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 12),
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(35)));
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Flutter頁面',
style: TextStyle(
color: Color(0xff0066ff),
fontSize: 20,
fontWeight: FontWeight.bold),
),
Padding(
padding: const EdgeInsets.only(top: 16, bottom: 8),
child: Row(
children: [
ValueListenableBuilder<int>(
valueListenable: countBean.curNum,
builder: (context, count, _) {
return Text('點擊次數(shù):$count',
style: const TextStyle(fontSize: 16));
}),
Padding(
padding: const EdgeInsets.only(left: 16, right: 8),
child: ElevatedButton(
style: btnStyle,
onPressed: computeCount,
child: const Text('+1'),
),
),
ElevatedButton(
style: btnStyle,
onPressed: flutterSendAndroidData,
child: const Text('發(fā)送給Android端'),
)
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
ValueListenableBuilder(
valueListenable: countBean.getAndroidNum,
builder: (context, count, _) {
return Text('獲取Android頁面點擊次數(shù):$count',
style: const TextStyle(fontSize: 16));
}),
Padding(
padding: const EdgeInsets.only(left: 16, right: 3),
child: ElevatedButton(
style: btnStyle,
onPressed: flutterGetAndroidData,
child: const Text('獲取Android端數(shù)據(jù)'),
),
),
],
),
),
ValueListenableBuilder(
valueListenable: countBean.androidNum,
builder: (context, count, _) {
return Text('接收Android端發(fā)送的點擊次數(shù):$count',
style: const TextStyle(fontSize: 16));
}),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xffA4D3EE),
body: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: SafeArea(
top: true,
child: Column(
children: [
Expanded(
flex: 1,
child: AndroidView(
viewType: viewType, // Android原生View 在Flutter引擎上注冊的唯一標(biāo)識,在Flutter端使用時必須一樣
creationParams: {'flutterNum': countBean.curNum.value}, // Flutter端 初始化時 向Android端 傳遞的參數(shù)
creationParamsCodec: const StandardMessageCodec(), // 消息編解碼器
onPlatformViewCreated: (viewId) {
initChannel(viewId);
// 使用 viewId 構(gòu)建不同名稱的 MethodChannel,
// 主要應(yīng)用于 多個相同AndroidView一起使用時,避免消息沖突
// List<MethodChannel> mChannels = [];
// mChannels.add(MethodChannel('flutter.mix.android/compute/$viewId'));
// mChannels[0].invokeMethod(method)
// mChannels[0].setMethodCallHandler((call) => null)
},
)),
Expanded(flex: 1, child: computeWidget()),
],
),
),
),
);
}
}
1.1 實體 +?ValueNotifier
import 'package:flutter/cupertino.dart';
class CountBean {
ValueNotifier<int> curNum = ValueNotifier<int>(10); // Flutter端點擊次數(shù)
ValueNotifier<int> androidNum = ValueNotifier<int>(0); // Android端點擊次數(shù)(接收到的)
ValueNotifier<int> getAndroidNum = ValueNotifier<int>(0); // Android端點擊次數(shù)(主動獲取的)
}
6、源碼地址
https://github.com/LanSeLianMa/flutter_mix_android文章來源地址http://www.zghlxwxcb.cn/news/detail-810635.html
到了這里,關(guān)于Flutter 頁面嵌入 Android原生 View的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!