HarmonyOS APP实战-画图APP - 第3篇:矩形与圆形绘制
HarmonyOS APP实战-画图APP - 第3篇实现基本形状绘制1. 开篇上一篇我们完成了画图APP的主页面布局构建了顶部工具栏、底部颜色选择栏和中间画布区域的基础框架。顶部工具栏预留了形状切换按钮底部颜色栏提供了基本的颜色选项画布区域能够响应触摸事件并显示触摸坐标。页面整体交互骨架已经建立但画布还无法真正绘制图形。本篇将在现有框架上注入绘制能力。我们将使用ohos.graphics.drawing模块提供的自绘制API实现矩形、正方形和圆形的绘制功能。具体包括通过触摸事件的按下和抬起位置确定形状的包围盒调用drawRect和drawCircleAPI进行实际绘制并支持底部颜色选择栏改变填充颜色。完成后点击工具栏的形状按钮在画布上拖拽就能生成对应的几何图形。2. 核心实现2.1 基础配置 — 建立绘制模块依赖与初始化在画图APP的工程目录下我们需要新建一个专门的绘制工具模块用于封装所有图形绘制逻辑。同时配置Canvas组件和ohos.graphics.drawing相关的导入。创建ShapeDrawer.ets文件// ShapeDrawer.ets// 本模块封装矩形、圆形等基本形状的绘制方法// 依赖 ohos.graphics.drawing 库中的 Canvas 和画笔相关类import{Canvas,Brush,Pen,Point,Rect}fromohos.graphics.drawing;// Canvas: 画布对象所有绘制操作基于它进行// Brush: 填充画笔用于设置填充颜色、透明度等// Pen: 描边画笔用于设置线条颜色、宽度等// Point: 坐标点对象// Rect: 矩形区域对象exportclassShapeDrawer{privatecanvas:Canvas|nullnull;/** * 初始化绘制器绑定画布实例 * param canvas - 由 ohos.graphics.drawing 创建的 Canvas 对象 */publicinit(canvas:Canvas):void{this.canvascanvas;console.info([ShapeDrawer] Canvas初始化完成);}/** * 绘制填充矩形 * param left - 矩形左上角x坐标 * param top - 矩形左上角y坐标 * param width - 矩形宽度 * param height - 矩形高度 * param color - 填充颜色格式为RGB十六进制字符串如 #FF0000 */publicdrawFillRect(left:number,top:number,width:number,height:number,color:string):void{if(!this.canvas){console.error([ShapeDrawer] Canvas未初始化);return;}// 创建填充画笔Brush设置颜色constbrushnewBrush();brush.setColor(this.parseColor(color));// 创建矩形区域对象使用左上角坐标和宽高constrectnewRect(left,top,leftwidth,topheight);// 保存当前画布状态避免后续操作污染this.canvas.save();// 设置Brush为绘制填充时使用的画笔this.canvas.attachBrush(brush);// 调用drawRect绘制填充矩形this.canvas.drawRect(rect);// 分离画笔恢复画布状态this.canvas.detachBrush();this.canvas.restore();console.info([ShapeDrawer] 绘制矩形: left${left}, top${top}, w${width}, h${height}, color${color});}/** * 绘制填充圆形 * param cx - 圆心x坐标 * param cy - 圆心y坐标 * param radius - 圆半径 * param color - 填充颜色 */publicdrawFillCircle(cx:number,cy:number,radius:number,color:string):void{if(!this.canvas){console.error([ShapeDrawer] Canvas未初始化);return;}constbrushnewBrush();brush.setColor(this.parseColor(color));this.canvas.save();this.canvas.attachBrush(brush);// 使用drawCircle API绘制圆形// 参数依次为圆心坐标点、半径、描边画笔(传null使用填充)、填充画笔this.canvas.drawCircle(newPoint(cx,cy),// 圆心坐标null,// Pen描边画笔不需要轮廓brush// Brush填充画笔);this.canvas.detachBrush();this.canvas.restore();console.info([ShapeDrawer] 绘制圆形: cx${cx}, cy${cy}, r${radius}, color${color});}/** * 将十六进制颜色字符串转为Color对象 * param hex - 十六进制颜色值 * returns Color对象 */privateparseColor(hex:string):Color{// 兼容带#前缀的颜色值constcolorStrhex.startsWith(#)?hex.substring(1):hex;// 支持6位标准色和3位简写色if(colorStr.length3){constrparseInt(colorStr[0]colorStr[0],16);constgparseInt(colorStr[1]colorStr[1],16);constbparseInt(colorStr[2]colorStr[2],16);returnnewColor(r,g,b,255);}elseif(colorStr.length6){constrparseInt(colorStr.substring(0,2),16);constgparseInt(colorStr.substring(2,4),16);constbparseInt(colorStr.substring(4,6),16);returnnewColor(r,g,b,255);}// 默认返回白色returnnewColor(255,255,255,255);}}关键点说明使用ohos.graphics.drawing中的Canvas、Brush、Pen、Point、Rect类。这是 ArkGraphics 2D 提供自绘能力的核心模块。Brush.setColor方法接受Color对象我们需要将常用的十六进制颜色字符串转为Color。drawRect接收一个Rect对象定义矩形区域。drawCircle的签名比较特殊第一个参数是Point圆心坐标第二个是Pen描边画笔传入null表示不描边第三个是Brush填充画笔。注意参数顺序和类型。每次绘制前后调用save()attachBrush()detachBrush()restore()是常规操作模式确保不同绘制之间不会互相影响状态。2.2 核心逻辑 — 实现触摸绘制形状的交互现在进入画布页面的核心绘制逻辑。我们需要在上一篇文章建立的DrawingCanvas组件中捕获用户的触摸事件根据按下的起始点和抬起时的终点确定图形的尺寸和位置然后调用ShapeDrawer进行绘制。在DrawingCanvas.ets中实现触摸绘制逻辑// DrawingCanvas.ets// 画布绘制组件实现触摸手势绘制矩形和圆形import{Canvas,Brush,Pen,Point,Rect}fromohos.graphics.drawing;import{ShapeDrawer}from./ShapeDrawer;Componentexportstruct DrawingCanvas{// 当前选择的绘制模式rect | circle | squarePropshapeType:stringrect;// 当前选中的颜色值PropcurrentColor:string#FF0000;// 触摸起始坐标privatestartX:number0;privatestartY:number0;// 触摸结束坐标privateendX:number0;privateendY:number0;// 画布引用用于privatecanvasRef:Canvas|nullnull;// 绘制工具实例privatedrawer:ShapeDrawernewShapeDrawer();build(){Column(){// 画布区域Canvas().width(100%).height(100%).backgroundColor(#FFFFFF).onReady((){// Canvas组件渲染完成时获取Canvas实例// 实际项目中需要通过Canvas组件的onReady回调获取绘图上下文// 这里简化处理假设this.canvasRef已正确赋值if(this.canvasRef){this.drawer.init(this.canvasRef);}}).onTouch((event:TouchEvent){this.handleCanvasTouch(event);})}.width(100%).height(100%)}/** * 处理画布上的触摸事件 * 按下时记录起始点抬起时根据形状类型绘制 */privatehandleCanvasTouch(event:TouchEvent):void{consttouchInfoevent.getTouches()[0];if(!touchInfo){return;}constxtouchInfo.x;constytouchInfo.y;if(event.typeTouchType.Down){// 记录按下位置this.startXx;this.startYy;console.info([DrawingCanvas] 触摸按下: (${x},${y}));}elseif(event.typeTouchType.Up){// 记录抬起位置this.endXx;this.endYy;// 计算形状的包围盒参数constleftMath.min(this.startX,this.endX);consttopMath.min(this.startY,this.endY);constwidthMath.abs(this.endX-this.startX);constheightMath.abs(this.endY-this.startY);console.info([DrawingCanvas] 触摸抬起: (${this.endX},${this.endY}));// 根据当前选中的形状类型执行绘制switch(this.shapeType){caserect:this.drawer.drawFillRect(left,top,width,height,this.currentColor);break;casesquare:// 正方形取宽高中的较大者使长宽相等constsideMath.max(width,height);this.drawer.drawFillRect(left,top,side,side,this.currentColor);break;casecircle:// 圆形计算圆心坐标和半径constcx(this.startXthis.endX)/2;constcy(this.startYthis.endY)/2;constradiusMath.max(width,height)/2;this.drawer.drawFillCircle(cx,cy,radius,this.currentColor);break;default:console.warn([DrawingCanvas] 未知的形状类型:${this.shapeType});}}}}关键点说明onTouch事件处理器在每次触摸时都会触发通过event.type判断是按下TouchType.Down还是抬起TouchType.Up。绘制时用Math.min和Math.abs计算包围盒确保无论用户从哪个方向拖拽都能正确圈出区域。正方形模式强制宽高相等取用户拖拽范围的较大边长。圆形模式使用拖拽范围的包围盒对角线以中心点为圆心进行计算。this.canvasRef的获取在onReady回调中这是获取 Canvas 上下文的标准方式。实际项目中这里需要更详细的初始化和上下文创建为了示例清晰做了简化。2.3 完整模块 — 集成绘制与页面传参最后我们需要将绘制模块和主页面进行完整集成。主页面Index.ets需要传递用户选择的形状类型和颜色值到DrawingCanvas组件并触发重新绘制。以下是一个集成后的完整主页面代码。在Index.ets中集成绘制功能// Index.ets// 画图APP主页面集成顶部工具栏、底部颜色栏和画布绘制区域import{DrawingCanvas}from./DrawingCanvas;EntryComponentstruct Index{// 当前绘制模式rect | circle | squareStatecurrentShape:stringrect;// 当前选中的颜色初始为红色StatecurrentColor:string#FF0000;build(){Column(){// 顶部工具栏 Row(){// 矩形按钮Button(矩形).onClick((){this.currentShaperect;console.info([Index] 切换到矩形绘制模式);})// 正方形按钮Button(正方形).onClick((){this.currentShapesquare;console.info([Index] 切换到正方形绘制模式);})// 圆形按钮Button(圆形).onClick((){this.currentShapecircle;console.info([Index] 切换到圆形绘制模式);})}.width(100%).padding({left:10,right:10}).backgroundColor(#F0F0F0)// 画布绘制区域 DrawingCanvas({shapeType:$currentShape,currentColor:$currentColor}).layoutWeight(1)// 底部颜色选择栏 Row(){// 六个常用颜色按钮this.colorButton(#FF0000,红色)this.colorButton(#00FF00,绿色)this.colorButton(#0000FF,蓝色)this.colorButton(#FFFF00,黄色)this.colorButton(#FF00FF,紫色)this.colorButton(#000000,黑色)}.width(100%).padding({top:10,bottom:10}).backgroundColor(#333333).justifyContent(FlexAlign.SpaceEvenly)}.width(100%).height(100%).backgroundColor(#E0E0E0)}/** * 底部颜色按钮构建方法 * param color - 颜色值 * param label - 按钮文字 */BuildercolorButton(color:string,label:string){Column(){Circle().width(40).height(40).fill(color).margin({bottom:4})Text(label).fontSize(10).fontColor(#FFFFFF)}.onClick((){this.currentColorcolor;console.info([Index] 选中颜色:${label}(${color}));})}}关键点说明主页面通过State装饰器响应式管理currentShape和currentColor状态。点击顶部形状按钮时更新currentShape通过$currentShape语法将状态同步到子组件DrawingCanvas。底部颜色栏设计为Circle组件加文字标签点击时更新currentColor。Circle是内置组件用于显示预设颜色的色块方便用户选择。DrawingCanvas接收这些参数后在触摸抬起时根据当前形状和颜色绘制对应图形。3. 运行验证在 DevEco Studio 中运行项目预期表现如下启动 APP看到熟悉的顶部工具栏、白色画布和底部颜色栏。点击顶部的“矩形”按钮未高亮显示为选中态下一篇将完善交互反馈然后在画布上从左上方到右下方拖拽手指。抬起手指后画布上应该出现一个红色的填充矩形且矩形区域正好覆盖你拖拽的范围。点击“正方形”按钮再拖拽一个任意矩形区域画布上应该出现一个以较长边为边长的正方形。点击“圆形”按钮拖拽后画布上生成一个填充圆形其直径等于拖拽范围的较长边。点击底部颜色栏的“绿色”按钮再绘制一个矩形新矩形的填充色变为绿色且不影响之前绘制的红色矩形。4. 小结与预告本篇在现有画布基础上成功实现了基本形状的绘制功能。核心是创建了ShapeDrawer工具模块封装了基于ohos.graphics.drawing的drawRect和drawCircleAPI并在DrawingCanvas组件中集成了触摸事件处理根据用户拖拽范围生成矩形、正方形和圆形同时后台颜色栏的状态已经可以影响绘制填充色虽然当前仅支持6种固定颜色。这些功能为画图APP打下了最基础的绘制能力后续所有高级功能都将建立在这个绘制管道之上。下一篇将聚焦“实现触摸手绘功能”。我们会脱离预定义形状允许用户像真正的画笔一样自由绘制轨迹。逻辑上需要记录连续的触摸点利用Path对象和drawPathAPI 实现平滑的曲线渲染。届时画图APP将同时支持“几何形状绘制”和“自由手绘”两种模式。