HarmonyOS鸿蒙Next中如何通过flutter端和鸿蒙通信实现扫码功能

HarmonyOS鸿蒙Next中如何通过flutter端和鸿蒙通信实现扫码功能 下面是鸿蒙原生的扫码功能,我可以实现

import { scanCore, scanBarcode, customScan } from '@kit.ScanKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common, abilityAccessCtrl } from '@kit.AbilityKit';
import display from '@ohos.display';

const TAG: string = '[CustomScan]';

@Component
struct CustomScanPage {
  @State userGrant: boolean = false // 是否已申请相机权限
  @State surfaceId: string = '' // xComponent组件生成id
  @State isShowBack: boolean = false // 是否已经返回扫码结果
  @State isFlashLightEnable: boolean = false // 是否开启了闪光灯
  @State isSensorLight: boolean = false // 记录当前环境亮暗状态
  @State cameraHeight: number = 640 // 设置预览流高度,默认单位:vp
  @State cameraWidth: number = 360 // 设置预览流宽度,默认单位:vp
  @State offsetX: number = 0 // 设置预览流x轴方向偏移量,默认单位:vp
  @State offsetY: number = 0 // 设置预览流y轴方向偏移量,默认单位:vp
  @State displayHeight: number = 0 // 屏幕高度,单位vp
  @State displayWidth: number = 0 // 屏幕宽度,单位vp
  @State scanResult: Array<scanBarcode.ScanResult> = [] // 扫码结果
  private mXComponentController: XComponentController = new XComponentController()

  async onPageShow() {
    // 自定义启动第一步,用户申请权限
    await this.requestCameraPermission();
    // 多码扫码识别,enableMultiMode: true 单码扫码识别enableMultiMode: false
    let options: scanBarcode.ScanOptions = {
      scanTypes: [scanCore.ScanType.ALL], // 支持所有码类型
      enableMultiMode: true, // 支持多码识别
      enableAlbum: true // 允许从相册选择图片
    }
    // 自定义启动第二步:设置预览流布局尺寸
    this.setDisplay();
    try {
      // 自定义启动第三步,初始化接口
      customScan.init(options);
    } catch (error) {
      console.error(TAG, `初始化自定义扫码失败。错误码: ${error.code}, 消息: ${error.message}`);
    }
  }

  async onPageHide() {
    // 页面消失或隐藏时,停止并释放相机流
    this.userGrant = false;
    this.isFlashLightEnable = false;
    this.isSensorLight = false;
    try {
      customScan.off('lightingFlash');
    } catch (error) {
      console.error(TAG, `关闭光线闪烁监听失败。错误码: ${error.code}, 消息: ${error.message}`);
    }
    this.customScanStop();
    try {
      // 自定义相机流释放接口
      customScan.release().catch((error: BusinessError) => {
        console.error(TAG, `通过Promise释放自定义扫码失败。错误码: ${error.code}, 消息: ${error.message}`);
      })
    } catch (error) {
      console.error(TAG, `释放自定义扫码失败。错误码: ${error.code}, 消息: ${error.message}`);
    }
  }

  // 用户申请权限
  async reqPermissionsFromUser(): Promise<number[]> {
    console.warn(TAG, '开始请求用户权限');
    let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    let atManager = abilityAccessCtrl.createAtManager();
    let grantStatus = await atManager.requestPermissionsFromUser(context, ['ohos.permission.CAMERA']);
    return grantStatus.authResults;
  }

  // 用户申请相机权限
  async requestCameraPermission() {
    let grantStatus = await this.reqPermissionsFromUser();
    for (let i = 0; i < grantStatus.length; i++) {
      if (grantStatus[i] === 0) {
        // 用户授权,可以继续访问目标操作
        console.warn(TAG, '获取相机权限成功');
        this.userGrant = true;
        break;
      }
    }
  }

  // 竖屏时获取屏幕尺寸,设置预览流全屏示例
  setDisplay() {
    try {
      // 默认竖屏
      let displayClass: display.Display = display.getDefaultDisplaySync();
      this.displayHeight = this.getUIContext().px2vp(displayClass.height);
      this.displayWidth = this.getUIContext().px2vp(displayClass.width);
      let maxLen: number = Math.max(this.displayWidth, this.displayHeight);
      let minLen: number = Math.min(this.displayWidth, this.displayHeight);
      const RATIO: number = 16 / 9;
      this.cameraHeight = maxLen;
      this.cameraWidth = maxLen / RATIO;
      this.offsetX = (minLen - this.cameraWidth) / 2;
    } catch (error) {
      console.error(TAG, `获取默认显示信息失败。错误码: ${error.code}, 消息: ${error.message}`);
    }
  }

  // toast显示扫码结果
  async showScanResult(result: scanBarcode.ScanResult) {
    try {
      // 使用toast显示出扫码结果
      this.getUIContext().getPromptAction().showToast({
        message: JSON.stringify(result),
        duration: 5000
      });
    } catch (error) {
      console.error(TAG, `显示扫码结果失败。错误码: ${error.code}, 消息: ${error.message}`);
    }
  }

  initCamera() {
    this.isShowBack = false;
    this.scanResult = [];
    let viewControl: customScan.ViewControl = {
      width: this.cameraWidth,
      height: this.cameraHeight,
      surfaceId: this.surfaceId
    };
    try {
      // 自定义启动第四步,请求扫码接口,通过Promise方式回调
      customScan.start(viewControl)
        .then((result: Array<scanBarcode.ScanResult>) => {
          console.warn(TAG, `扫描结果: ${JSON.stringify(result)}`);
          if (result.length) {
            // 解析码值结果跳转应用服务页
            this.scanResult = result;
            this.isShowBack = true;
            // 获取到扫描结果后暂停相机流
            this.customScanStop();
          }
        }).catch((error: BusinessError) => {
        console.error(TAG, `启动自定义扫码失败。错误码: ${error.code}, 消息: ${error.message}`);
      });
    } catch (error) {
      console.error(TAG, `启动自定义扫码失败。错误码: ${error.code}, 消息: ${error.message}`);
    }
  }

  customScanStop() {
    try {
      customScan.stop().catch((error: BusinessError) => {
        console.error(TAG, `停止自定义扫码失败。错误码: ${error.code}, 消息: ${error.message}`);
      })
    } catch (error) {
      console.error(TAG, `停止自定义扫码失败。错误码: ${error.code}, 消息: ${error.message}`);
    }
  }

  // 自定义扫码界面的顶部返回按钮和扫码提示
  @Builder
  TopTool() {
    Column() {
      Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
        Text('返回')
          .onClick(async () => {
            this.getUIContext().getRouter().back();
          })
      }.padding({ left: 24, right: 24, top: 40 })

      Column() {
        Text('扫描二维码/条形码')
      }.margin({ left: 24, right: 24, top: 24 })
    }
    .height(146)
    .width('100%')
  }

  build() {
    Stack() {
      if (this.userGrant) {
        Column() {
          XComponent({
            id: 'componentId',
            type: XComponentType.SURFACE,
            controller: this.mXComponentController
          })
            .onLoad(async () => {
              console.warn(TAG, '加载成功,调用onLoad方法');
              // 获取XComponent组件的surfaceId
              this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
              console.warn(TAG, `获取surfaceId成功: ${this.surfaceId}`);
              this.initCamera();
              // 闪光灯监听接口
              customScan.on('lightingFlash', (error, isLightingFlash) => {
                if (error) {
                  console.error(TAG, `光线闪烁监听失败。错误码: ${error.code}, 消息: ${error.message}`);
                  return;
                }
                if (isLightingFlash) {
                  this.isFlashLightEnable = true;
                } else {
                  try {
                    if (!customScan.getFlashLightStatus()) {
                      this.isFlashLightEnable = false;
                    }
                  } catch (error) {
                    console.error(TAG, `获取闪光灯状态失败。错误码: ${error.code}, 消息: ${error.message}`);
                  }
                }
                this.isSensorLight = isLightingFlash;
              });
            })
            .width(this.cameraWidth)
            .height(this.cameraHeight)
            .position({ x: this.offsetX, y: this.offsetY })
        }
        .height('100%')
        .width('100%')
      }

      Column() {
        this.TopTool()
        Column() {
        }
        .layoutWeight(1)
        .width('100%')

        Column() {
          Row() {
            // 闪光灯按钮,使用图标且一直显示
            Image($r('app.media.nav_icon_scan_selected'))
              .width(40)
              .height(40)
              .onClick(() => {
                let lightStatus: boolean = false;
                try {
                  lightStatus = customScan.getFlashLightStatus();
                } catch (error) {
                  console.error(TAG, `获取闪光灯状态失败。错误码: ${error.code}, 消息: ${error.message}`);
                }

                // 根据当前闪光灯状态,选择打开或关闭闪光灯
                if (lightStatus) {
                  try {
                    customScan.closeFlashLight();
                    setTimeout(() => {
                      this.isFlashLightEnable = this.isSensorLight;
                    }, 200);
                  } catch (error) {
                    console.error(TAG, `关闭闪光灯失败。错误码: ${error.code}, 消息: ${error.message}`);
                  }
                } else {
                  try {
                    customScan.openFlashLight();
                  } catch (error) {
                    console.error(TAG, `打开闪光灯失败。错误码: ${error.code}, 消息: ${error.message}`);
                  }
                }
              })
              .visibility(Visibility.Visible) // 一直显示


            // 扫码成功后,点击按钮后重新扫码
            Button('重新扫描')
              .onClick(() => {
                // 点击按钮重启相机流,重新扫码
                this.initCamera();
              })
              .visibility(this.isShowBack ? Visibility.Visible : Visibility.None)
          }
        }
        .width('50%')
        .height(180)
      }

      // 单码、多码扫描后,显示码图蓝点位置。点击toast码图信息
      ForEach(this.scanResult, (item: scanBarcode.ScanResult) => {
        if (item.scanCodeRect) {
          Image($r('app.media.nav_icon_scan_selected'))
            .width(40)
            .height(40)
            .markAnchor({ x: 20, y: 20 })
            .position({
              x: (item.scanCodeRect.left + item?.scanCodeRect?.right) / 2 + this.offsetX,
              y: (item.scanCodeRect.top + item?.scanCodeRect?.bottom) / 2 + this.offsetY
            })
            .onClick(() => {
              this.showScanResult(item);
            })
        }
      }, (item: scanBarcode.ScanResult) => '' + item?.scanCodeRect?.left + item?.scanCodeRect?.right + 'px'
    }
    // 建议相机流设置为全屏
    .width('100%')
    .height('100%')
    .onClick((event: ClickEvent) => {
      // 是否已扫描到结果
      if (this.isShowBack) {
        return;
      }
      // 点击屏幕位置,获取点击位置(x,y),设置相机焦点
      let x1 = this.getUIContext().vp2px(event.displayY) / (this.displayHeight + 0.0);
      let y1 = 1.0 - (this.getUIContext().vp2px(event.displayX) / (this.displayWidth + 0.0));
      try {
        customScan.setFocusPoint({ x: x1, y: y1 });
        console.warn(TAG, `设置焦点成功,坐标: x1: ${x1}, y1: ${y1}`);
      } catch (error) {
        console.error(TAG, `设置焦点失败。错误码: ${error.code}, 消息: ${error.message}`);
      }
      console.warn(TAG, `设置焦点成功,坐标:x1: ${x1}, y1: ${y1}`);
      // 设置连续自动对焦模式
      setTimeout(() => {
        try {
          customScan.resetFocus();
        } catch (error) {
          console.error(TAG, `重置焦点失败。错误码: ${error.code}, 消息: ${error.message}`);
        }
      }, 200);
    }).gesture(PinchGesture({ fingers: 2 })
      .onActionStart(_: GestureEvent) {
        console.warn(TAG, '捏合启动');
      }
      .onActionUpdate(event: GestureEvent) {
        // 已移除缩放功能相关代码
      }
      .onActionEnd(_: GestureEvent) {
        // 已移除缩放功能相关代码
      })
  }
}

是我仿照上面做的flutter和原生通信实现的扫码功能,但是出现问题
flutter端

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

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

  ScanViewController._(this._channel) {
    _channel.setMethodCallHandler((call) async {
      switch (call.method) {
        case 'onScanResult':
          final result = call.arguments as String;
          _controller.sink.add(result);
          break;
        case 'surfaceReady':
        // 处理surface就绪事件
          break;
      }
    });
  }

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

  Future<void> startScan() async {
    await _channel.invokeMethod('startScan');
  }

  Future<void> stopScan() async {
    await _channel.invokeMethod('stopScan');
  }
}

class ScanView extends StatefulWidget {
  const ScanView({super.key});

  @override
  State<ScanView> createState() => _ScanViewState();
}

class _ScanViewState extends State<ScanView> {
  ScanViewController? _controller;
  String _scanResult = '';

  void _onPlatformViewCreated(int id) {
    _controller = ScanViewController._(MethodChannel('samples.flutter.dev/scan/view$id'));
    _controller?.scanResultStream.listen((result) {
      setState(() => _scanResult = result);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Expanded(
          child: OhosView(
            viewType: 'scan_surface',
            onPlatformViewCreated: _onPlatformViewCreated,
            creationParamsCodec: const StandardMessageCodec(),
          ),
        ),
        Text('扫描结果: $_scanResult'),
        ElevatedButton(
          onPressed: _controller?.startScan,
          child: const Text('开始扫码'),
        ),
        ElevatedButton(
          onPressed: _controller?.stopScan,
          child: const Text('停止扫码'),
        ),
      ],
    );
  }
}

鸿蒙原生端

import { BusinessError } from '@kit.BasicServicesKit';
import { abilityAccessCtrl, common } from '@kit.AbilityKit';
import {
  BinaryMessenger,
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodChannel,
  MethodResult,
  StandardMessageCodec,
} from '@ohos/flutter_ohos';
import { scanCore, scanBarcode } from '@kit.ScanKit';
import { Factory } from '../entryability/ScanPlatformViewFactory';

const TAG = "ScanPlugin";

interface ScanParams {
  scanTypes?: string;
  enableMultiMode?: boolean;
  enableAlbum?: boolean;
}

export default class ScanPlugin implements FlutterPlugin {
  private channel?: MethodChannel;
  private context: common.UIAbilityContext | null = null;
  private VIEW_TYPE: string = "scan_surface";

  getUniqueClassName(): string {
    return 'ScanPlugin';
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(
      binding.getBinaryMessenger(),
      "samples.flutter.dev/scan"
    );

    // 注册视图工厂
    binding.getPlatformViewRegistry().registerViewFactory(
      this.VIEW_TYPE,
      new Factory(
        binding.getBinaryMessenger(),
        StandardMessageCodec.INSTANCE,
        this.channel
      )
    );

    // 处理全局方法调用
    this.channel.setMethodCallHandler({
      onMethodCall: (call: MethodCall, result: MethodResult) => {
        this.context = getContext() as common.UIAbilityContext;
        if (!this.context) {
          result.error(TAG, '上下文未初始化', null);
          return;
        }

        switch (call.method) {
          case "requestCameraPermission":
            this.requestCameraPermission().then(granted => {
              result.success(granted);
            }).catch((err: Error) => {
              result.error(TAG, err.message, null);
            });
            break;

          case "startScan":
            const options = call.argument as scanBarcode.ScanOptions;
            // 实际扫码操作由PlatformView处理,这里只需转发
            result.success(true);
            break;

          case "stopScan":
            // 实际停止操作由PlatformView处理,这里只需转发
            result.success(true);
            break;

          default:
            result.notImplemented();
            break;
        }
      }
    });
  }

  private async requestCameraPermission(): Promise<boolean> {
    try {
      const atManager = abilityAccessCtrl.createAtManager();
      const grantStatus = await atManager.requestPermissionsFromUser(
        this.context!,
        ['ohos.permission.CAMERA']
      );
      return grantStatus.authResults.every(status => status === 0);
    } catch (error) {
      const err = error as BusinessError;
      throw new Error(err.message);
    }
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    this.channel?.setMethodCallHandler(null);
  }
}

原生视图:

import {
  BinaryMessenger,
  MessageCodec,
  MethodChannel,
  PlatformView,
  PlatformViewFactory,
} from "@ohos/flutter_ohos";
import { DVModel } from "@ohos/flutter_ohos/src/main/ets/view/DynamicView/dynamicView";
import { customScan, scanBarcode, scanCore } from "@kit.ScanKit";
import { BusinessError } from "@kit.BasicServicesKit";
import { abilityAccessCtrl, common } from "@kit.AbilityKit";

// 定义参数接口
interface BaseParams {
  direction?: Direction;
  platformView?: PlatformView;
}

interface ScanViewCreateArgs {
  width?: number;
  height?: number;
}

const TAG: string = '[ScanPlatformView]';

// 扫描视图组件
@Component
struct ScanComponent {
  @Prop params: BaseParams;

  get scanPlatformView(): ScanPlatformView | null {
    if (this.params.platformView instanceof ScanPlatformView) {
      return this.params.platformView as ScanPlatformView;
    }
    console.error(TAG, `platformView类型错误: 预期ScanPlatformView,实际${this.params.platformView?.constructor.name}`);
    return null;
  }

  build() {
    Column() {
      if (this.scanPlatformView) {
        XComponent({
          id: `scan_surface_${this.scanPlatformView.viewId}`,
          type: XComponentType.SURFACE,
          controller: this.scanPlatformView.mXComponentController
        })
          .onLoad(() => {
            // 修复:添加非空断言(已通过if判断确保非空)
            console.warn(TAG, `XComponent ${this.scanPlatformView!.viewId} 加载成功`);
            this.scanPlatformView!.handleXComponentLoad();
          })
          .width(this.scanPlatformView.args.width || 360)
          .height(this.scanPlatformView.args.height || 640)
      } else {
        Text("扫码视图初始化失败")
          .fontColor(Color.Red)
          .padding(16)
      }
    }
    .width('100%')
    .height('100%')
  }
}

// 构建器函数
@Builder
function ScanBuilder(params: BaseParams) {
  ScanComponent({ params: params })
}

// 自定义平台视图
export default class ScanPlatformView extends PlatformView {
  context: common.Context;
  viewId: number;
  args: ScanViewCreateArgs;
  globalChannel: MethodChannel;
  mXComponentController: XComponentController = new XComponentController();
  surfaceId: string = '';

  constructor(
    context: common.Context,
    viewId: number,
    args: ScanViewCreateArgs,
    messenger: BinaryMessenger,
    globalChannel: MethodChannel
  ) {
    super();
    this.context = context;
    this.viewId = viewId;
    // 提供默认宽高
    this.args = {
      width: args?.width || 360,
      height: args?.height || 640
    };
    this.globalChannel = globalChannel;

    // 检查相机权限(移回正确位置)
    this.checkCameraPermission();
    console.warn(TAG, `创建ScanPlatformView: viewId=${viewId}`);
  }

  // XComponent加载完成回调
  handleXComponentLoad() {
    try {
      this.surfaceId = this.mXComponentController.getXComponentSurfaceId();
      console.warn(TAG, `XComponent加载完成: viewId=${this.viewId}, surfaceId=${this.surfaceId}`);

      if (this.surfaceId) {
        this.globalChannel.invokeMethod('surfaceReady', {
          viewId: this.viewId,
          surfaceId: this.surfaceId
        });
      } else {
        console.error(TAG, `获取surfaceId失败`);
      }
    } catch (err) {
      console.error(TAG, `处理XComponent加载异常: ${(err as Error).message}`);
    }
  }

  // 检查相机权限
  private async checkCameraPermission() {
    try {
      const atManager = abilityAccessCtrl.createAtManager();
      const grantStatus = await atManager.requestPermissionsFromUser(
        this.context,
        ['ohos.permission.CAMERA']
      );
      const hasPermission = grantStatus.authResults.every(status => status === 0);
      if (!hasPermission) {
        console.error(TAG, "未获取相机权限,无法预览");
        this.globalChannel.invokeMethod('permissionDenied', '请授予相机权限以使用扫码功能');
      }
    } catch (err) {
      console.error(TAG, `权限检查失败: ${(err as Error).message}`);
    }
  }

  // 启动扫码
  startScan(options: scanBarcode.ScanOptions): Promise<boolean> {
    return new Promise((resolve, reject) => {
      if (!this.surfaceId) {
        reject(new Error('surfaceId未初始化'));
        return;
      }

      const viewControl: customScan.ViewControl = {
        width: this.args.width || 360,
        height: this.args.height || 640,
        surfaceId: this.surfaceId
      };

      customScan.start(viewControl)
        .then((results: scanBarcode.ScanResult[]) => {
          const codes = results.map(r => r.originalValue);
          this.globalChannel.invokeMethod('onScanResult', codes);
          resolve(true);
        })
        .catch((err: BusinessError) => {
          reject(new Error(`启动失败: ${err.message}`));
        });
    });
  }

  // 停止扫码
  stopScan(): Promise<boolean> {
    return new Promise((resolve, reject) => {
      customScan.stop()
        .then(() => resolve(true))
        .catch((err: BusinessError) => {
          reject(new Error(`停止失败: ${err.message}`));
        });
    });
  }

  // 构建视图
  getView(): WrappedBuilder<[BaseParams]> {
    const self = this;
    return new WrappedBuilder<([BaseParams])>((params: BaseParams) => {
      const scanParams: BaseParams = {
        direction: params.direction,
        platformView: self
      };
      ScanBuilder(scanParams);
    });
  }

  // 实现PlatformView接口其他方法
  getType(): string {
    return 'scan_surface';
  }

  onFlutterViewAttached(dvModel: DVModel): void {
    console.warn(TAG, `视图${this.viewId}附加到Flutter`);
  }

  onFlutterViewDetached(): void {
    console.warn(TAG, `视图${this.viewId}从Flutter分离`);
  }

  dispose(): void {
    console.warn(TAG, `释放资源: viewId=${this.viewId}`);
    this.stopScan().catch((err: Error) =>
      console.error(TAG, `停止扫码失败: ${err.message}`)
    );
  }
}

// 视图工厂(重点修复部分)
export default class Factory extends PlatformViewFactory {
  // 声明工厂类的属性
  private messenger: BinaryMessenger;
  private globalChannel: MethodChannel;

  // 修复:正确定义Factory构造函数,传递codec给父类
  constructor(
    messenger: BinaryMessenger,
    codec: MessageCodec<ScanViewCreateArgs>,
    globalChannel: MethodChannel
  ) {
    super(codec); // 修复:父类需要codec参数
    this.messenger = messenger; // 初始化messenger
    this.globalChannel = globalChannel; // 初始化globalChannel
  }

  // 创建平台视图
  create(context: common.Context, viewId: number, args: ScanViewCreateArgs): PlatformView {
    console.warn('[ScanFactory]', `创建平台视图: viewId=${viewId}`);
    return new ScanPlatformView(
      context,
      viewId,
      args,
      this.messenger,
      this.globalChannel
    );
  }
}

出现问题


更多关于HarmonyOS鸿蒙Next中如何通过flutter端和鸿蒙通信实现扫码功能的实战教程也可以访问 https://www.itying.com/category-92-b0.html

3 回复

在原生侧创建 ScanService 类,封装扫码逻辑并暴露给 Flutter:

// ScanService.ts

import { customScan, scanBarcode, scanCore } from '@kit.ScanKit';

import { BusinessError } from '@kit.BasicServicesKit';

import { common } from '@kit.AbilityKit';

import { MethodChannel } from '@kit.ArkUI';

// 注册 MethodChannel
const CHANNEL_NAME = 'com.example/scan';
const channel = new MethodChannel(common.UIAbilityContext, CHANNEL_NAME);

// 处理 Flutter 请求
channel.setMethodCallHandler({
  onCall: (method: string, args: any, result: MethodChannel.Result) => {
    switch (method) {
      case 'startScan':
        startCustomScan(result); // 调用原生扫码
        break;
      default:
        result.error('未实现的方法', null);
    }
  }
});

// 封装扫码逻辑
async function startCustomScan(result: MethodChannel.Result) {
  try {
    const options: scanBarcode.ScanOptions = {
      scanTypes: [scanCore.ScanType.ALL],
      enableMultiMode: true,
      enableAlbum: true
    };
    customScan.init(options);

    const viewControl = { width: 360, height: 640, surfaceId: 'your_surface_id' };
    const scanResult = await customScan.start(viewControl);
    result.success(scanResult.value); // 返回扫码结果到 Flutter
  } catch (error) {
    result.error((error as BusinessError).code.toString(), error.message);
  }
}

在 Dart 侧通过 MethodChannel 触发扫码并接收结果:

// flutter/lib/scan_service.dart

import 'package:flutter/services.dart';

class ScanService {
  static const _channel = MethodChannel('com.example/scan');

  static Future<String?> startScan() async {
    try {
      final result = await _channel.invokeMethod<String>('startScan');
      return result;
    } on PlatformException catch (e) {
      print('扫码失败: ${e.message}');
      return null;
    }
  }
}

// 在 Flutter 页面中调用
ElevatedButton(
  onPressed: () async {
    final result = await ScanService.startScan();
    if (result != null) {
      print('扫码结果: $result');
    }
  },
  child: Text('启动扫码')
)

更多关于HarmonyOS鸿蒙Next中如何通过flutter端和鸿蒙通信实现扫码功能的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在HarmonyOS Next中,Flutter端可通过Platform Channels与鸿蒙原生层通信实现扫码功能。具体步骤:

  1. 鸿蒙侧实现扫码能力:
  • 创建OhosScanner类,使用@ohos.multimedia.camera@ohos.image包处理摄像头和图像识别
  • 通过ZBarZXing库解析二维码数据
  1. Flutter侧配置:
  • pubspec.yaml添加flutter_ohos插件
  • 创建MethodChannel连接原生层
  1. 通信实现:
  • Flutter调用invokeMethod('startScan')触发扫码
  • 鸿蒙侧通过MethodCallHandler接收指令并返回扫码结果

扫码结果通过Result.success()回调至Flutter端。需确保鸿蒙侧已配置相机权限。

在HarmonyOS Next中实现Flutter与原生通信实现扫码功能的关键点如下:

  1. 确保Flutter端正确注册了PlatformView:
  • 检查OhosViewviewType是否与原生端注册的视图类型一致
  • 确认creationParamsCodec使用正确的编解码器
  1. 原生端需要正确实现PlatformViewFactory:
  • 确保工厂类继承自PlatformViewFactory
  • create方法中返回自定义的ScanPlatformView实例
  1. 权限处理问题:
  • 确保在调用扫码功能前已获取相机权限
  • 建议在Flutter端调用原生方法检查权限状态
  1. Surface初始化时序:
  • 确保XComponent的surfaceId在扫码功能调用前已准备好
  • 可以通过surfaceReady事件通知Flutter端
  1. 常见问题排查:
  • 检查日志中是否有权限拒绝或surface初始化失败的记录
  • 确认customScan.init()是否在适当时机调用
  • 验证MethodChannel的通信是否正常

建议重点检查XComponent的surfaceId获取流程和权限申请逻辑,这两个是导致扫码功能无法正常工作的常见原因。

回到顶部