OpenHarmony アプリケーションは QR コードのスキャンと認識を実現します

この記事は、著者zhushangyuan_のOpenHarmony アプリケーションによる QR コード スキャン認識の実現」からの転載です。

コンセプト紹介

QR コードは幅広いシーンで使用できます。ショッピング アプリケーションでは、消費者は商品の QR コードを直接スキャンして、商品を閲覧したり購入したりできます。写真は、ショッピング アプリケーションで QR コードをスキャンするためのページです。

この記事では、オレンジ ショッピング サンプル アプリケーションを例に、OpenHarmony アプリケーションの QR コード開発に関する技術的なポイントを説明します。

まず、QR コードに関連するいくつかの概念を見てみましょう。

  • QRコードの生成

OpenHarmony アプリケーション フレームワークは、単一の QR コードを表示するために使用されるQRCode コンポーネントを提供します。このコンポーネントは QR コードを表示するためにのみ使用でき、バーコードや解析されたコード コンテンツを表示することはできません。

  • QRコード解析

OpenHarmony は、1D/2D コードを解析/生成するための強力なサードパーティ ライブラリ @ohos/zxing を提供します。詳細については、 @ohos/zxingを参照してください

QR コードを解析する場合、通常は 2 つの方法があり、カメラで写真を撮るか、フォト アルバムを開いて写真を選択し、QR コード解析に適した画像形式を解析します。

Tangerine Shopping サンプル アプリケーションによってスキャンされた QR コードのサンプル画像:

設定ファイル

QR コードに関連する概念を理解した後、オレンジ ショッピング サンプル アプリケーションの oh-package.json5 構成ファイルを見てみましょう。

Orange Shopping サンプル アプリケーションでは、ホームページ上の QR コード スキャンを実現するページのファイルの場所は、 entry/src/main/ets/pages/ScanPage.etsですファイルの内容は次のとおりです。

import {
       
        QRCodeScanComponent } from "@ohos/scan-component"
@Entry
@Component
struct Scan {
       
       
  build() {
       
       
    Column() {
       
       
      QRCodeScanComponent()
    }
  }
}

内容は非常に単純で、主にインポートされたカスタム コンポーネント QRCodeScanComponent です。このコンポーネントのコードは、 QR コード スキャン サンプル アプリケーションから取得しています。この方法でこの QR コード スキャン アプリケーションを開発する方法を後で分析します。

この行から、OpenHarmony アプリケーションが ohpm ローカル サードパーティ ライブラリをどのように参照しているかを理解できます。

"@ohos/scan-component":"file:../libs/ohos-qr-code-scan-1.0.1.har",

oh-package.json5 構成ファイルのフラグメントは次のとおりです。

{
       
       
  "license": "ISC",
  "devDependencies": {},
  "name": "product",
  "description": "example description",
  "repository": {},
  "version": "1.0.0",
  "dependencies": {
       
       
    "@ohos/http": "file:../libs/ohos-http-1.0.0.tgz",
    "@ohos/video-component": "file:../libs/ohos-video-component-1.0.5.tgz",
    "@ohos/details-page-component": "file:../feature/detailPage",
    "@ohos/notification": "file:../libs/ohos-notification-1.0.0.tgz",
    "@ohos/scan-component": "file:../libs/ohos-qr-code-scan-1.0.1.har",
    "@ohos/updatedialog": "file:../libs/ohos-updatedialog-1.0.0.tgz",
    "@ohos/enter-animation": "file:../libs/ohos-enter-animation-1.0.1.tgz",
    "@ohos/share-component": "file:../libs/ohos-sharecomponent-1.0.1.tgz",
    "@ohos/emitter": "file:../feature/emitter",
    "@ohos/navigation-component": "file:../feature/navigationHome"
  }
}

開発ステップ

QRコード読み取り機能がどのように開発されているかを見てみましょう。

ohpm の 3 部構成ライブラリをインポートする

開発前に、ohpm コンポーネント ライブラリ (@ohos/zxing) をインポートする必要があります。コマンドラインを使用してohpm install @ohos/zxing をインポートすることも、ファイル スニペットに示すように、ファイルエントリ\oh-package.json5で直接構成することもできます。

QR コード スキャンのコア コードが、簡単に再利用できる独立したモジュールである Feature ディレクトリに保存されていることがわかります。

“@ohos/feature-qr-code-scan”: “file:…/Feature”

ファイルエントリ\oh-package.json5フラグメント:

  "dependencies": {
       
       
    "@ohos/feature-qr-code-scan": "file:../Feature",
    "@ohos/zxing": "^2.0.0"
  }  

カメラサービス

CameraService.etsファイルのカメラ サービス コンストラクターで、画像レシーバーが作成されます。

画像受信側は、カメラが写真を撮るときに発生する「imageArrival」イベントをリッスンできます。リスニングイベントのコールバック関数内で、撮影した写真の処理を実現します。

CameraService.ets ファイルのカメラ サービス コンストラクター:

constructor(imgReceiver?: image.ImageReceiver) {
       
       
    if (imgReceiver === undefined) {
       
       
      this.imageReceiver = image.createImageReceiver(QRCodeScanConst.IMG_DEFAULT_SIZE.WIDTH,
      QRCodeScanConst.IMG_DEFAULT_SIZE.HEIGHT, image.ImageFormat.JPEG, QRCodeScanConst.MAX_IMAGE_CAPACITY)
    } else {
       
       
      this.imageReceiver = image.createImageReceiver(imgReceiver.size.width, imgReceiver.size.height,
      imgReceiver.format, imgReceiver.capacity)
    }
  }

カメラ関数を作成するCameraService.etsファイルには、主に次の手順が含まれます。

  • サポートされているカメラを入手する

コンテキストに従って CameraManager を取得し、サポートされているカメラ (カメラ) を取得します。サポートされているカメラがない場合は、次のようになります。

サポートされているカメラがある場合は、カメラ リストの最初のカメラがデフォルトで使用されます。実際のアプリケーションでは、QR コードのスキャンには背面カメラを使用する必要があります。

  • カメラの入力および出力ストリームを取得する

まず、指定されたカメラに応じて、カメラ入力ストリームthis.cameraInputを作成します

次に、カメラの CameraOutputCapability パラメーターを取得し、2 つの出力ストリームを作成します。

1. 出力ストリームをプレビューする

XComponent コンポーネントの surfaceId を使用して、カメラ プレビュー出力ストリーム this.previewOutput を作成します。カメラが写真を撮る前に、画像プレビューに対応する出力ストリームをプレビューします。

2.写真出力ストリーム

上で作成した画像レシーバーからの受信SurfaceIdを使用して、写真出力ストリーム this.photoOutput を作成します。写真に保存するための写真出力ストリーム。

  • カメラセッションを構成する

カメラ セッションの設定も比較的簡単です。入力ストリームと出力ストリームを追加し、コードとそのコメントを確認するだけです。

CameraService.ets ファイルはカメラ関数を作成します。

  /**
   * 创建相机
   */
  async createCamera(surfaceId: string) {
       
       
    Logger.info("createCamera start")
    // 根据context获取CameraManager
    let cameraManager = camera.getCameraManager(AppStorage.Get('context'))
    // 获取Camera对象数组
    let cameras = cameraManager.getSupportedCameras()
    // 没有相机就停止
    if (cameras.length === 0) {
       
       
      Logger.error("createCamera: cameras length is 0.")
      return
    }
    // 拿到相机列表中的第一个默认相机id, 根据id获取相机输入流
    this.cameraInput = cameraManager.createCameraInput(cameras[0])
    this.cameraInput.open()
    // 获取cameraOutputCapability参数
    let cameraOutputCapability = cameraManager.getSupportedOutputCapability(cameras[0])
    // 获取相机输出流
    this.previewOutput = cameraManager.createPreviewOutput(cameraOutputCapability.previewProfiles[0], surfaceId)
    // 获取一个可以创建相片输出流的id
    let receivingSurfaceId = await this.imageReceiver.getReceivingSurfaceId()
    // 创建相片输出流
    this.photoOutput = cameraManager.createPhotoOutput(cameraOutputCapability.photoProfiles[0], receivingSurfaceId)
    // 获取捕获会话的实例
    this.captureSession = cameraManager.createCaptureSession()
    // 开始会话配置
    this.captureSession.beginConfig()
    // 使用相机输入流---添加一个摄像头输入流
    this.captureSession.addInput(this.cameraInput)
    // 使用相机输出流---添加一个摄像头输出
     this.captureSession.addOutput(this.previewOutput)
    // 使用相片输出流---添加相机照片的输出
    this.captureSession.addOutput(this.photoOutput)
    // 结束并提交配置
    await this.captureSession.commitConfig()
    // 开始捕获会话
    await this.captureSession.start()
    Logger.info("createCamera end")
  }

CameraService.etsファイルのカメラ関数で写真のパラメーター設定を指定し、capture() 関数を呼び出して写真を完成させます。

写真を撮った後、画像受信側の「imageArrival」イベントがトリガーされます。カメラ関数は、カメラで QR コードをスキャンすると呼び出されます。

画像受信側は、カメラが写真を撮るときに発生する「imageArrival」イベントをリッスンできます。リスニングイベントのコールバック関数内で、撮影した写真の処理を実現します。

CameraService.ets ファイルのカメラ関数:

  takePicture() {
       
       
    let photoSetting = {
       
       
      rotation: camera.ImageRotation.ROTATION_0,
      quality: camera.QualityLevel.QUALITY_LEVEL_MEDIUM,
      mirror: false
    }
    this.photoOutput.capture(photoSetting)
  }

二次元コード解析実装コード

QR コード解析クラス ファイルは、 QRCodeParser.etsです。これは、QR コードを認識するための写真の撮影をサポートし、認識のためにフォト アルバムから QR コード画像を選択することもサポートします。

まず、カメラから取得した QR コード画像を解析する方法を見てみましょう。対応する関数は: parseQRCodeImageFromCameraです。このクラスは、時間ランダムな画像ファイル名と画像ファイル形式を指定し、関数parseQRCodeImageWithNameFromCameraの呼び出しを続けます

  /**
   * 解析从相机获取的二维码图片
   *
   * @param cameraService
   * @param canvasContext
   */
  parseQRCodeImageFromCamera(cameraService: CameraService,
                             imageComponentType?: image.ComponentType): void {
       
       
    Logger.info("parseQRCodeImageFromCamera start")
    let fileName = this.getRandomFileName(QRCodeScanConst.IMG_FILE_PREFIX, QRCodeScanConst.IMG_SUFFIX_JPG)
    this.parseQRCodeImageWithNameFromCamera(cameraService, fileName, imageComponentType);
    Logger.info("parseQRCodeImageFromCamera end")
  }

関数parseQRCodeImageWithNameFromCameraで、「imageArrival」イベントをリッスンする画像受信者を登録し、リッスン関数で QR コード画像を分析して認識します。

カメラが QR コードの写真を撮影すると、QR コード画像が指定されたディレクトリに保存され、ファイル URI が返されます。写真を保存する関数createPublicDirFileAssetの実装については、ソースコードを自分で確認できます。

返された画像 URI に従って、関数parseImageQRCode を呼び出してQR コードを解析します。parseImageQRCode関数は後で紹介します。

解析が失敗した場合は、解析が失敗したことを示すポップアップ ウィンドウが表示されます。解析が成功すると、解析結果が AppStorage に保存されます。

AppStorageに保存されたQRコードの解析結果は@watchデコレータの変数で監視され、QRコードの認識結果が検出された場合には後述するインターフェース上に表示されます。

QRCodeParser.ets ファイルの parseQRCodeImageWithNameFromCamera 関数コード:

  /**
   * 解析从相机获取的二维码图片,指定文件名称
   *
   * @param cameraService
   * @param canvasContext
   */
  parseQRCodeImageWithNameFromCamera(cameraService: CameraService,
                                     fileDisplayName: string,
                                     imageComponentType?: image.ComponentType): void {
       
       
    Logger.info("parseQRCodeImageWithNameFromCamera...")
    cameraService.imageReceiver.on('imageArrival', async () => {
       
       
      Logger.info("parseQRCodeImageWithNameFromCamera imageArrival start")
      // 从接收器获取下一个图像,并返回结果
      let targetImage: image.Image = await cameraService.imageReceiver.readNextImage()
      // 默认按JPEG格式处理
      let imgComponentType = imageComponentType === undefined ? image.ComponentType.JPEG : imageComponentType
      let imageComponent = await targetImage.getComponent(imgComponentType)
      // 将image的ArrayBuffer写入指定文件中,返回文件uri
      let imageUri = await this.createPublicDirFileAsset(fileDisplayName, mediaLibrary.MediaType.IMAGE,
                     mediaLibrary.DirectoryType.DIR_IMAGE, imageComponent.byteBuffer);
      // 释放已读取的image资源,以便处理下一个资源
      await targetImage.release()

      // 解析二维码
      let qrCodeParseRlt = await this.parseImageQRCode(imageUri);
      if (!qrCodeParseRlt.isSucess) {
       
       
        Logger.error("parseQRCodeImageWithNameFromCamera qrCodeParseRlt is null")
        prompt.showToast({
       
       
          message: $r('app.string.qrCodeNotRecognized')
        })
        return;
      }
      // 拼接解析结果
      AppStorage.SetOrCreate(QRCodeScanConst.QR_CODE_PARSE_RESULT, qrCodeParseRlt.decodeResult);
      Logger.info("parseQRCodeImageWithNameFromCamera imageArrival end")
    })
  }

QR コード解析クラス ファイルは、 QRCodeParser.etsです。これは、QR コードを認識するための写真の撮影をサポートし、認識のためにフォト アルバムから QR コード画像を選択することもサポートします。

次に、アルバムから選択した QR コード画像を解析する方法を見てみましょう。

パラメータ imageSrc は、選択した画像の URI アドレスです。

getImageSource() コードは、それ自体でクエリを実行して、画像 URI に従って画像の幅、高さ、pixelMap データを返すことができます。次に、zxing の 2 次元コード認識プログラムで使用できるように、ピクセル データを ArrayBuffer に書き込みます。

RGBLuminanceSource、BinaryBitmap、BinaryBitmap などの関数はすべて zxing クラスです。QR コード画像は、MultiFormatReader のデコード関数を呼び出すことで解析されます。

解析が成功すると、成功したタグと解析結果が返されます。

解析が失敗した場合は、catch ステートメント ブロックで処理され、失敗マークと解析失敗の理由が返されます。

QRCodeParser.ets ファイルの parseImageQRCode 関数コード:

 /**
   * 解析图片二维码信息
   * @param canvasContext
   * @param imageSrc
   */
  async parseImageQRCode(imageSrc: string): Promise<DecodeResultAttribute> {
       
       
    Logger.info(`parseImageQRCode start`);
    // 获取图片的宽高
    let imageSource = await this.getImageSource(imageSrc);
    let imageWidth = imageSource.width;
    let imageHeight = imageSource.height;
    // 获取PixelMap图片数据
    let pixMapData = imageSource.pixelMap;
    let pixelBytesNumber = pixMapData.getPixelBytesNumber();
    let arrayBuffer: ArrayBuffer = new ArrayBuffer(pixelBytesNumber);
    // 读取图像像素数据,结果写入ArrayBuffer里
    await pixMapData.readPixelsToBuffer(arrayBuffer);
    let int32Array = new Int32Array(arrayBuffer);
    let luminanceSource = new RGBLuminanceSource(int32Array, imageWidth, imageHeight);
    let binaryBitmap = new BinaryBitmap(new HybridBinarizer(luminanceSource));
    let mltiFormatReader = new MultiFormatReader();
    let hints = new Map();
    hints.set(DecodeHintType.POSSIBLE_FORMATS, [BarcodeFormat.QR_CODE]);
    mltiFormatReader.setHints(hints);
    try {
       
       
      // 解析二维码
      let decodeResult = mltiFormatReader.decode(binaryBitmap);
      let decodeText = decodeResult.getText();
      Logger.info(`parseImageQRCode end ${decodeText}`);
      return {
       
        isSucess: true, decodeResult: decodeText };
    } catch (err) {
       
       
      let error = `The error is ${err}`;
      Logger.info(`parseImageQRCode end`);
      return {
       
        isSucess: false, decodeResult: error };
    }
  }

カメラが QR コードをスキャンして認識します

QR コード スキャン カスタム コンポーネントは、ファイルQRCodeScanComponent.etsに実装されています。このファイル内の QR コードをスキャンするカメラを実装する方法を見てみましょう。

QR コード スキャン コンポーネントの aboutToAppear() 関数によって呼び出される watchCameraPermission() 関数は、カメラを使用して QR コードをスキャンして認識するために使用されます。

watchCameraPermission()関数では、setInterval関数を使用して100msごとにカメラ許可の有無を判定し、カメラ許可がある場合はカメラでQRコードを読み取ることができます。

カメラの許可がある場合は、setInterval 関数を使用して 4000 ミリ秒ごとにポーリングして QR コード画像が認識されるかどうかを判断し、認識された場合はポーリングをキャンセルします。

QR コードが認識されない場合は、関数 takePicture() を呼び出して写真を撮り続けます。この関数を呼び出した後、画像受信側の監視イベント「imageArrival」がトリガーされます。このイベントの監視と分析については、上記を参照してください。

ファイル QRCodeScanComponent.ets には、カメラが写真を撮って QR コードを認識するためのコード スニペットが含まれています。

aboutToAppear() {
       
       
// 监听相机权限
this.watchCameraPermission()
// 设置扫描动画
this.setQRCodeScanAnimation()
// 解析二维码图片信息
this.qrCodeParser.parseQRCodeImageFromCamera(this.cameraService);
}
......
// 监听相机权限变化
watchCameraPermission() {
       
       
let interval = setInterval(() => {
       
       
  this.hasCameraPermission = AppStorage.Get(QRCodeScanConst.HAS_CAMERA_PERMISSION)
  if (this.hasCameraPermission) {
       
       
	let qrCodeScanInterval = setInterval(() => {
       
       
	  if (this.qrCodeParseResult.length > 0 || this.isQRCodeScanStopped) {
       
       
		clearInterval(qrCodeScanInterval)
	  }
	  // 拍照
	  this.cameraService.takePicture()
	}, 4000)
	clearInterval(interval)
  }
}, 100)
}

アルバムの QR コード画像を識別する

QR コード スキャン カスタム コンポーネントは、ファイルQRCodeScanComponent.etsに実装されています。このファイル内のアルバムの QR コード画像を識別する方法を見てみましょう。

まず、this.isQRCodeScanStopped を true に設定します。これにより、写真の撮影と QR コードの認識のためにカメラがオフになります。

次に、ユーザーが QR コード画像を選択できるように、startAbilityForResult を通じてフォト アルバム アプリケーションを起動します。

画像の選択に失敗するとエラーが表示されます。

画像が正常に選択された場合は、QR コード デコード関数 parseImageQRCode を呼び出して、画像 QR コードの認識を完了します。

QR コードが正常に認識されると、ポップアップ ウィンドウに QR コードの結果が表示されます。

認識されると、トーストに「QR コードが認識されません」と表示されます。

ファイル QRCodeScanComponent.ets では、フォト アルバムによって QR コード画像が選択され、コード スニペットが識別されます。

Image($r('app.media.scan_photo'))
  .width(30)
  .height(30)
  .id('scanPhoto')
  .onClick(async () => {
       
       
	// 打开相册获取图片
	this.isQRCodeScanStopped = true
	let context = AppStorage.Get('context') as common.UIAbilityContext
	await context.startAbilityForResult({
       
       
	  parameters: {
       
        uri: 'singleselect' },
	  bundleName: 'com.ohos.photos',
	  abilityName: 'com.ohos.photos.MainAbility',
	}).then(data => {
       
       
	  // 获取want数据
	  let want = data['want'];
	  if (want) {
       
       
		// param代表want参数中的paramters
		let param = want['parameters'];
		if (param) {
       
       
		  // 被选中的图片路径media/image/8
		  let selectedUri = param['select-item-list'];
		  setTimeout(async () => {
       
       
			if (!selectedUri) {
       
       
			  prompt.showToast({
       
       
				message: $r('app.string.queryImageFailed'),
				duration: 1000
			  })
			  return;
			}
			// 获取解析数据
			let qrCodeParseRlt = await this.qrCodeParser.parseImageQRCode(selectedUri[0]);
			if (qrCodeParseRlt.isSucess) {
       
       
			  prompt.showDialog({
       
       
				title: $r('app.string.qrcodeResult'),
				message: qrCodeParseRlt.decodeResult
			  })
			} else {
       
       
			  prompt.showToast({
       
       
				message: $r('app.string.qrCodeNotRecognized')
			  })
			}
		  }, 50)
		}
	  }
	})
  })

QR コード スキャン コンポーネント インターフェイス

QR コード スキャン カスタム コンポーネントは、ファイルQRCodeScanComponent.etsに実装されています。QR コード スキャン コンポーネントのページ レイアウトを見てみましょう。

ページ全体はスタック レイアウトに Stack を使用します。

カメラ権限がある場合、XComponent コンポーネントを使用してカメラのプレビュー出力ストリームが表示されます。カメラは XComponent コンポーネントの onLoad 関数で作成され、カメラは onDestroy 関数で解放されます。

Image($r('app.media.scan_border'))画像は QR コード スキャン ボックスです。これは、ユーザーが QR コードをボックスに入れてスキャンして認識するようにガイドします。

ディバイダーとは、アニメーション効果を実現するための分割線で、QRコードを認識する過程で、分割線がQRコード認識枠内を上から下に移動します。スキャンアニメーションの実装コードは次のとおりです。

  // 扫描扫描动画
  setQRCodeScanAnimation() {
       
       
    setInterval(() => {
       
       
      animateTo({
       
       
        duration: 1000, // 动画时间
        tempo: 0.5, // 动画速率
        curve: Curve.EaseInOut,
        delay: 200, // 动画延迟时间
        iterations: -1, // 动画是否重复播放
        playMode: PlayMode.Normal,
      }, () => {
       
       
        this.animationOrdinate = 390 // 扫描动画结束Y坐标
      })
    }, 2000)
  }

Text($r('app.string.putTheQRCodeToScan')) は、スキャンと認識のために QR コードをボックスに入れるようにユーザーをガイドします。

Image($r('app.media.scan_back')) はアプリケーションを終了するために戻ります。

Image($r('app.media.scan_photo')) は、認識のためにフォト アルバムから QR コード画像を選択します。

build() {
       
       
Column() {
       
       
  Stack() {
       
       
	if (this.hasCameraPermission) {
       
       
	  XComponent({
       
       
		id: 'componentId',
		type: 'surface',
		controller: this.xComponentController
	  })
		.onLoad(() => {
       
       
		  // 适配可能需要获取设备信息
		  this.xComponentController.setXComponentSurfaceSize({
       
       
			surfaceWidth: QRCodeScanConst.IMG_DEFAULT_SIZE.WIDTH,
			surfaceHeight: QRCodeScanConst.IMG_DEFAULT_SIZE.HEIGHT
		  })
		  this.surFaceId = this.xComponentController.getXComponentSurfaceId()
		  this.cameraService.createCamera(this.surFaceId)
		})
		.onDestroy(() => {
       
       
		  this.cameraService.releaseCamera()
		})
		.height('100%')
		.width('100%')
	}
	Column() {
       
       
	  Column() {
       
       
		Image($r('app.media.scan_border'))
		......
		Divider()
		  .strokeWidth(1)
		  .height(4)
		  .width('100%')
		  .color(Color.White)
		  .width('100%')
		  .position({
       
        x: 0, y: 0 })
		  .translate({
       
        x: 0, y: this.animationOrdinate })
	  }
	......
	  Text($r('app.string.putTheQRCodeToScan'))
	......
	}
	......
	Row() {
       
       
	  Image($r('app.media.scan_back'))
	  ......
	  Row({ space: 16 }) {
       
       
		Image($r('app.media.scan_photo'))
		......
}

テストエフェクトを実行する

オレンジ ショッピングのサンプル アプリケーションコードをダウンロードし、DevEco Studio を使用してコンパイルおよびビルドし、シミュレーターまたは実際のデバイスを使用して実行体験を行うことができます。カメラを使ってQRコード画像を認識する体験や、アルバム内のQRコード画像の認識にも挑戦できます。

git init
git config core.sparsecheckout true
echo code/Solutions/Shopping/OrangeShopping/ > .git/info/sparse-checkout
git remote add origin https://gitee.com/openharmony/applications_app_samples.git
git pull origin master

予防

現在の QR コード サンプル アプリケーションはフォト アルバムの QR コードを認識しますが、認識結果がポップアップ表示された後、プログラムがクラッシュし、船荷証券が追跡されます。サンプルプログラムは改良予定です。

カメラ機能を使用して QR コードを直接キャプチャする機能は正常に実行されていないため、さらに最適化する必要があります。

参考文献

みかんショッピングのサンプルアプリケーション

QRコードスキャンアプリケーション例

@ohos/zxing

QRコードコンポーネント

カメラ開発の概要

イメージ開発の概要

Xコンポーネント

 

おすすめ

転載: blog.csdn.net/OpenHarmony_dev/article/details/132451986
おすすめ