HarmonyOS鸿蒙Next中flutter使用platformView怎么同时构建不同组件供flutter端使用

HarmonyOS鸿蒙Next中flutter使用platformView怎么同时构建不同组件供flutter端使用 f目前在学习flutter混合鸿蒙的方法中,想要在尝试在flutter混合鸿蒙组件的场景,官方提供的方案是单个组件混合,多个组件分场景调用的话需要怎么实现呢

flutter_samples/ohos/docs/04_development/如何使用PlatformView.md-代码预览-flutter_samples:基于 OpenHarmony 生态的 Flutter 开发示例项目 - AtomGit | GitCode

这个是官方代码的示例


更多关于HarmonyOS鸿蒙Next中flutter使用platformView怎么同时构建不同组件供flutter端使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

10 回复

尊敬的开发者,您好,
请问您是需要在flutter中根据不同的场景切换platformView显示的组件吗?还请看下通过if语句切换显示的组件的方式是否满足您的需求呢:

import MethodChannel, {
  MethodCallHandler,
  MethodResult
} from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel';
import PlatformView, { Params } from '@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformView';
import common from '@ohos.app.ability.common';
import { BinaryMessenger } from '@ohos/flutter_ohos/src/main/ets/plugin/common/BinaryMessenger';
import StandardMethodCodec from '@ohos/flutter_ohos/src/main/ets/plugin/common/StandardMethodCodec';
import MethodCall from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodCall';
import { webview } from '@kit.ArkWeb';
import {
  photoAccessHelper,
  RecentPhotoOptions,
  PhotoSource,
  RecentPhotoCheckResultCallback,
  RecentPhotoClickCallback,
  BaseItemInfo
} from '@kit.MediaLibraryKit';
import { RecentPhotoCheckInfoCallback, RecentPhotoInfo } from '@ohos.file.RecentPhotoComponent';

@Component
struct ButtonComponent {
  @ObjectLink params: Params
  customView: CustomView = this.params.platformView as CustomView
  @StorageLink('numValue') storageLink: string = "first"
  @StorageLink('viewCase') viewCase: number = 1;
  @State bkColor: Color = Color.Red
  private webviewController: webview.WebviewController = new webview.WebviewController();
  private recentPhotoOptions: RecentPhotoOptions = new RecentPhotoOptions();
  private recentPhotoCheckResultCallback: RecentPhotoCheckResultCallback =
    (recentPhotoExists: boolean) => this.onRecentPhotoCheckResult(recentPhotoExists);
  private recentPhotoClickCallback: RecentPhotoClickCallback =
    (recentPhotoInfo: BaseItemInfo): boolean => this.onRecentPhotoClick(recentPhotoInfo);
  private recentPhotoCheckInfoCallback: RecentPhotoCheckInfoCallback =
    (recentPhotoExists: boolean, info: RecentPhotoInfo) => this.onRecentPhotoCheckInfo(recentPhotoExists, info);

  aboutToAppear() {
    this.recentPhotoOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE;
    this.recentPhotoOptions.period = 30;
    this.recentPhotoOptions.photoSource = PhotoSource.ALL;
  }

  private onRecentPhotoCheckResult(recentPhotoExists: boolean): void {
    // 存在符合条件的照片或视频。
    if (recentPhotoExists) {
      console.info('The photo is exist.');
    }
  }

  private onRecentPhotoClick(recentPhotoInfo: BaseItemInfo): boolean {
    // 照片或视频返回。
    if (recentPhotoInfo) {
      console.info('The photo uri is ' + recentPhotoInfo.uri);
      return true;
    }
    return true;
  }

  private onRecentPhotoCheckInfo(recentPhotoExists: boolean, info: RecentPhotoInfo): void {
    // 是否存在符合条件的照片或视频,若存在则可以拿到该照片或视频的相关信息。
  }

  build() {
    Column() {
      Text('viewCase: ' + this.viewCase)
      if (this.viewCase === 1) {
        Column() {
          Button("发送数据给Flutter")
            .border({ width: 2, color: Color.Blue })
            .backgroundColor(this.bkColor)
            .onTouch((event: TouchEvent) => {
              console.log("nodeController button on touched")
            })
            .onClick((event: ClickEvent) => {
              this.customView.sendMessage();
              console.log("nodeController button on click")
            })
          Text(`来自Flutter的数据 : ${this.storageLink}`)
            .onTouch((event: TouchEvent) => {
              console.log("nodeController text on touched")
            })
        }
        .height('50%')
        .width('50%')
        .backgroundColor(Color.Pink)
      } else if (this.viewCase === 2) {
        Column() {
          Button("发送数据给Flutter")
            .border({ width: 2, color: Color.Blue })
            .backgroundColor(this.bkColor)
            .onTouch((event: TouchEvent) => {
              console.log("nodeController button on touched")
            })
            .onClick((event: ClickEvent) => {
              this.customView.sendMessage();
              console.log("nodeController button on click")
            })
          Text(`来自Flutter的数据 : ${this.storageLink}`)
            .onTouch((event: TouchEvent) => {
              console.log("nodeController text on touched")
            })
        }
        .height('50%')
        .width('50%')
        .backgroundColor(Color.Yellow)
      }
    }
  }
}

@Builder
function ButtonBuilder(params: Params) {
  ButtonComponent({ params: params })
}

AppStorage.setOrCreate('numValue', 'test')
AppStorage.setOrCreate('viewCase', 1)

@Observed
export class CustomView extends PlatformView implements MethodCallHandler {
  numValue: string = "test";
  viewCase: number = 1;
  methodChannel: MethodChannel;
  index: number = 1;

  constructor(context: common.Context, viewId: number, args: ESObject, message: BinaryMessenger) {
    super();
    // 注册消息通道
    this.methodChannel =
      new MethodChannel(message, `com.rex.custom.ohos/customView${viewId}`, StandardMethodCodec.INSTANCE);
    this.methodChannel.setMethodCallHandler(this);
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    // 接受Dart侧发来的消息
    let method: string = call.method;
    let link1: SubscribedAbstractProperty<number> = AppStorage.link('numValue');
    let link2: SubscribedAbstractProperty<number> = AppStorage.link('viewCase');
    switch (method) {
      case 'getMessageFromFlutterView':
        let value: ESObject = call.args;
        this.numValue = value;
        link1.set(value)
        console.log("nodeController receive message from dart: " + this.numValue);
        result.success(true);
        break;
      case 'changeViewCase':
        let viewNumber: ESObject = call.args;
        this.viewCase = viewNumber;
        link2.set(viewNumber);
        result.success(true);
        break;
    }
  }

  public sendMessage = () => {
    console.log("nodeController sendMessage")
    //向Dart侧发送消息
    this.methodChannel.invokeMethod('getMessageFromOhosView', 'natvie - ' + this.index++);
  }

  getView(): WrappedBuilder<[Params]> {
    return new WrappedBuilder(ButtonBuilder);
  }

  dispose(): void {
  }
}

mian.dart:

import 'dart:math';

import 'package:flutter/material.dart';
import 'custom_ohos_view.dart';

void main() {
  runApp(const MaterialApp(home: MyHome()));
}

class MyHome extends StatelessWidget {
  const MyHome({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: CustomExample(),
    );
  }
}

class CustomExample extends StatefulWidget {
  const CustomExample({Key? key}) : super(key: key);

  @override
  State<CustomExample> createState() => _CustomExampleState();
}

class _CustomExampleState extends State<CustomExample> {
  String receivedData = '';
  CustomViewController? _controller;

  void _onCustomOhosViewCreated(CustomViewController controller) {
    _controller = controller;
    _controller?.customDataStream.listen((data) {
      //接收到来自OHOS端的数据
      setState(() {
        receivedData = '来自ohos的数据:$data';
      });
    });
  }

  Widget _buildOhosView() {
    return Expanded(
      child: Container(
        color: Colors.blueAccent.withAlpha(60),
        child: CustomOhosView(_onCustomOhosViewCreated),
      ),
      flex: 1,
    );
  }

  Widget _buildFlutterView() {
    return Expanded(
      child: Stack(
        alignment: AlignmentDirectional.bottomCenter,
        children: [
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            mainAxisSize: MainAxisSize.max,
            children: [
              TextButton(
                onPressed: () {
                  final randomNum = Random().nextInt(10);
                  _controller
                      ?.sendMessageToOhosView('flutter - $randomNum ');
                },
                child: const Text('发送数据给ohos'),
              ),
               TextButton(
                onPressed: () {
                  _controller
                      ?.changeViewCase();
                },
                child: const Text('切换viewCase'),
              ),
              const SizedBox(height: 10),
              Text(receivedData),
            ],
          ),
          const Padding(
            padding: EdgeInsets.only(bottom: 15),
            child: Text(
              'Flutter - View',
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ],
      ),
      flex: 1,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        _buildOhosView(),
        _buildFlutterView(),
      ],
    );
  }
}

CustomOhosView.dart:

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

typedef OnViewCreated = Function(CustomViewController);

///自定义OhosView
class CustomOhosView extends StatefulWidget {
  final OnViewCreated onViewCreated;

  const CustomOhosView(this.onViewCreated, {Key? key}) : super(key: key);

  @override
  State<CustomOhosView> createState() => _CustomOhosViewState();
}

class _CustomOhosViewState extends State<CustomOhosView> {
  late MethodChannel _channel;

  @override
  Widget build(BuildContext context) {
    return _getPlatformFaceView();
  }

  Widget _getPlatformFaceView() {
    return OhosView(
      viewType: 'com.rex.custom.ohos/customView',
      onPlatformViewCreated: _onPlatformViewCreated,
      creationParams: const <String, dynamic>{'initParams': 'hello world'},
      creationParamsCodec: const StandardMessageCodec(),
    );
  }

  void _onPlatformViewCreated(int id) {
    _channel = MethodChannel('com.rex.custom.ohos/customView$id');
    final controller = CustomViewController._(
      _channel,
    );
    widget.onViewCreated(controller);
  }
}

class CustomViewController {
  final MethodChannel _channel;
  final StreamController<String> _controller = StreamController<String>();

  CustomViewController._(
    this._channel,
  ) {
    _channel.setMethodCallHandler(
      (call) async {
        switch (call.method) {
          case 'getMessageFromOhosView':
            // 从native端获取数据
            final result = call.arguments as String;
            _controller.sink.add(result);
            break;
        }
      },
    );
  }

  Stream<String> get customDataStream => _controller.stream;

  // 发送数据给native
  Future<void> sendMessageToOhosView(String message) async {
    await _channel.invokeMethod(
      'getMessageFromFlutterView',
      message,
    );
  }
  Future<void> changeViewCase() async {
    await _channel.invokeMethod(
      'changeViewCase',
      2,
    );
  }
}

若是不满足您的场景,还请您详细描述下您的场景,是需要加载任意自定义的页面还是加载简单的组件场景。

更多关于HarmonyOS鸿蒙Next中flutter使用platformView怎么同时构建不同组件供flutter端使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


虽然也可以用这种组件的形式来加载不同页面,不过还是想知道直接加载不同的页面的话具体代码是怎么实现呢

尊敬的开发者,您好,
请问下您是需要应用在启动阶段加载不同的页面,还是希望在应用运行时通过应用内的按钮或者其他方式控制切换同层渲染的ArkTS组件?

抱歉,我前面说错了,我想实现的是可以加载不同组件的场景,但是如果组件布局比较复杂(不适合用示例中的将不同组件引入到一个组件中区分调用),这种需要区分编写的场景需要怎么处理,大概的就像二楼说的那样,但是我在实现的时候有些细节不理解,还是不知怎么实现

参考文档

有要学HarmonyOS AI的同学吗,联系我:https://www.itying.com/goods-1206.html

Flutter PlatformView 的多组件方案本质上就是注册多个 PlatformViewFactory,每个 Factory 对应一个 viewTypeId。Flutter 端通过不同的 viewTypeId 来区分调用哪个原生组件。你试一下复制官方的 Factory 代码,改 viewTypeIdcreate() 里的组件,然后都注册到 getPlatformViewRegistry()

在 HarmonyOS Next 中,Flutter 使用 PlatformView 同时构建不同组件时,需在鸿蒙侧注册多个 PlatformViewFactory,每个 Factory 对应一个 unique viewType。Flutter 端通过 AndroidView(对标鸿蒙 PlatformView)并指定不同 viewType 来区分。鸿蒙侧工厂实现中,返回对应的 ArkUI 原生组件即可。

在 HarmonyOS ArkUI 端,通过 PlatformView 同时构建多个不同组件供 Flutter 端使用的核心方法是为每种组件创建独立的工厂并注册。

  1. 在 ArkTS 侧创建多个工厂
    为每个需要嵌入的鸿蒙原生组件,分别实现 FlutterPlatformViewFactory 并重写 onCreate 方法返回对应的组件实例。例如 MapPlatformViewFactory 返回地图组件,VideoPlatformViewFactory 返回视频组件。

  2. 注册多个视图类型
    在 EntryAbility 的 configureFlutterEngine 中,多次调用 plugin.registerViewFactory,为每个工厂绑定不同的 viewType 字符串(如 “map”、“video”),确保 viewType 全局唯一。

  3. Flutter 端按 viewType 调用
    在 Dart 层使用 HarmonyView 组件(对应文档中的 PlatformView),通过 viewType 参数指定要加载的原生组件。例如 HarmonyView(viewType: 'map')HarmonyView(viewType: 'video') 会分别激活 ArkUI 侧对应的工厂,构建出不同组件。

通过这种 “多工厂注册 + viewType 驱动” 的模式,即可在同一项目中分场景自由组合多个原生组件,无需局限于单一实现。

回到顶部