HarmonyOS APP实战-画图APP - 第6篇:形状切换与UI优化

HarmonyOS APP实战-画图APP - 第6篇:形状切换与UI优化
HarmonyOS APP实战-画图APP - 第6篇切换绘制模式与交互反馈1. 开篇在上一篇中我们为画图APP实现了完整的画笔属性调节功能通过色盘选择颜色、滑块控制粗细和透明度并将这些属性绑定到Pen对象使得所有后续绘制的图形都遵循这些属性。至此用户已经可以自由调整画笔样式但绘制的形状还是固定的——矩形、圆形、自由画笔只能通过修改代码来切换无法在运行时动态选择。本篇将实现绘制模式切换功能在工具栏中增加矩形、圆形、自由画笔三个模式按钮点击后画布触摸事件根据当前模式绘制对应图形。同时优化UI反馈让选中模式高亮显示并显示当前模式名称。这个模块的完成将使画图APP从“可调画笔的绘图工具”进化为“真正的多模式绘图应用”操作体验更接近专业的绘画软件。2. 核心实现2.1 基础配置首先在已有的工程中我们需要确保已导入ohos.graphics.drawing模块。由于我们在第3篇和第4篇中已经使用过该模块本篇无需额外导入。在pages/Index.ets中我们会引入一个新的自定义组件ModeSelector并列在工具栏区域。为了管理绘制模式我们在全局或页面级别定义一个枚举// 定义绘制模式枚举enumDrawMode{RECT矩形,CIRCLE圆形,FREEHAND自由画笔}然后在主页面中新增一个状态变量记录当前模式StatecurrentMode:DrawModeDrawMode.FREEHAND;// 默认自由画笔2.2 ModeSelector 组件实现ModeSelector组件接收当前模式并回调切换事件。它使用三个Button组件根据是否选中设置不同样色高亮。Componentstruct ModeSelector{privatemodes:DrawMode[][DrawMode.RECT,DrawMode.CIRCLE,DrawMode.FREEHAND];LinkcurrentMode:DrawMode;// 与父组件双向绑定build(){Row(){ForEach(this.modes,(mode:DrawMode){Button(mode)// 按钮文字直接显示枚举值的字符串.width(80).height(40).backgroundColor(modethis.currentMode?#FF4081:#3D5AFE).fontColor(Color.White).fontSize(14).borderRadius(20).margin({left:8}).onClick((){this.currentModemode;// 更新当前模式})},(mode:DrawMode)mode)}.padding({left:10}).alignItems(VerticalAlign.Center)}}关键点说明Link双向绑定当用户点击按钮时currentMode变化会同步到父组件父组件再传递给DrawingCanvas。高亮逻辑backgroundColor(mode this.currentMode ? #FF4081 : #3D5AFE)选中时用粉色突出。ForEach遍历枚举数组确保每个模式对应一个按钮。按钮文字直接使用枚举值的字符串如“矩形”用户一目了然。2.3 DrawingCanvas 组件增强原来的DrawingCanvas组件中触摸事件逻辑是固定的例如之前默认只画矩形。现在我们需要根据mode决定调用哪个绘制方法。此外触摸事件需要区分三种模式的交互方式矩形/圆形需要记录按下坐标和移动坐标松开时完成绘制。自由画笔需要连续的Path轨迹。我们将DrawingCanvas的mode属性设置为Prop接收并在触摸事件onTouch中根据mode分发。下面是核心的触摸事件处理逻辑Componentstruct DrawingCanvas{privatecanvasWidth:number0;privatecanvasHeight:number0;Stateprivateshapes:ShapeData[][];// 存储已绘制的图形数据Propmode:DrawMode;// 当前模式从父组件传入// 临时绘图数据用于当前正在绘制的图形privatestartX:number0;privatestartY:number0;privatecurrentPath:drawing.Pathnewdrawing.Path();build(){Canvas(this.context).width(100%).height(100%).onReady((){this.canvasWidththis.context.width;this.canvasHeightthis.context.height;}).onTouch((event:TouchEvent){constxevent.touches[0].x;constyevent.touches[0].y;switch(event.type){caseTouchType.Down:if(this.modeDrawMode.RECT||this.modeDrawMode.CIRCLE){// 记录起始点this.startXx;this.startYy;}elseif(this.modeDrawMode.FREEHAND){// 开始新路径this.currentPath.moveTo(x,y);}break;caseTouchType.Move:if(this.modeDrawMode.FREEHAND){// 路径连线this.currentPath.lineTo(x,y);// 实时绘制到画布this.drawAllShapes();// 重绘所有图形包含当前路径}break;caseTouchType.Up:if(this.modeDrawMode.RECT){constrect{x:this.startX,y:this.startY,width:x-this.startX,height:y-this.startY};this.shapes.push({type:rect,params:rect});this.drawAllShapes();}elseif(this.modeDrawMode.CIRCLE){// 以起点到终点的距离为半径constdxx-this.startX;constdyy-this.startY;constradiusMath.sqrt(dx*dxdy*dy);constcircle{cx:this.startX,cy:this.startY,radius:radius};this.shapes.push({type:circle,params:circle});this.drawAllShapes();}elseif(this.modeDrawMode.FREEHAND){// 完成路径保存当前路径数据this.shapes.push({type:path,path:this.currentPath});this.currentPathnewdrawing.Path();// 重置路径this.drawAllShapes();}break;}})}// 根据 shapes 数组重绘所有图形privatedrawAllShapes(){constcanvasthis.context;canvas.clear();for(constshapeofthis.shapes){// 根据类型绘制省略具体的画笔属性设置沿用之前定义的全局Pen// 此处假设已有全局 pen 对象canvas.attachPen(pen);if(shape.typerect){constrshape.paramsasRectParams;canvas.drawRect(r.x,r.y,r.xr.width,r.yr.height);}elseif(shape.typecircle){constcshape.paramsasCircleParams;canvas.drawCircle(c.cx,c.cy,c.radius);}elseif(shape.typepath){canvas.drawPath(shape.path);}canvas.detachPen();}// 如果当前有正在绘制的路径自由画笔移动时也绘制临时路径if(this.modeDrawMode.FREEHANDthis.currentPath){canvas.attachPen(pen);canvas.drawPath(this.currentPath);canvas.detachPen();}}}关键点说明使用drawing.Path对象存储自由画笔的连续轨迹触摸Move时不断追加lineTo并实时重绘。矩形和圆形采用TouchType.Down记录起点TouchType.Up完成绘制并保存到shapes数组。每次完成一个图形后调用drawAllShapes()刷新画布drawAllShapes会清除画布并重绘所有已保存的图形。为了支持圆形我们计算了起点到终点的欧氏距离作为半径实际项目中也可采用用户拖拽的方向定义半径。注意在drawAllShapes中临时路径的绘制必须在循环之后。2.4 主页面整合将ModeSelector和DrawingCanvas组合到主页面中。主页面布局为顶部工具栏包含模式选择按钮和之前的画笔属性控件中间画布区域。EntryComponentstruct Index{StatecurrentMode:DrawModeDrawMode.FREEHAND;build(){Column(){// 工具栏Row(){ModeSelector({currentMode:$currentMode})// 其他工具栏按钮颜色、粗细等略}.width(100%).height(50).backgroundColor(#F5F5F5)// 模式提示文字Text(当前模式:${this.currentMode}).fontSize(14).fontColor(#666).width(100%).textAlign(TextAlign.Center)// 画布DrawingCanvas({mode:this.currentMode}).layoutWeight(1)}.width(100%).height(100%)}}关键点说明$currentMode使用State的引用传递给ModeSelector的Link实现双向绑定。DrawingCanvas通过Prop接收当前模式无需双向修改。模式提示文字让用户明确当前操作状态提升交互清晰度。3. 运行验证编译运行APP后顶部工具栏会显示三个圆形按钮默认选中“自由画笔”粉色高亮。点击“矩形”按钮按钮颜色变为粉色同时模式提示文字变为“当前模式: 矩形”。此时在画布上按下并拖动松开后即绘制一个矩形起点到终点。切换为“圆形”拖动时根据起点和终点的距离绘制圆形。切换为“自由画笔”手指移动即可连续绘制路径。所有绘制的图形颜色、粗细、透明度均遵循当前画笔属性设置。4. 小结与预告本篇实现了三种绘制模式的动态切换矩形、圆形、自由画笔。通过ModeSelector组件提供了清晰的UI反馈高亮选中按钮、显示当前模式DrawingCanvas组件根据模式正确处理触摸事件使用ohos.graphics.drawing的drawRect、drawCircle、drawPath完成图形绘制。现在用户可以在APP中方便地切换绘图工具体验接近专业画图软件。下一篇「在画布上添加文字」将实现文本绘制功能用户输入文字内容选择字体大小、颜色并拖拽放置在画布上。我们将使用ohos.graphics.drawing中的文本相关API让画图APP支持文字标注进一步完善功能。