
【引言】
在本篇文章中,我們將探討如何在鴻蒙NEXT平臺上實現(xiàn)二維碼的生成與識別功能。通過使用ArkUI組件庫和相關(guān)的媒體庫,我們將創(chuàng)建一個簡單的應用程序,用戶可以生成二維碼并掃描識別。
【環(huán)境準備】
? 操作系統(tǒng):Windows 10
? 開發(fā)工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
? 目標設備:華為Mate60 Pro
? 開發(fā)語言:ArkTS
? 框架:ArkUI
? API版本:API 12
? 權(quán)限:ohos.permission.WRITE_IMAGEVIDEO(為實現(xiàn)將圖片保存至相冊功能)
【項目介紹】
項目結(jié)構(gòu)
我們首先定義一個名為QrCodeGeneratorAndScanner的組件,使用@Component裝飾器進行標記。該組件包含多個狀態(tài)變量和方法,用于處理二維碼的生成、識別和剪貼板操作。組件狀態(tài)
組件的狀態(tài)包括:
buttonOptions: 定義分段按鈕的選項,用于切換生成和識別二維碼的功能。
inputText: 用戶輸入的文本,用于生成二維碼。
scanResult: 掃描結(jié)果文本。
scanResultObject: 存儲掃描結(jié)果的對象。
- 用戶界面構(gòu)建
在build方法中,我們使用Column和Row布局來構(gòu)建用戶界面。主要包含以下部分:
分段按鈕:用戶可以選擇生成二維碼或識別二維碼。
輸入?yún)^(qū)域:用戶可以輸入文本并生成二維碼。
二維碼顯示:根據(jù)輸入文本生成二維碼。
掃描區(qū)域:用戶可以通過相機掃描二維碼或從圖庫選擇圖片進行識別。
二維碼生成
二維碼生成使用QRCode組件,輸入文本通過this.inputText傳遞。用戶輸入后,二維碼會實時更新。二維碼識別
二維碼識別功能通過scanBarcode模塊實現(xiàn)。用戶可以點擊“掃一掃”按鈕,啟動相機進行掃描,或選擇圖庫中的圖片進行識別。識別結(jié)果將顯示在界面上,并提供復制功能。剪貼板操作
用戶可以將掃描結(jié)果復制到剪貼板,使用pasteboard模塊實現(xiàn)。點擊“復制”按鈕后,掃描結(jié)果將被復制,用戶會收到提示。
【完整代碼】
填寫權(quán)限使用聲明字符串: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": "保存功能需要該權(quán)限"
}
]
}
配置權(quán)限: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 中的業(yè)務錯誤和剪貼板操作
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 = ""; // 掃描結(jié)果
@State @Watch('selectIndexChanged') selectIndex: number = 0 // 選擇索引
@State @Watch('selectedIndexesChanged') selectedIndexes: number[] = [0]; // 選中索引數(shù)組
private qrCodeId: string = "qrCodeId" // 二維碼 ID
@State private scanResultObject: scanBarcode.ScanResult = ({} as scanBarcode.ScanResult) // 掃描結(jié)果對象
@State private textColor: string = "#2e2e2e"; // 文本顏色
@State private shadowColor: string = "#d5d5d5"; // 陰影顏色
@State private basePadding: number = 30; // 基礎內(nèi)邊距
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); // 創(chuàng)建剪貼板數(shù)據(jù)
const systemPasteboard = pasteboard.getSystemPasteboard(); // 獲取系統(tǒng)剪貼板
systemPasteboard.setData(pasteboardData); // 設置數(shù)據(jù)
promptAction.showToast({ message: '已復制' }); // 彈出提示消息
}
build() { // 構(gòu)建界面
Column() { // 列布局
SegmentButton({ // 分段按鈕
options: this.buttonOptions, // 選項
selectedIndexes: this.selectedIndexes // 選中索引
}).width('400lpx').margin({ top: 20 }) // 設置寬度和外邊距
Tabs({ index: this.selectIndex }) { // 選項卡
TabContent() { // 選項卡內(nèi)容
Scroll() { // 滾動視圖
Column() { // 列布局
Column() { // 列布局
Row() { // 行布局
Text('示例') // 文本
.fontColor("#5871ce") // 字體顏色
.fontSize(16) // 字體大小
.padding(`${this.basePadding / 2}lpx`) // 內(nèi)邊距
.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`) // 內(nèi)邊距
.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: `請輸入內(nèi)容` }) // 文本輸入框
.width(`${650 - this.basePadding * 2}lpx`) // 設置寬度
.height(100) // 設置高度
.fontSize(16) // 字體大小
.caretColor(this.textColor) // 光標顏色
.fontColor(this.textColor) // 字體顏色
.margin({ top: `${this.basePadding}lpx` }) // 外邊距
.padding(0) // 內(nèi)邊距
.backgroundColor(Color.Transparent) // 背景顏色
.borderRadius(0) // 邊框圓角
.textAlign(TextAlign.JUSTIFY); // 文本對齊方式
}
.alignItems(HorizontalAlign.Start) // 交叉軸對齊方式
.width('650lpx') // 設置寬度
.padding(`${this.basePadding}lpx`) // 內(nèi)邊距
.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'); // 創(chuàng)建圖片資源
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(); // 創(chuàng)建圖像打包器
return imagePacker.packToFile(pixelMap, file.fd, packOpts).finally(() => { // 打包并保存文件
imagePacker.release(); // 釋放打包器
fs.close(file.fd); // 關(guān)閉文件
promptAction.showToast({ // 彈出提示消息
message: '圖片已保存至相冊', // 提示內(nèi)容
duration: 2000 // 持續(xù)時間
});
});
})
} catch (error) { // 捕獲錯誤
const err: BusinessError = error as BusinessError; // 轉(zhuǎn)換為業(yè)務錯誤
console.error(`Failed to save photo. Code is ${err.code}, message is ${err.message}`); // 打印錯誤信息
}
} else { // 如果保存失敗
promptAction.showToast({ // 彈出提示消息
message: '設置權(quán)限失敗!', // 提示內(nèi)容
duration: 2000 // 持續(xù)時間
});
}
})
}
.visibility(this.inputText ? Visibility.Visible : Visibility.Hidden) // 根據(jù)輸入文本設置可見性
.justifyContent(FlexAlign.SpaceBetween) // 主軸對齊方式
.width('650lpx') // 設置寬度
.padding(`${this.basePadding}lpx`) // 內(nèi)邊距
.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 }) // 設置內(nèi)邊距
.width('100%') // 設置寬度
}.scrollBar(BarState.Off) // 禁用滾動條
.align(Alignment.Top) // 頂部對齊
.height('100%') // 設置高度
}
TabContent() { // 第二個選項卡內(nèi)容
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) // 布局權(quán)重
.onClick(() => { // 點擊事件
if (canIUse('SystemCapability.Multimedia.Scan.ScanBarcode')) { // 檢查是否支持掃描
try { // 嘗試
scanBarcode.startScanForResult(getContext(this), { // 開始掃描
enableMultiMode: true, // 啟用多模式
enableAlbum: true // 啟用相冊選擇
},
(error: BusinessError, result: scanBarcode.ScanResult) => { // 掃描結(jié)果回調(diào)
if (error) { // 如果發(fā)生錯誤
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; // 設置掃描結(jié)果對象
this.scanResult = result.originalValue ? result.originalValue : '無法識別'; // 設置掃描結(jié)果文本
});
} 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) // 布局權(quán)重
.onClick(() => { // 點擊事件
if (canIUse('SystemCapability.Multimedia.Scan.ScanBarcode')) { // 檢查是否支持掃描
let photoOption = new photoAccessHelper.PhotoSelectOptions(); // 創(chuàng)建照片選擇選項
photoOption.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 設置 MIME 類型
photoOption.maxSelectNumber = 1; // 設置最大選擇數(shù)量
let photoPicker = new photoAccessHelper.PhotoViewPicker(); // 創(chuàng)建照片選擇器
photoPicker.select(photoOption).then((result) => { // 選擇照片
let inputImage: detectBarcode.InputImage = { uri: result.photoUris[0] }; // 獲取選中的圖片 URI
try { // 嘗試
detectBarcode.decode(inputImage, // 解碼條形碼
(error: BusinessError, result: Array<scanBarcode.ScanResult>) => { // 解碼結(jié)果回調(diào)
if (error && error.code) { // 如果發(fā)生錯誤
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) { // 如果有結(jié)果
this.scanResultObject = result[0]; // 設置掃描結(jié)果對象
this.scanResult = result[0].originalValue ? result[0].originalValue : '無法識別'; // 設置掃描結(jié)果文本
} else { // 如果沒有結(jié)果
this.scanResult = '不存在二維碼'; // 設置結(jié)果文本
}
});
} 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`) // 內(nèi)邊距
.borderRadius(10) // 邊框圓角
.backgroundColor(Color.White) // 背景顏色
.shadow({ // 陰影
radius: 10, // 陰影半徑
color: this.shadowColor, // 陰影顏色
offsetX: 0, // X 軸偏移
offsetY: 0 // Y 軸偏移
});
Column() { // 列布局
Row() { // 行布局
Text(`解析結(jié)果:\n${this.scanResult}`) // 顯示解析結(jié)果文本
.fontColor(this.textColor) // 設置字體顏色
.fontSize(18) // 設置字體大小
.layoutWeight(1) // 設置布局權(quán)重
Text('復制') // 復制按鈕文本
.fontColor(Color.White) // 設置字體顏色為白色
.fontSize(16) // 設置字體大小
.padding(`${this.basePadding / 2}lpx`) // 設置內(nèi)邊距
.backgroundColor("#0052d9") // 設置背景顏色
.borderRadius(5) // 設置邊框圓角
.clickEffect({ level: ClickEffectLevel.LIGHT, scale: 0.8 }) // 設置點擊效果
.onClick(() => { // 點擊事件
this.copyToClipboard(this.scanResult); // 復制掃描結(jié)果到剪貼板
});
}.constraintSize({ minHeight: 45 }) // 設置最小高度約束
.justifyContent(FlexAlign.SpaceBetween) // 設置主軸對齊方式
.width('100%'); // 設置寬度為100%
}
.visibility(this.scanResult ? Visibility.Visible : Visibility.Hidden) // 根據(jù)掃描結(jié)果設置可見性
.alignItems(HorizontalAlign.Start) // 設置交叉軸對齊方式
.width('650lpx') // 設置寬度
.padding(`${this.basePadding}lpx`) // 設置內(nèi)邊距
.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(`完整結(jié)果:`).fontColor(this.textColor).fontSize(18).layoutWeight(1) // 顯示完整結(jié)果文本
}.constraintSize({ minHeight: 45 }) // 設置最小高度約束
.justifyContent(FlexAlign.SpaceBetween) // 設置主軸對齊方式
.width('100%'); // 設置寬度為100%
Divider().margin({ top: 2, bottom: 15 }); // 添加分隔線并設置外邊距
Row() { // 行布局
Text(`${JSON.stringify(this.scanResultObject, null, '\u00A0\u00A0')}`) // 顯示完整掃描結(jié)果
.fontColor(this.textColor) // 設置字體顏色
.fontSize(18) // 設置字體大小
.layoutWeight(1) // 設置布局權(quán)重
.copyOption(CopyOptions.LocalDevice); // 設置復制選項
}.constraintSize({ minHeight: 45 }) // 設置最小高度約束
.justifyContent(FlexAlign.SpaceBetween) // 設置主軸對齊方式
.width('100%'); // 設置寬度為100%
}
.visibility(this.scanResult ? Visibility.Visible : Visibility.Hidden) // 根據(jù)掃描結(jié)果設置可見性
.alignItems(HorizontalAlign.Start) // 設置交叉軸對齊方式
.width('650lpx') // 設置寬度
.padding(`${this.basePadding}lpx`) // 設置內(nèi)邊距
.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 }) // 設置內(nèi)邊距
.width('100%') // 設置寬度為100%
}.scrollBar(BarState.Off) // 禁用滾動條
.align(Alignment.Top) // 頂部對齊
.height('100%') // 設置高度為100%
}
}
.barHeight(0) // 設置選項卡條高度為0
.tabIndex(this.selectIndex) // 設置當前選中的索引
.width('100%') // 設置寬度為100%
.layoutWeight(1) // 設置布局權(quán)重
.onChange((index: number) => { // 選項卡變化事件
this.selectIndex = index; // 更新選擇的索引
});
}
.height('100%') // 設置高度為100%
.width('100%') // 設置寬度為100%
.backgroundColor("#f4f8fb"); // 設置背景顏色
}
}