【引言】
在本篇文章中,我們將探討如何在鴻蒙NEXT平臺上實現二維碼的生成與識別功能。透過使用ArkUI元件庫和相關的媒體庫,我們將建立一個簡單的應用程式,使用者可以生成二維碼並掃描識別。
【環境準備】
• 作業系統:Windows 10
• 開發工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
• 目標裝置:華為Mate60 Pro
• 開發語言:ArkTS
• 框架:ArkUI
• API版本:API 12
• 許可權:ohos.permission.WRITE_IMAGEVIDEO(為實現將圖片儲存至相簿功能)
【專案介紹】
1. 專案結構
我們首先定義一個名為QrCodeGeneratorAndScanner
的元件,使用@Component
裝飾器進行標記。該元件包含多個狀態變數和方法,用於處理二維碼的生成、識別和剪貼簿操作。
2. 元件狀態
元件的狀態包括:
buttonOptions
: 定義分段按鈕的選項,用於切換生成和識別二維碼的功能。inputText
: 使用者輸入的文字,用於生成二維碼。scanResult
: 掃描結果文字。scanResultObject
: 儲存掃描結果的物件。
3. 使用者介面構建
在build
方法中,我們使用Column
和Row
佈局來構建使用者介面。主要包含以下部分:
- 分段按鈕:使用者可以選擇生成二維碼或識別二維碼。
- 輸入區域:使用者可以輸入文字並生成二維碼。
- 二維碼顯示:根據輸入文字生成二維碼。
- 掃描區域:使用者可以透過相機掃描二維碼或從相簿選擇圖片進行識別。
4. 二維碼生成
二維碼生成使用QRCode
元件,輸入文字透過this.inputText
傳遞。使用者輸入後,二維碼會實時更新。
5. 二維碼識別
二維碼識別功能透過scanBarcode
模組實現。使用者可以點選“掃一掃”按鈕,啟動相機進行掃描,或選擇相簿中的圖片進行識別。識別結果將顯示在介面上,並提供複製功能。
6. 剪貼簿操作
使用者可以將掃描結果複製到剪貼簿,使用pasteboard
模組實現。點選“複製”按鈕後,掃描結果將被複制,使用者會收到提示。
【完整程式碼】
填寫許可權使用宣告字串:src/main/resources/base/element/string.json
{ "string": [ { "name": "module_desc", "value": "module description" }, { "name": "EntryAbility_desc", "value": "description" }, { "name": "EntryAbility_label", "value": "label" }, { "name": "WRITE_IMAGEVIDEO_info", "value": "儲存功能需要該許可權" } ] }
配置許可權:src/main/module.json5
{ "module": { "requestPermissions": [ { "name": 'ohos.permission.WRITE_IMAGEVIDEO', "reason": "$string:WRITE_IMAGEVIDEO_info", "usedScene": { } } ], //... ...
示例程式碼:src/main/ets/pages/Index.ets
import { componentSnapshot, // 元件快照 promptAction, // 提示操作 SegmentButton, // 分段按鈕 SegmentButtonItemTuple, // 分段按鈕項元組 SegmentButtonOptions // 分段按鈕選項 } from '@kit.ArkUI'; // 引入 ArkUI 元件庫 import { photoAccessHelper } from '@kit.MediaLibraryKit'; // 引入 MediaLibraryKit 中的照片訪問助手 import { common } from '@kit.AbilityKit'; // 引入 AbilityKit 中的通用功能 import { fileIo as fs } from '@kit.CoreFileKit'; // 引入 CoreFileKit 中的檔案 I/O 模組 import { image } from '@kit.ImageKit'; // 引入 ImageKit 中的影像處理模組 import { BusinessError, pasteboard } from '@kit.BasicServicesKit'; // 引入 BasicServicesKit 中的業務錯誤和剪貼簿操作 import { hilog } from '@kit.PerformanceAnalysisKit'; // 引入 PerformanceAnalysisKit 中的效能分析模組 import { detectBarcode, scanBarcode } from '@kit.ScanKit'; // 引入 ScanKit 中的條形碼識別模組 @Entry // 入口標記 @Component // 元件標記 struct QrCodeGeneratorAndScanner { // 定義二維碼生成與識別元件 @State private buttonOptions: SegmentButtonOptions = SegmentButtonOptions.capsule({ // 定義分段按鈕選項 buttons: [{ text: '生成二維碼' }, { text: '識別二維碼' }] as SegmentButtonItemTuple, // 按鈕文字 multiply: false, // 不允許多選 fontColor: Color.White, // 字型顏色為白色 selectedFontColor: Color.White, // 選中字型顏色為白色 selectedBackgroundColor: Color.Orange, // 選中背景顏色為橙色 backgroundColor: "#d5d5d5", // 背景顏色 backgroundBlurStyle: BlurStyle.BACKGROUND_THICK // 背景模糊樣式 }) @State private sampleText: string = 'hello world'; // 示例文字 @State private inputText: string = ""; // 輸入文字 @State private scanResult: string = ""; // 掃描結果 @State @Watch('selectIndexChanged') selectIndex: number = 0 // 選擇索引 @State @Watch('selectedIndexesChanged') selectedIndexes: number[] = [0]; // 選中索引陣列 private qrCodeId: string = "qrCodeId" // 二維碼 ID @State private scanResultObject: scanBarcode.ScanResult = ({} as scanBarcode.ScanResult) // 掃描結果物件 @State private textColor: string = "#2e2e2e"; // 文字顏色 @State private shadowColor: string = "#d5d5d5"; // 陰影顏色 @State private basePadding: number = 30; // 基礎內邊距 selectedIndexesChanged() { // 選中索引改變事件 console.info(`this.selectedIndexes[0]:${this.selectedIndexes[0]}`) this.selectIndex = this.selectedIndexes[0] } selectIndexChanged() { // 選擇索引改變事件 console.info(`selectIndex:${this.selectIndex}`) this.selectedIndexes[0] = this.selectIndex } private copyToClipboard(text: string): void { // 複製文字到剪貼簿 const pasteboardData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text); // 建立剪貼簿資料 const systemPasteboard = pasteboard.getSystemPasteboard(); // 獲取系統剪貼簿 systemPasteboard.setData(pasteboardData); // 設定資料 promptAction.showToast({ message: '已複製' }); // 彈出提示訊息 } build() { // 構建介面 Column() { // 列布局 SegmentButton({ // 分段按鈕 options: this.buttonOptions, // 選項 selectedIndexes: this.selectedIndexes // 選中索引 }).width('400lpx').margin({ top: 20 }) // 設定寬度和外邊距 Tabs({ index: this.selectIndex }) { // 選項卡 TabContent() { // 選項卡內容 Scroll() { // 滾動檢視 Column() { // 列布局 Column() { // 列布局 Row() { // 行佈局 Text('示例') // 文字 .fontColor("#5871ce") // 字型顏色 .fontSize(16) // 字型大小 .padding(`${this.basePadding / 2}lpx`) // 內邊距 .backgroundColor("#f2f1fd") // 背景顏色 .borderRadius(5) // 邊框圓角 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 點選效果 .onClick(() => { // 點選事件 this.inputText = this.sampleText; // 設定輸入文字為示例文字 }); Blank(); // 空白佔位 Text('清空') // 清空按鈕 .fontColor("#e48742") // 字型顏色 .fontSize(16) // 字型大小 .padding(`${this.basePadding / 2}lpx`) // 內邊距 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 點選效果 .backgroundColor("#ffefe6") // 背景顏色 .borderRadius(5) // 邊框圓角 .onClick(() => { // 點選事件 this.inputText = ""; // 清空輸入文字 }); } .height(45) // 設定高度 .justifyContent(FlexAlign.SpaceBetween) // 主軸對齊方式 .width('100%'); // 設定寬度 Divider(); // 分隔線 TextArea({ text: $$this.inputText, placeholder: `請輸入內容` }) // 文字輸入框 .width(`${650 - this.basePadding * 2}lpx`) // 設定寬度 .height(100) // 設定高度 .fontSize(16) // 字型大小 .caretColor(this.textColor) // 游標顏色 .fontColor(this.textColor) // 字型顏色 .margin({ top: `${this.basePadding}lpx` }) // 外邊距 .padding(0) // 內邊距 .backgroundColor(Color.Transparent) // 背景顏色 .borderRadius(0) // 邊框圓角 .textAlign(TextAlign.JUSTIFY); // 文字對齊方式 } .alignItems(HorizontalAlign.Start) // 交叉軸對齊方式 .width('650lpx') // 設定寬度 .padding(`${this.basePadding}lpx`) // 內邊距 .borderRadius(10) // 邊框圓角 .backgroundColor(Color.White) // 背景顏色 .shadow({ // 陰影 radius: 10, // 陰影半徑 color: this.shadowColor, // 陰影顏色 offsetX: 0, // X 軸偏移 offsetY: 0 // Y 軸偏移 }); Row() { // 行佈局 QRCode(this.inputText) // 二維碼元件 .width('300lpx') // 設定寬度 .aspectRatio(1) // 設定寬高比 .id(this.qrCodeId) // 設定 ID SaveButton() // 儲存按鈕 .onClick(async (_event: ClickEvent, result: SaveButtonOnClickResult) => { // 點選事件 if (result === SaveButtonOnClickResult.SUCCESS) { // 如果儲存成功 const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; // 獲取上下文 let helper = photoAccessHelper.getPhotoAccessHelper(context); // 獲取照片訪問助手 try { // 嘗試 let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg'); // 建立圖片資源 let file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); // 開啟檔案 componentSnapshot.get(this.qrCodeId).then((pixelMap) => { // 獲取二維碼快照 let packOpts: image.PackingOption = { format: 'image/png', quality: 100 } // 打包選項 const imagePacker: image.ImagePacker = image.createImagePacker(); // 建立影像打包器 return imagePacker.packToFile(pixelMap, file.fd, packOpts).finally(() => { // 打包並儲存檔案 imagePacker.release(); // 釋放打包器 fs.close(file.fd); // 關閉檔案 promptAction.showToast({ // 彈出提示訊息 message: '圖片已儲存至相簿', // 提示內容 duration: 2000 // 持續時間 }); }); }) } catch (error) { // 捕獲錯誤 const err: BusinessError = error as BusinessError; // 轉換為業務錯誤 console.error(`Failed to save photo. Code is ${err.code}, message is ${err.message}`); // 列印錯誤資訊 } } else { // 如果儲存失敗 promptAction.showToast({ // 彈出提示訊息 message: '設定許可權失敗!', // 提示內容 duration: 2000 // 持續時間 }); } }) } .visibility(this.inputText ? Visibility.Visible : Visibility.Hidden) // 根據輸入文字設定可見性 .justifyContent(FlexAlign.SpaceBetween) // 主軸對齊方式 .width('650lpx') // 設定寬度 .padding(`${this.basePadding}lpx`) // 內邊距 .margin({ top: `${this.basePadding}lpx` }) // 外邊距 .borderRadius(10) // 邊框圓角 .backgroundColor(Color.White) // 背景顏色 .shadow({ // 陰影 radius: 10, // 陰影半徑 color: this.shadowColor, // 陰影顏色 offsetX: 0, // X 軸偏移 offsetY: 0 // Y 軸偏移 }); }.padding({ top: 20, bottom: 20 }) // 設定內邊距 .width('100%') // 設定寬度 }.scrollBar(BarState.Off) // 禁用捲軸 .align(Alignment.Top) // 頂部對齊 .height('100%') // 設定高度 } TabContent() { // 第二個選項卡內容 Scroll() { // 滾動檢視 Column() { // 列布局 Row() { // 行佈局 Text('掃一掃') // 掃一掃文字 .fontSize(20) // 字型大小 .textAlign(TextAlign.Center) // 文字居中對齊 .fontColor("#5871ce") // 字型顏色 .backgroundColor("#f2f1fd") // 背景顏色 .clickEffect({ scale: 0.8, level: ClickEffectLevel.LIGHT }) // 點選效果 .borderRadius(10) // 邊框圓角 .height('250lpx') // 設定高度 .layoutWeight(1) // 佈局權重 .onClick(() => { // 點選事件 if (canIUse('SystemCapability.Multimedia.Scan.ScanBarcode')) { // 檢查是否支援掃描 try { // 嘗試 scanBarcode.startScanForResult(getContext(this), { // 開始掃描 enableMultiMode: true, // 啟用多模式 enableAlbum: true // 啟用相簿選擇 }, (error: BusinessError, result: scanBarcode.ScanResult) => { // 掃描結果回撥 if (error) { // 如果發生錯誤 hilog.error(0x0001, '[Scan CPSample]', // 記錄錯誤日誌 `Failed to get ScanResult by callback with options. Code: ${error.code}, message: ${error.message}`); return; // 退出 } hilog.info(0x0001, '[Scan CPSample]', // 記錄成功日誌 `Succeeded in getting ScanResult by callback with options, result is ${JSON.stringify(result)}`); this.scanResultObject = result; // 設定掃描結果物件 this.scanResult = result.originalValue ? result.originalValue : '無法識別'; // 設定掃描結果文字 }); } catch (error) { // 捕獲錯誤 hilog.error(0x0001, '[Scan CPSample]', // 記錄錯誤日誌 `Failed to start the scanning service. Code:${error.code}, message: ${error.message}`); } } else { // 如果不支援掃描 promptAction.showToast({ message: '當前裝置不支援二維碼掃描' }); // 彈出提示訊息 } }); Line().width(`${this.basePadding}lpx`).aspectRatio(1); // 分隔線 Text('相簿選') // 相簿選擇文字 .fontSize(20) // 字型大小 .textAlign(TextAlign.Center) // 文字居中對齊 .fontColor("#e48742") // 字型顏色 .backgroundColor("#ffefe6") // 背景顏色 .borderRadius(10) // 邊框圓角 .clickEffect({ scale: 0.8, level: ClickEffectLevel.LIGHT }) // 點選效果 .height('250lpx') // 設定高度 .layoutWeight(1) // 佈局權重 .onClick(() => { // 點選事件 if (canIUse('SystemCapability.Multimedia.Scan.ScanBarcode')) { // 檢查是否支援掃描 let photoOption = new photoAccessHelper.PhotoSelectOptions(); // 建立照片選擇選項 photoOption.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 設定 MIME 型別 photoOption.maxSelectNumber = 1; // 設定最大選擇數量 let photoPicker = new photoAccessHelper.PhotoViewPicker(); // 建立照片選擇器 photoPicker.select(photoOption).then((result) => { // 選擇照片 let inputImage: detectBarcode.InputImage = { uri: result.photoUris[0] }; // 獲取選中的圖片 URI try { // 嘗試 detectBarcode.decode(inputImage, // 解碼條形碼 (error: BusinessError, result: Array<scanBarcode.ScanResult>) => { // 解碼結果回撥 if (error && error.code) { // 如果發生錯誤 hilog.error(0x0001, '[Scan Sample]', // 記錄錯誤日誌 `Failed to get ScanResult by callback. Code: ${error.code}, message: ${error.message}`); return; // 退出 } hilog.info(0x0001, '[Scan Sample]', // 記錄成功日誌 `Succeeded in getting ScanResult by callback, result is ${JSON.stringify(result, null, '\u00A0\u00A0')}`); if (result.length > 0) { // 如果有結果 this.scanResultObject = result[0]; // 設定掃描結果物件 this.scanResult = result[0].originalValue ? result[0].originalValue : '無法識別'; // 設定掃描結果文字 } else { // 如果沒有結果 this.scanResult = '不存在二維碼'; // 設定結果文字 } }); } catch (error) { // 捕獲錯誤 hilog.error(0x0001, '[Scan Sample]', // 記錄錯誤日誌 `Failed to detect Barcode. Code: ${error.code}, message: ${error.message}`); } }); } else { // 如果不支援掃描 promptAction.showToast({ message: '當前裝置不支援二維碼掃描' }); // 彈出提示訊息 } }); } .justifyContent(FlexAlign.SpaceEvenly) // 主軸對齊方式 .width('650lpx') // 設定寬度 .padding(`${this.basePadding}lpx`) // 內邊距 .borderRadius(10) // 邊框圓角 .backgroundColor(Color.White) // 背景顏色 .shadow({ // 陰影 radius: 10, // 陰影半徑 color: this.shadowColor, // 陰影顏色 offsetX: 0, // X 軸偏移 offsetY: 0 // Y 軸偏移 }); Column() { // 列布局 Row() { // 行佈局 Text(`解析結果:\n${this.scanResult}`) // 顯示解析結果文字 .fontColor(this.textColor) // 設定字型顏色 .fontSize(18) // 設定字型大小 .layoutWeight(1) // 設定佈局權重 Text('複製') // 複製按鈕文字 .fontColor(Color.White) // 設定字型顏色為白色 .fontSize(16) // 設定字型大小 .padding(`${this.basePadding / 2}lpx`) // 設定內邊距 .backgroundColor("#0052d9") // 設定背景顏色 .borderRadius(5) // 設定邊框圓角 .clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 設定點選效果 .onClick(() => { // 點選事件 this.copyToClipboard(this.scanResult); // 複製掃描結果到剪貼簿 }); }.constraintSize({ minHeight: 45 }) // 設定最小高度約束 .justifyContent(FlexAlign.SpaceBetween) // 設定主軸對齊方式 .width('100%'); // 設定寬度為100% } .visibility(this.scanResult ? Visibility.Visible : Visibility.Hidden) // 根據掃描結果設定可見性 .alignItems(HorizontalAlign.Start) // 設定交叉軸對齊方式 .width('650lpx') // 設定寬度 .padding(`${this.basePadding}lpx`) // 設定內邊距 .margin({ top: `${this.basePadding}lpx` }) // 設定外邊距 .borderRadius(10) // 設定邊框圓角 .backgroundColor(Color.White) // 設定背景顏色為白色 .shadow({ // 設定陰影 radius: 10, // 設定陰影半徑 color: this.shadowColor, // 設定陰影顏色 offsetX: 0, // 設定X軸偏移 offsetY: 0 // 設定Y軸偏移 }); Column() { // 列布局 Row() { // 行佈局 Text(`完整結果:`).fontColor(this.textColor).fontSize(18).layoutWeight(1) // 顯示完整結果文字 }.constraintSize({ minHeight: 45 }) // 設定最小高度約束 .justifyContent(FlexAlign.SpaceBetween) // 設定主軸對齊方式 .width('100%'); // 設定寬度為100% Divider().margin({ top: 2, bottom: 15 }); // 新增分隔線並設定外邊距 Row() { // 行佈局 Text(`${JSON.stringify(this.scanResultObject, null, '\u00A0\u00A0')}`) // 顯示完整掃描結果 .fontColor(this.textColor) // 設定字型顏色 .fontSize(18) // 設定字型大小 .layoutWeight(1) // 設定佈局權重 .copyOption(CopyOptions.LocalDevice); // 設定複製選項 }.constraintSize({ minHeight: 45 }) // 設定最小高度約束 .justifyContent(FlexAlign.SpaceBetween) // 設定主軸對齊方式 .width('100%'); // 設定寬度為100% } .visibility(this.scanResult ? Visibility.Visible : Visibility.Hidden) // 根據掃描結果設定可見性 .alignItems(HorizontalAlign.Start) // 設定交叉軸對齊方式 .width('650lpx') // 設定寬度 .padding(`${this.basePadding}lpx`) // 設定內邊距 .margin({ top: `${this.basePadding}lpx` }) // 設定外邊距 .borderRadius(10) // 設定邊框圓角 .backgroundColor(Color.White) // 設定背景顏色為白色 .shadow({ // 設定陰影 radius: 10, // 設定陰影半徑 color: this.shadowColor, // 設定陰影顏色 offsetX: 0, // 設定X軸偏移 offsetY: 0 // 設定Y軸偏移 }); } .padding({ top: 20, bottom: 20 }) // 設定內邊距 .width('100%') // 設定寬度為100% }.scrollBar(BarState.Off) // 禁用捲軸 .align(Alignment.Top) // 頂部對齊 .height('100%') // 設定高度為100% } } .barHeight(0) // 設定選項卡條高度為0 .tabIndex(this.selectIndex) // 設定當前選中的索引 .width('100%') // 設定寬度為100% .layoutWeight(1) // 設定佈局權重 .onChange((index: number) => { // 選項卡變化事件 this.selectIndex = index; // 更新選擇的索引 }); } .height('100%') // 設定高度為100% .width('100%') // 設定寬度為100% .backgroundColor("#f4f8fb"); // 設定背景顏色 } }