HarmonyOS APP实战-画图APP - 第7篇:文本绘制功能
HarmonyOS APP实战-画图APP - 第7篇在画布上添加文字1. 开篇在第6篇中我们完成了绘制模式的切换与交互反馈优化。通过DrawMode枚举Rect、Circle、FreeDraw管理当前绘制模式在工具栏中实现了高亮选中状态并在画布触摸事件中根据模式调用不同的绘制逻辑drawRect、drawCircle、drawPath。顶部显示当前模式标签用户操作时获得明确的视觉反馈。本篇我们将为画图APP增加文字绘制功能。文字是图形创作中不可或缺的元素——添加标题、批注、签名等场景都需要它在画布上呈现。实现思路是用户通过一个弹出窗口输入文字、选择字体大小和颜色然后在画布上点击或拖拽放置文本。我们将使用ohos.graphics.drawing模块的drawTextAPI在Canvas上渲染文字并实现可拖拽定位的交互。2. 核心实现2.1 基础配置首先导入绘制文字所需的模块并扩展我们的状态管理。在Index.ets顶部添加导入。// Index.ets — 基础配置与模块导入import{CanvasRenderingContext2D}fromohos.graphics.drawing;// 定义文字对象的数据结构interfaceTextItem{content:string;// 文字内容fontSize:number;// 字体大小pxcolor:string;// 颜色十六进制字符串x:number;// 文本左上角x坐标y:number;// 文本左上角y坐标width?:number;// 文本渲染宽度用于碰撞检测height?:number;// 文本渲染高度用于碰撞检测}// 扩展DrawMode枚举添加TEXT模式enumDrawMode{Rect,Circle,FreeDraw,Text// 新增文字模式}关键点说明TextItem接口存储文字的所有属性width和height用于触摸命中检测判断用户是否点到文本区域。DrawMode.Text作为新的绘制模式与之前的形状/画笔模式平级。后续我们需要在画布渲染循环中遍历TextItem数组并调用drawText。2.2 FontPicker组件字体大小与颜色选择器用户输入文字时需要同时选择字体大小和颜色。我们创建一个独立的FontPicker组件它显示一个自定义弹窗或内嵌UI。// components/FontPicker.ets — 字体大小与颜色选择器Componentexportstruct FontPicker{ConsumefontSize:number;// 从父组件继承的字体大小ConsumefontColor:string;// 从父组件继承的字体颜色build(){Column(){// 字体大小滑块Row(){Text(字体大小:).fontSize(16).width(80)Slider({value:this.fontSize,min:12,max:120,step:4,style:SliderStyle.OutSet}).width(200).onChange((value:number){this.fontSizevalue;// 通过Consume双向绑定})}.margin({bottom:12})// 颜色选择器预设色块Row(){Text(字体颜色:).fontSize(16).width(80)// 使用Grid展示常用颜色ForEach([#000000,#FF0000,#00FF00,#0000FF,#FFFF00,#FF00FF,#00FFFF,#FFFFFF],(color:string){Circle({width:30,height:30}).fill(color).stroke(this.fontColorcolor?Color.Black:Color.White).strokeWidth(3).margin(4).onClick((){this.fontColorcolor;})})}}.padding(12).backgroundColor(#F5F5F5).borderRadius(8)}}关键点说明使用Consume装饰器实现父子组件双向数据绑定父组件提供fontSize和fontColor两个状态变量。Slider组件控制字体大小范围12~120px步进4px。颜色选择器用8个预设色块点击直接赋值实际项目可扩展为完整的色盘参考第5篇的颜色选择逻辑。此组件被嵌入到文本输入弹窗中跟随弹窗显示。2.3 文本输入与拖拽放置模块TextTool核心逻辑当用户点击工具栏的“文字”按钮后模式切换为Text。点击画布任意位置弹出输入框让用户输入文字内容并确认。文字对象随即出现在点击位置用户可继续拖拽调整位置。// components/TextTool.ets — 文字输入与拖拽放置模块Componentexportstruct TextTool{privatecanvasCtx:CanvasRenderingContext2D;LinktextItems:TextItem[];// 画布上所有文字对象StatefontSize:number36;// 当前字体大小StatefontColor:string#000000;// 当前字体颜色StateisAddingText:booleanfalse;// 是否正在输入文字StateinputContent:string;// 输入框内容privatetextDialogController:CustomDialogController;build(){Column(){// 文字模式专用按钮Button(添加文字).fontSize(16).onClick((){this.isAddingTexttrue;// 弹出自定义对话框用于输入文字this.showTextInputDialog();})}}// 显示文字输入对话框使用CustomDialogshowTextInputDialog(){this.textDialogControllernewCustomDialogController({builder:TextInputDialog({fontSize:this.fontSize,fontColor:this.fontColor,onConfirm:(content:string){this.inputContentcontent;this.isAddingTexttrue;this.textDialogController.close();},onCancel:(){this.isAddingTextfalse;this.textDialogController.close();}}),autoCancel:true,alignment:DialogAlignment.Center,offset:{dx:0,dy:-80}});this.textDialogController.open();}// 在画布上放置文字的触摸事件处理handleCanvasTouch(event:TouchEvent):void{if(this.isAddingTextthis.inputContent){// 获取触摸点坐标相对于画布consttouchXevent.touches[0].x;consttouchYevent.touches[0].y;// 创建一个新的TextItem并添加到textItems数组constnewText:TextItem{content:this.inputContent,fontSize:this.fontSize,color:this.fontColor,x:touchX,y:touchY};this.textItems[...this.textItems,newText];// 重置状态准备下一次输入this.inputContent;this.isAddingTextfalse;// 重绘画布this.redrawCanvas();}else{// 尝试拖拽已有的文字对象this.dragTextItem(event);}}// 拖拽文字在触摸移动时更新坐标dragTextItem(event:TouchEvent){consttouchXevent.touches[0].x;consttouchYevent.touches[0].y;// 遍历文字列表检查触摸点是否落在某个文字区域内for(letithis.textItems.length-1;i0;i--){constitemthis.textItems[i];// 计算文字区域估算宽度content长度*fontSize*0.6高度fontSize*1.2constestimatedWidthitem.content.length*item.fontSize*0.6;constestimatedHeightitem.fontSize*1.2;// 判断触摸点是否在文字矩形区域内if(touchXitem.xtouchXitem.xestimatedWidthtouchYitem.y-estimatedHeighttouchYitem.y){// 更新坐标if(event.typeTouchType.Move){item.xtouchX-estimatedWidth/2;item.ytouchY;this.textItems[...this.textItems];// 触发UI更新this.redrawCanvas();}break;}}}// 重新绘制画布包含所有文字redrawCanvas(){if(!this.canvasCtx)return;// 清空画布并重新绘制所有图形此处简化实际需结合之前的绘制指令this.canvasCtx.clearRect(0,0,this.canvasCtx.width,this.canvasCtx.height);// ... 重新绘制之前的形状 画笔轨迹省略与第3-6篇代码集成this.drawAllTexts();}// 遍历文字列表绘制所有文字drawAllTexts(){if(!this.canvasCtx)return;this.textItems.forEach((item:TextItem){this.canvasCtx.font${item.fontSize}px sans-serif;this.canvasCtx.fillStyleitem.color;this.canvasCtx.textAlignleft;this.canvasCtx.textBaselinetop;this.canvasCtx.fillText(item.content,item.x,item.y);});}}关键点说明使用CustomDialogController弹出输入对话框TextInputDialog组件需另定义用于输入文字内容。触摸事件处理当处于“添加文字”状态且内容非空时触摸点即文字放置位置否则尝试拖拽已有文字。estimatedWidth和estimatedHeight是粗略估算的文字区域用于触摸命中判断。精确计算可使用CanvasRenderingContext2D.measureText但因异步问题本实现采用估算方式仍可满足基本交互。drawAllTexts方法在每次重绘时调用使用fillText绘制文字。font、fillStyle依据TextItem属性动态设置。2.4 在画布组件中集成文字绘制最后将TextTool集成到主画布组件中在渲染循环中调用文字绘制。// Index.ets — 主画布组件核心代码片段Componentstruct DrawingCanvas{privatecanvasCtx:CanvasRenderingContext2DnewCanvasRenderingContext2D();StatecurrentMode:DrawModeDrawMode.Rect;StatetextItems:TextItem[][];// 文字对象数组privatetextTool:TextToolnewTextTool();build(){Column(){// ... 工具栏省略包含模式切换按钮// 文字模式下显示TextTool组件if(this.currentModeDrawMode.Text){TextTool({canvasCtx:this.canvasCtx,textItems:$textItems}).width(100%).height(60)}// 画布区域Canvas(this.canvasCtx).width(100%).layoutWeight(1).backgroundColor(#FFFFFF).onTouch((event:TouchEvent){if(this.currentModeDrawMode.Text){this.textTool.handleCanvasTouch(event);}else{// 原有形状/画笔模式处理略}})}.width(100%).height(100%)}}关键点说明TextItem数组通过State声明驱动UI更新。触摸事件根据currentMode分发文字模式调用TextTool.handleCanvasTouch其他模式走原有逻辑。TextTool组件接收canvasCtx和textItems引用实现跨组件的协作。3. 运行验证在DevEco Studio中运行项目按照以下步骤验证点击工具栏新增的“文字”按钮模式切换为文字模式按钮高亮。点击画布任意位置弹出文字输入对话框。输入文字内容例如“Hello HarmonyOS”通过滑块调整字体大小为48px选择红色。点击“确定”后输入的文字出现在点击位置。再次在文字区域内触摸并拖动文字跟随手指移动。重复上述步骤添加多个文字可叠加在之前的图形之上。预期表现文字清晰渲染颜色和大小与设置一致拖拽行为流畅触摸命中区域基本准确。4. 小结与预告本篇完成了文本绘制功能在画图APP中的集成。核心产出包括定义了TextItem数据结构支持存储内容、字体、颜色、坐标。实现了FontPicker组件提供字体大小和颜色选择UI。实现了TextTool模块包含文字输入对话框、触摸放置和拖拽逻辑。使用CanvasRenderingContext2D.fillTextAPI渲染文字并与原有绘制逻辑共存。至此画图APP已支持矩形、圆形、自由画笔和文字四种绘制元素用户的创作能力大大增强。下一篇《使用effectKit处理图像》将引入ohos.effectKit模块为画布内容添加亮度调节、模糊化、灰度调节等图像特效。届时用户在绘制完成后可选择应用滤镜实现更丰富的视觉效果。我们将在第8篇中构建特效面板并集成到工具栏中。