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...

相关推荐
似霰11 分钟前
安卓adb shell串口基础指令
android·adb
fatiaozhang95272 小时前
中兴云电脑W102D_晶晨S905X2_2+16G_mt7661无线_安卓9.0_线刷固件包
android·adb·电视盒子·魔百盒刷机·魔百盒固件
CYRUS_STUDIO3 小时前
Android APP 热修复原理
android·app·hotfix
鸿蒙布道师4 小时前
鸿蒙NEXT开发通知工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师4 小时前
鸿蒙NEXT开发网络相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
大耳猫4 小时前
【解决】Android Gradle Sync 报错 Could not read workspace metadata
android·gradle·android studio
ta叫我小白4 小时前
实现 Android 图片信息获取和 EXIF 坐标解析
android·exif·经纬度
bst@微胖子5 小时前
Flutter之路由和导航
flutter
dpxiaolong5 小时前
RK3588平台用v4l工具调试USB摄像头实践(亮度,饱和度,对比度,色相等)
android·windows
亚洲小炫风6 小时前
flutter 中各种日志
前端·flutter·日志·log