Flutter 页面嵌入 Android原生 View

前言

文章主要讲解Flutter页面如何使用Android原生View,但用到了Flutter 和 Android原生 相互通信知识,建议先看完这篇讲解通信的文章

juejin.cn/spost/73257...

数据观察监听,Flutter使用ValueNotifier,Android原生使用LiveData,在实体数据发生改变时,自动刷新。

效果图

图解

1、Android原生端

1.0 PlatformView

Android:ComputeLayoutPlatform.kt

js 复制代码
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引擎上注册的唯一标识,在Flutter端使用时必须一样
        private const val ANDROID_SEND_FLUTTER_DATA_NOTICE: String = "androidSendFlutterDataNotice" // Android端 向 Flutter端 发送数据
        private const val ANDROID_GET_FLUTTER_DATA_NOTICE: String = "androidGetFlutterDataNotice" // Android端 获取 Flutter端 数据
        private const val FLUTTER_SEND_ANDROID_DATA_NOTICE: String = "flutterSendAndroidDataNotice" // Flutter端 向 Android端 发送数据
        private const val FLUTTER_GET_ANDROID_DATA_NOTICE: String = "flutterGetAndroidDataNotice" // Flutter端 获取 Android端 数据
    }

    init {
        initChannel(messenger, viewId)
        initView()
        initData(rootContext, args)
    }

    /**
     * 初始化消息通道
     */
    private fun initChannel(messenger: BinaryMessenger, viewId: Int) {
        // 创建 Android端和Flutter端的,相互通信的通道
        // 通道名称,两端必须一致
        mChannel = MethodChannel(messenger, "flutter.mix.android/compute/$viewId")

        // 监听来自 Flutter端 的消息通道
        // Flutter端调用了函数,这个handler函数就会被触发
        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端 发送数据,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端 未实现 Android端 定义的接口方法
                 */
                override fun notImplemented() {
                    Log.d("TAG", "notImplemented")
                }
            })
    }

    /**
     * Android端 获取 Flutter端 数据,GET 操作
     */
    private fun androidGetFlutterData() {
        // 说一个坑,不传参数可以写null,
        // 但不能这样写,目前它没有这个重载方法,invokeMethod第二个参数是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端 未实现 Android端 定义的接口方法
                 */
                override fun notImplemented() {
                    Log.d("TAG", "notImplemented")
                }
            })
    }

    /**
     * 初始化数据
     */
    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 传递的参数
        val map: Map<String, Int> = args as Map<String, Int>
        viewModel.getFlutterNum.value = map["flutterNum"]
    }

    /**
     * 监听来自 Flutter端 的消息通道
     *
     * call: Android端 接收到 Flutter端 发来的 数据对象
     * result:Android端 给 Flutter端 执行回调的接口对象
     */
    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        // 获取调用函数的名称
        val methodName: String = call.method
        when (methodName) {
            FLUTTER_SEND_ANDROID_DATA_NOTICE -> {
                // 回调结果对象
                // 获取Flutter端传过来的数据
                val flutterCount: Int? = call.argument<Int>("flutterNum")
                updateFlutterNum(flutterCount ?: 0)
                result.success("success")

                // 回调状态接口对象,里面有三个回调方法
                // 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() {
        // 解除绑定
        mChannel.setMethodCallHandler(null)
    }

}

1.1 PlatformViewFactory

Android:ComputeLayoutPlatformFactory.kt

js 复制代码
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工厂,创建PlatformView
 */
class ComputeLayoutPlatformFactory(
    private val rootContext: Context,
    private val messenger: BinaryMessenger, // 二进制信使
) : 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

js 复制代码
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引擎上注册的唯一标识,在Flutter端使用时必须一样
        private const val viewType: String = "com.example.flutter_mix_android.ui.flutterplugin.platform/ComputeLayoutPlatform"
    }

    /**
     * 连接到flutter引擎时调用
     */
    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        // 将Android原生View 在Flutter引擎上注册
        binding.platformViewRegistry.registerViewFactory(
            viewType,
            ComputeLayoutPlatformFactory(rootContext, binding.binaryMessenger)
        )
    }

    /**
     * 与flutter引擎分离时调用
     */
    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {}

}

1.3 注册插件

Android:MainActivity.kt

Ps:建议大家直接使用FlutterFragmentActivity平替掉FlutterActivity,因为

FlutterActivity继承于Activity

FlutterFragmentActivity继承于FragmentActivity,它实现了 LifecycleOwnerViewModelStoreOwner

js 复制代码
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

js 复制代码
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端点击次数

    var flutterNum: MutableLiveData<Int> = MutableLiveData<Int>() // Flutter端点击次数(接收到的)

    var getFlutterNum: MutableLiveData<Int> = MutableLiveData<Int>() // Flutter端点击次数(主动获取的)

}

2、Flutter端

1.0 页面完整代码

js 复制代码
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引擎上注册的唯一标识,在Flutter端使用时必须一样
  final String viewType = 'com.example.flutter_mix_android.ui.flutterplugin.platform/ComputeLayoutPlatform';
  static const String FLUTTER_SEND_ANDROID_DATA_NOTICE = 'flutterSendAndroidDataNotice'; // Flutter端 向 Android端 发送数据
  static const String FLUTTER_GET_ANDROID_DATA_NOTICE = 'flutterGetAndroidDataNotice'; // Flutter端 获取 Android端 数据
  static const String ANDROID_SEND_FLUTTER_DATA_NOTICE = 'androidSendFlutterDataNotice'; // Android端 向 Flutter端 发送数据
  static const String ANDROID_GET_FLUTTER_DATA_NOTICE = 'androidGetFlutterDataNotice'; // Android端 获取 Flutter端 数据

  /// 初始化消息通道
  initChannel(int viewId) {
    channel = MethodChannel('flutter.mix.android/compute/$viewId'); // 创建 Flutter端和Android端的,相互通信的通道

    // 监听来自 Android端 的消息通道
    // Android端调用了函数,这个handler函数就会被触发
    channel.setMethodCallHandler(handler);
  }

  /// 监听来自 Android端 的消息通道
  /// Android端调用了函数,这个handler函数就会被触发
  Future<dynamic> handler(MethodCall call) async {
    // 获取调用函数的名称
    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端具体实现函数', details: '具体描述');
        }
    }
  }

  /// Flutter端 向 Android端 发送数据,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端具体实现函数');
      } else {
        debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Error:$e');
      }
    });
  }

  ///  Flutter端 获取 Android端 数据,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端具体实现函数');
      } else {
        debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Error:$e');
      }
    });
  }

  /// 累计点击次数
  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('点击次数:$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('发送给Android端'),
                )
              ],
            ),
          ),
          Padding(
            padding: const EdgeInsets.only(bottom: 8),
            child: Row(
              children: [
                ValueListenableBuilder(
                    valueListenable: countBean.getAndroidNum,
                    builder: (context, count, _) {
                      return Text('获取Android页面点击次数:$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端数据'),
                  ),
                ),
              ],
            ),
          ),
          ValueListenableBuilder(
              valueListenable: countBean.androidNum,
              builder: (context, count, _) {
                return Text('接收Android端发送的点击次数:$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引擎上注册的唯一标识,在Flutter端使用时必须一样
                    creationParams: {'flutterNum': countBean.curNum.value}, // Flutter端 初始化时 向Android端 传递的参数
                    creationParamsCodec: const StandardMessageCodec(), // 消息编解码器
                    onPlatformViewCreated: (viewId) {
                      initChannel(viewId);
                      // 使用 viewId 构建不同名称的 MethodChannel,
                      // 主要应用于 多个相同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

js 复制代码
import 'package:flutter/cupertino.dart';

class CountBean {

  ValueNotifier<int> curNum = ValueNotifier<int>(10); // Flutter端点击次数

  ValueNotifier<int> androidNum = ValueNotifier<int>(0); // Android端点击次数(接收到的)

  ValueNotifier<int> getAndroidNum = ValueNotifier<int>(0); // Android端点击次数(主动获取的)

}

6、源码地址

github.com/LanSeLianMa...

相关推荐
比格丽巴格丽抱5 小时前
flutter项目苹果编译运行打包上线
flutter·ios
长亭外的少年6 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
SoaringHeart6 小时前
Flutter进阶:基于 MLKit 的 OCR 文字识别
前端·flutter
建群新人小猿8 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神9 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛9 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
AiFlutter10 小时前
Flutter通过 Coap发送组播
flutter
Y多了个想法10 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter11 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快12 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android