純血HarmonyOS5 打造小游戲實踐:繪畫板(附源文件)

OS

應用整體架構與技術棧

該繪圖應用采用了鴻蒙系統推薦的ArkUI框架進行開發,基于TypeScript語言編寫,充分利用了鴻蒙系統的圖形渲染和文件操作能力。應用整體架構遵循MVVM(Model-View-ViewModel)模式,通過@State裝飾器實現狀態與視圖的雙向綁定,確保數據變化時UI能夠自動更新。

技術棧主要包括:

  • ArkUI框架:提供聲明式UI開發能力,支持響應式布局和組件化開發
  • Canvas繪圖API:通過CanvasRenderingContext2D實現底層繪圖邏輯
  • 文件操作API:使用fileIo和fs模塊進行文件讀寫和管理
  • 系統交互API:通過window、promptAction等模塊實現系統交互功能

核心功能模塊解析

狀態管理與數據模型

應用使用@State裝飾器管理核心狀態,這些狀態直接影響UI展示和用戶交互:

@State brushSize: number = 10; // 畫筆大小
@State brushColor: string = '#000000'; // 畫筆顏色
@State backgroundColor1: string = '#FFFFFF'; // 背景顏色
@State isEraser: boolean = false; // 是否使用橡皮擦
@State drawingPoints: Array<Array<number>> = []; // 繪制的點數據
@State isDrawing: boolean = false; // 是否正在繪制

其中,drawingPoints是一個二維數組,用于存儲繪制軌跡的坐標點,每個元素形如[x, y],記錄了用戶繪制時的每一個關鍵點。這種數據結構使得應用能夠高效地重繪整個畫布,即使在界面旋轉或尺寸變化時也能保持繪制內容的完整性。

繪圖核心邏輯實現

繪圖功能的核心在于drawLine方法,它負責在畫布上繪制線條,并根據是否為橡皮擦模式應用不同的繪制樣式:

drawLine(x1: number, y1: number, x2: number, y2: number) {this.context.beginPath();this.context.moveTo(x1, y1);this.context.lineTo(x2, y2);// 設置畫筆樣式if (this.isEraser) {// 橡皮擦效果this.context.strokeStyle = this.backgroundColor1;this.context.lineWidth = this.brushSize * 1.5;} else {// 畫筆效果this.context.strokeStyle = this.brushColor;this.context.lineWidth = this.brushSize;this.context.lineCap = 'round';this.context.lineJoin = 'round';}this.context.stroke();
}

橡皮擦功能的實現采用了巧妙的設計:通過將筆觸顏色設置為背景色,并適當增加線條寬度,實現了擦除已有繪制內容的效果。lineCaplineJoin屬性設置為round,使得線條端點和連接處呈現圓角效果,提升了繪制線條的美觀度。

畫布管理與交互處理

Canvas組件的交互處理是繪圖應用的關鍵,代碼中通過onTouch事件監聽實現了繪制軌跡的記錄:

onTouch((event) => {const touch: TouchObject = event.touches[0];const touchX = touch.x;const touchY = touch.y;switch (event.type) {case TouchType.Down:this.isDrawing = true;this.drawingPoints.push([touchX, touchY]);break;case TouchType.Move:if (this.isDrawing) {this.drawingPoints.push([touchX, touchY]);this.drawLine(touchX, touchY, touchX, touchY);}break;case TouchType.Up:this.isDrawing = false;break;}
});

這段代碼實現了典型的觸摸事件三階段處理:

  • 按下(Down):開始繪制,記錄起始點
  • 移動(Move):持續記錄移動軌跡,繪制線條
  • 抬起(Up):結束繪制

通過這種方式,應用能夠準確捕捉用戶的繪制意圖,并將其轉化為畫布上的線條。

界面設計與用戶體驗優化

響應式布局設計

應用采用了ArkUI的響應式布局特性,確保在不同尺寸的屏幕上都能良好顯示:

build() {Column() {// 頂部工具欄Row({ space: 15 }) { /* 工具欄組件 */ }// 顏色選擇區Row({ space: 5 }) { /* 顏色選擇組件 */ }// 繪畫區域Stack() { /* Canvas組件 */ }// 底部操作區Column() { /* 說明文本和保存按鈕 */ }}.width('100%').height('100%');
}

根布局使用Column垂直排列各功能區塊,頂部工具欄、顏色選擇區、繪畫區域和底部操作區依次排列。各組件使用百分比寬度(如width('90%'))和相對單位,確保界面元素能夠根據屏幕尺寸自動調整。

交互組件設計

應用提供了直觀的用戶交互組件,包括:

  1. 工具欄
    • 清除按鈕:一鍵清空畫布
    • 橡皮擦/畫筆切換按鈕:通過顏色變化直觀顯示當前模式
    • 畫筆大小滑塊:實時調整畫筆粗細
  1. 顏色選擇區
    • 預設七種常用顏色,選中時顯示黑色邊框
    • 點擊顏色塊即可切換當前畫筆顏色
  1. 畫布區域
    • 初始狀態顯示提示文本"點擊開始繪畫"
    • 支持手勢繪制,實時顯示繪制內容
  1. 保存功能
    • 底部醒目的保存按鈕,點擊后將畫布內容保存為PNG圖片

圖片保存與文件操作

圖片導出功能實現

圖片保存功能是該應用的重要組成部分,通過exportCanvas方法實現:

exportCanvas() {try {// 獲取畫布數據URLconst dataUrl = this.context.toDataURL('image/png');if (!dataUrl) {promptAction.showToast({message: '獲取畫布數據失敗',duration: 2000});return;}// 解析Base64數據const base64Data = dataUrl.split(';base64,').pop() || '';const bufferData = new Uint8Array(base64Data.length);for (let i = 0; i < base64Data.length; i++) {bufferData[i] = base64Data.charCodeAt(i);}// 生成保存路徑const timestamp = Date.now();const fileName = `drawing_${timestamp}.png`;const fileDir = getContext().filesDir;const filePath = `${fileDir}/${fileName}`;// 寫入文件fileIo.open(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY).then((file) => {// 寫入文件內容并處理后續邏輯}).catch((err:Error) => {// 錯誤處理});} catch (error) {console.error('導出畫布時發生錯誤:', error);promptAction.showToast({message: '保存圖片失敗',duration: 2000});}
}

該方法首先通過toDataURL獲取畫布的PNG格式數據URL,然后將Base64編碼的數據轉換為Uint8Array,最后使用fileIo模塊將數據寫入文件系統。這種實現方式確保了畫布內容能夠準確地保存為圖片文件。

文件操作與錯誤處理

代碼中采用了Promise鏈式調用處理文件操作的異步邏輯,并包含了完整的錯誤處理機制:

fileIo.open(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY).then((file) => {fileIo.write(file.fd, bufferData.buffer).then(() => {fileIo.close(file.fd).then(() => {promptAction.showToast({message: '保存圖片成功',duration: 2000});}).catch((err: Error) => {console.error('關閉文件失敗:', err);promptAction.showToast({message: '保存圖片失敗',duration: 2000});});}).catch((err:Error) => {console.error('寫入文件失敗:', err);fileIo.close(file.fd).then(() => {promptAction.showToast({message: '保存圖片失敗',duration: 2000});});});
}).catch((err:Error) => {console.error('打開文件失敗:', err);promptAction.showToast({message: '保存圖片失敗',duration: 2000});
});

這種分層的錯誤處理方式確保了無論在文件打開、寫入還是關閉階段發生錯誤,都能給出適當的錯誤提示,并確保資源被正確釋放。

技術要點

關鍵技術要點

  1. 狀態管理:使用@State實現數據與UI的雙向綁定,簡化了狀態更新邏輯
  2. Canvas繪圖:掌握CanvasRenderingContext2D的基本操作,包括路徑繪制、樣式設置等
  3. 異步操作:通過Promise和async/await處理文件操作等異步任務
  4. 響應式布局:利用ArkUI的布局組件和百分比單位實現適配不同屏幕的界面

總結

本文介紹的鴻蒙繪圖應用實現了基礎的繪圖功能,包括畫筆繪制、橡皮擦、顏色選擇和圖片保存等核心功能。通過ArkUI框架和Canvas繪圖API的結合,展示了鴻蒙系統在圖形應用開發方面的強大能力。

對于開發者而言,該應用可以作為進一步開發復雜繪圖應用的基礎。通過添加更多繪圖工具(如矩形、圓形、文本工具)、圖像處理功能(如濾鏡、調整亮度對比度)以及云同步功能,能夠將其拓展為功能完善的繪圖應用。

在鴻蒙生態不斷發展的背景下,掌握這類圖形應用的開發技術,將有助于開發者創造出更多優秀的用戶體驗,滿足不同用戶的需求。

附:代碼

import { mediaquery, promptAction, window } from '@kit.ArkUI';
import { fileIo } from '@kit.CoreFileKit';
import preferences from '@ohos.data.preferences';@Entry
@Component
struct Index {@State brushSize: number = 10; // 畫筆大小@State brushColor: string = '#000000'; // 畫筆顏色@State backgroundColor1: string = '#FFFFFF'; // 背景顏色@State isEraser: boolean = false; // 是否使用橡皮擦@State drawingPoints: Array<Array<number>> = []; // 繪制的點數據@State isDrawing: boolean = false; // 是否正在繪制// 預設顏色private presetColors: Array<string> = ['#000000', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF'];// 畫布參數private canvasWidth: number = 0;private canvasHeight: number = 0;private context: CanvasRenderingContext2D = new CanvasRenderingContext2D({ antialias: true});// 頁面初始化aboutToAppear(): void {// 設置頁面背景色window.getLastWindow(getContext()).then((windowClass) => {windowClass.setWindowBackgroundColor('#F5F5F5');});}// 清除畫布clearCanvas() {this.drawingPoints = [];this.redrawCanvas();}// 重繪畫布redrawCanvas() {this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight);this.context.fillStyle = this.backgroundColor1;this.context.fillRect(0, 0, this.canvasWidth, this.canvasHeight);// 重繪所有繪制點for (let i = 0; i < this.drawingPoints.length; i++) {const point = this.drawingPoints[i];if (i > 0) {const prevPoint = this.drawingPoints[i - 1];this.drawLine(prevPoint[0], prevPoint[1], point[0], point[1]);}}}// 繪制線條drawLine(x1: number, y1: number, x2: number, y2: number) {this.context.beginPath();this.context.moveTo(x1, y1);this.context.lineTo(x2, y2);// 設置畫筆樣式if (this.isEraser) {// 橡皮擦效果this.context.strokeStyle = this.backgroundColor1;this.context.lineWidth = this.brushSize * 1.5;} else {// 畫筆效果this.context.strokeStyle = this.brushColor;this.context.lineWidth = this.brushSize;this.context.lineCap = 'round';this.context.lineJoin = 'round';}this.context.stroke();}// 導出畫布為圖片exportCanvas() {try {// 獲取畫布數據URLconst dataUrl = this.context.toDataURL('image/png');if (!dataUrl) {promptAction.showToast({message: '獲取畫布數據失敗',duration: 2000});return;}// 解析Base64數據const base64Data = dataUrl.split(';base64,').pop() || '';const bufferData = new Uint8Array(base64Data.length);for (let i = 0; i < base64Data.length; i++) {bufferData[i] = base64Data.charCodeAt(i);}// 生成保存路徑const timestamp = Date.now();const fileName = `drawing_${timestamp}.png`;const fileDir = getContext().filesDir;const filePath = `${fileDir}/${fileName}`;// 寫入文件fileIo.open(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY).then((file) => {fileIo.write(file.fd, bufferData.buffer).then(() => {fileIo.close(file.fd).then(() => {promptAction.showToast({message: '保存圖片成功',duration: 2000});console.info(`圖片已保存至: ${filePath}`);}).catch((err: Error) => {console.error('關閉文件失敗:', err);promptAction.showToast({message: '保存圖片失敗',duration: 2000});});}).catch((err:Error) => {console.error('寫入文件失敗:', err);fileIo.close(file.fd).then(() => {promptAction.showToast({message: '保存圖片失敗',duration: 2000});});});}).catch((err:Error) => {console.error('打開文件失敗:', err);promptAction.showToast({message: '保存圖片失敗',duration: 2000});});} catch (error) {console.error('導出畫布時發生錯誤:', error);promptAction.showToast({message: '保存圖片失敗',duration: 2000});}}build() {Column() {// 頂部工具欄Row({ space: 15 }) {// 清除按鈕Button('清除').width('20%').height('8%').fontSize(14).backgroundColor('#FFCCCC').onClick(() => {this.clearCanvas();});// 橡皮擦按鈕Button(this.isEraser ? '橡皮擦':'畫筆').width('18%').height('8%').fontSize(14).backgroundColor(this.isEraser ? '#FFCCCC' : '#CCFFCC').onClick(() => {this.isEraser = !this.isEraser;});// 畫筆大小控制Column() {Text('畫筆').fontSize(12).margin({ bottom: 2 });Slider({min: 1,max: 30,value: this.brushSize,// showTips: true}).width('60%').onChange((value: number) => {this.brushSize = value;});}.width('30%');}.width('100%').padding(10).backgroundColor('#E6E6E6');// 顏色選擇區Row({ space: 5 }) {ForEach(this.presetColors, (color: string) => {Stack() {// 顯示顏色塊Column().width(30).height(30).borderRadius(5).backgroundColor(color).borderWidth(this.brushColor === color ? 2 : 0).borderColor('#000000') // 統一使用黑色邊框表示選中狀態,避免顏色沖突.onClick(() => {this.brushColor = color;this.isEraser = false; // 切換顏色時取消橡皮擦模式console.log(`Selected color: ${color}`)});}.width(30).height(30).onClick(() => {this.brushColor = color;this.isEraser = false; // 切換顏色時取消橡皮擦模式});});}.width('100%').padding(10).backgroundColor('#FFFFFF');// 繪畫區域Stack() {Canvas(this.context).aspectRatio(3/4).width('90%').height('60%').backgroundColor(this.backgroundColor1).borderRadius(10).onReady(() => {this.context.fillStyle = this.backgroundColor1;this.context.fillRect(0, 0, this.canvasWidth, this.canvasHeight);}).onAreaChange((oldVal, newVal) => {this.canvasWidth = newVal.width as number;this.canvasHeight = newVal.height as number;this.context.fillStyle = this.backgroundColor1;this.context.fillRect(0, 0, this.canvasWidth, this.canvasHeight);}).onTouch((event) => {const touch: TouchObject = event.touches[0];const touchX = touch.x;const touchY = touch.y;switch (event.type) {case TouchType.Down:this.isDrawing = true;this.drawingPoints.push([touchX, touchY]);break;case TouchType.Move:if (this.isDrawing) {this.drawingPoints.push([touchX, touchY]);// 使用更平滑的繪制方式this.drawLine(touchX, touchY, touchX, touchY);}break;case TouchType.Up:this.isDrawing = false;break;}});// 提示文本if (this.drawingPoints.length === 0) {Text('點擊開始繪畫').fontSize(18).fontColor('#999').fontStyle(FontStyle.Italic);}}.width('100%').margin({ top: 20, bottom: 30 });// 底部說明Text('簡單繪畫板 - 拖動手指即可繪制').fontSize(14).fontColor('#666').margin({ bottom: 20 });Button('保存圖片', { type: ButtonType.Normal, stateEffect: true }).width('90%').height(40).fontSize(16).fontColor('#333333').backgroundColor('#E0E0E0').borderRadius(8).onClick(() => {this.exportCanvas();});}.width('100%').height('100%');}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/86130.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/86130.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/86130.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據分析和可視化:Py爬蟲-XPath解析章節要點總結

重要知識點 XPath 概述&#xff1a;XPath 是一門可以在 XML 文件中查找信息的語言&#xff0c;也可用于 HTML 文件。它功能強大&#xff0c;提供簡潔明了的路徑表達式和多個函數&#xff0c;用于字符串、數值、時間比較等。1999 年成為 W3C 標準&#xff0c;常用于爬蟲中抓取網…

深入理解PHP中的生成器(Generators)

創建一個生成器非常簡單。你只需要像定義普通函數一樣定義它&#xff0c;但是使用yield關鍵字來產出值。例如&#xff0c;以下是一個簡單的斐波那契數列生成器&#xff1a; function fibonacci() {$num1 0;$num2 1;while (true) {yield $num1;$temp $num1 $num2;$num1 $n…

ubuntu 系統 pgm圖片和png相互轉化

ubuntu 系統 pgm圖片和png相互轉化。 安裝轉化工具&#xff1a; sudo apt-get install imagemagick pgm轉為png指令如下: convert input.pgm output.png png轉為pgm指令如下: convert input.png output.pgm

leetcode:98. 驗證二叉搜索樹

學習要點 加深純遞歸算法的理解 題目鏈接 98. 驗證二叉搜索樹 - 力扣&#xff08;LeetCode&#xff09; 題目描述 解法&#xff1a;純遞歸 vector<int> v;void dfs(TreeNode* root){if(root nullptr){return;}dfs(root->left);v.push_back(root->val);dfs(root…

如何確定IP的缺省子網掩碼是多少?

IP地址 201.100.200.1 的缺省子網掩碼由其 IP地址類別 決定。以下是判斷步驟&#xff1a; 1. 確定IP地址類別 IPv4地址分為 A、B、C、D、E 五類&#xff0c;根據第一個字節&#xff08;前8位&#xff09;的范圍劃分&#xff1a; A類&#xff1a;1.0.0.0 ~ 126.255.255.255&am…

Vue.js 粒子連線動畫組件 - FlyingLines

Vue.js 粒子連線動畫組件 - FlyingLines 使用指南 &#x1f31f; 簡介 FlyingLines 是一個基于 Vue.js 的炫酷粒子連線動畫組件&#xff0c;可以為您的網站添加動態的背景效果。該組件具有以下特點&#xff1a; ? 流暢動畫&#xff1a;基于 Canvas 的高性能渲染&#x1f5b…

無人機交互控制技術要點

一、技術要點 1. 物理交互設計 仿生柔性形態學&#xff1a;采用梯度剛度復合材料&#xff08;如硅膠-碳纖維&#xff09;設計柔性抓取器&#xff0c;模仿鳥類爪部結構&#xff0c;實現被動碰撞抑制與動態力生成&#xff0c;支持高速交互&#xff08;>3 m/s&#xff09;和…

qt集成openssl

第一&#xff1a;下載項目中對應版本的openssl的庫 https://openssl-library.org/source/old/1.0.2/ 老版本的openssl的下載地址&#xff0c;這個下載的好像是源碼&#xff0c;還要編譯。 https://indy.fulgan.com/SSL/ 在這里下載不需要編譯&#xff0c;下載下來直接用dll文件…

【鴻蒙HarmonyOS Next App實戰開發】??ArkUI時鐘界面實現解析:動態雙模式時鐘與沉浸式體驗?

在鴻蒙next系統上&#xff0c;通過ArkTS寫了個時鐘顯示頁面&#xff0c;集成在【圖影工具箱】應用中&#xff0c;應用商店可以下載使用。 這個頁面實現起來比較簡單&#xff0c;就是左邊一個模擬時鐘&#xff0c;右邊一個數字時鐘&#xff08;包含時間和日期的文字&#xff09…

ios簽名錯誤的解決辦法

另一種最常見的解決方案。在終端中運行以下命令。您應該添加自己的鑰匙串名稱和密碼。security lock-keychain temp.keychainsecurity unlock-keychain -pp ssw0rd temp.keychain在這種情況下&#xff0c;使用鑰匙串名稱為“temp”&#xff0c;其密碼為“p ssw0rd”。此外&am…

C#讀取OPCUA節點數據

本人第一次接觸OPCUA&#xff0c;如有不對的地方望指正&#xff0c;獲取的是公司的OPCUA服務器的數據 方式一&#xff1a; 測試環境: window11 vs2022 OPCFoundation.NetStandard.Opc.Ua .net framework 4.8 (2025-06-23 經過測試&#xff0c;.net8也可以使用這套.net …

OpenCV計算機視覺實戰(11)——邊緣檢測詳解

OpenCV計算機視覺實戰&#xff08;11&#xff09;——邊緣檢測詳解 0. 前言1. Sobel 算子與方向梯度1.1 Sobel 算子簡介1.2 實現過程 2. Laplacian 邊緣檢測2.1 Laplacian 算子簡介2.2 實現過程 3. Canny 算法3.1 Canny 算法簡介3.2 實現過程 小結系列鏈接 0. 前言 邊緣檢測能…

哈爾濱idc服務器租用-青蛙云

在數字化浪潮洶涌的當下&#xff0c;企業對于服務器的需求愈發強烈。哈爾濱作為東北地區重要的經濟文化中心&#xff0c;其 IDC 服務器租用市場也呈現出蓬勃發展的態勢。眾多企業在尋求 IDC 服務器租用時&#xff0c;青蛙云憑借自身顯著優勢脫穎而出&#xff0c;成為眾多用戶的…

Zephyr 系統深入解析:SoC 支持包結構與中斷調度器調優實踐

本文將全面深入講解 Zephyr RTOS 的 SoC 支持包設計架構&#xff08;SoC Series / SoC Variant&#xff09;、中斷系統實現、調度器原理、時間片與優先級調優技巧&#xff0c;以及如何在實際項目中構建自定義 SoC 支持包、實現高效的調度器策略和系統性能優化。全文超過 5000 字…

FPGA基礎 -- Verilog 結構建模之模塊參數值

Verilog 中模塊參數值&#xff08;parameter&#xff09;的使用&#xff0c;這是結構建模和模塊可配置設計的核心機制&#xff0c;廣泛應用于 總線寬度配置、流水線級數、功能開關、模塊復用 等場景。 一、什么是模塊參數值&#xff08;parameter&#xff09; parameter 是 Ver…

Skrill是什么?中國用戶能用嗎?安全嗎?完整指南

什么是Skrill&#xff1f; Skrill 前身為 Moneybookers&#xff0c;成立于 2001 年&#xff0c;總部位于英國倫敦&#xff0c;目前隸屬于 Paysafe 集團。作為一個多功能電子支付平臺&#xff0c;Skrill 支持全球 100 多個國家和地區、40 多種貨幣&#xff0c;被廣泛用于&#…

java+vue+SpringBoo校園部門資料管理系統(程序+數據庫+報告+部署教程+答辯指導)

源代碼數據庫LW文檔&#xff08;1萬字以上&#xff09;開題報告答辯稿ppt部署教程代碼講解代碼時間修改工具 技術實現 開發語言&#xff1a;后端&#xff1a;Java 前端&#xff1a;vue框架&#xff1a;springboot數據庫&#xff1a;mysql 開發工具 JDK版本&#xff1a;JDK1.…

Java中的Map實現類詳解

Java中的Map實現類詳解 Java集合框架提供了多種Map接口的實現&#xff0c;每種實現都有其特定的使用場景和特點。以下是主要的Map實現類及其特性分析&#xff1a; 1. 通用Map實現 HashMap 特點&#xff1a;基于哈希表的實現&#xff0c;允許null鍵和null值線程安全&#xf…

Pytorch Lightning 進階 1 - 梯度檢查點(Gradient Checkpointing)

梯度檢查點&#xff08;Gradient Checkpointing&#xff09;是一種在深度學習訓練中優化顯存使用的技術&#xff0c;尤其適用于處理大型模型&#xff08;如Transformer架構&#xff09;時顯存不足的情況。下面用簡單的例子解釋其工作原理和優缺點&#xff1a; 核心原理 深度學…

SpreadJS 迷你圖:數據趨勢可視化的利器

引言 在數據處理和分析領域&#xff0c;直觀地展示數據趨勢對于理解數據和做出決策至關重要。迷你圖作為一種簡潔而有效的數據可視化方式&#xff0c;在顯示數據趨勢方面發揮著重要作用&#xff0c;尤其在與他人共享數據時&#xff0c;能夠快速傳達關鍵信息。SpreadJS 作為一款…