📸 鸿蒙 PC 端截图标注工具全解析
技术栈 : HarmonyOS NEXT (API 23+) · ArkTS · Canvas · PixelMap
代码行数 : ~1330 行
适用设备: PC / 平板,支持鼠标交互



一、背景与设计目标
PC 端日常办公中,截图工具是最高频的生产力需求之一。撰写文档、制作教程、记录 Bug、协作沟通------几乎每天都需要截图 + 标注。然而 HarmonyOS NEXT 系统的原生截图通常仅提供基础截取能力,缺少标注增强。
本项目基于 HarmonyOS NEXT API 23+,用 ArkTS 语言实现了一款全功能 PC 端截图标注工具,支持截图 → 标注 → 裁剪 → 保存/分享的一站式工作流。整个工具共有约 1330 行 Kotlin-ETS 代码,包含了完整的 UI 交互、Canvas 绘图引擎和图像编解码处理。
二、整体架构
2.1 文件组成
| 文件 | 作用 |
|---|---|
ScreenshotTool.ets |
主组件,含 UI、绘图、业务逻辑 (~1330 行) |
main_pages.json |
注册 pages/ScreenshotTool 路由 |
module.json5 |
声明 ohos.permission.CAPTURE_SCREEN 权限 |
string.json |
添加 capture_screen_reason 权限说明字符串 |
2.2 模块划分
┌──────────────────────────────────────────────┐
│ ScreenshotTool │
├──────────────────────────────────────────────┤
│ ① 常量定义 (工具模式 / 颜色 / 线宽 / 延时) │
│ ② 类型接口 (Annotation / CropRegion / Point) │
│ ③ 绘图工具函数 (箭头/矩形/文字/裁剪覆盖层) │
│ ④ 主组件 (18个状态变量 / 截图 / 标注 / 裁剪) │
│ ⑤ UI 构建 (8 个 @Builder 子组件) │
└──────────────────────────────────────────────┘
三、核心数据结构
3.1 标注数据模型
typescript
interface Annotation {
type: 'arrow' | 'rectangle' | 'text';
startX: number; startY: number;
endX: number; endY: number;
color: string; lineWidth: number;
text: string; fontSize: number;
}
使用联合类型 type 区分标注形态,渲染时根据类型选择对应的绘图函数。
3.2 裁剪区域模型
typescript
interface CropRegion {
x: number; y: number;
width: number; height: number;
}
裁剪区域在鼠标拖拽中动态计算,每次更新触发 Canvas 全量重绘,实现所见即所得的预览。
3.3 工具模式枚举
typescript
const TOOL_SELECT = 0; // 选择
const TOOL_CROP = 1; // 裁剪
const TOOL_ARROW = 2; // 箭头
const TOOL_RECT = 3; // 矩形
const TOOL_TEXT = 4; // 文字
数字常量性能更优,配合 Record<number, string> 快速映射工具名称。
四、关键技术实现
4.1 Canvas 全量重绘策略
renderCanvas() 方法每次执行完整的五步流程:
clearRect清空画布drawImage绘制截图背景(自动缩放至 Canvas 尺寸)- 裁剪模式下绘制覆盖层(暗角遮罩 + 虚线框 + 角标 + 尺寸标注)
- 遍历
annotations数组绘制所有标注 - 拖拽中绘制临时图形
优点:状态一致性极好,不会残留绘图痕迹;实现简单,无需管理脏区域。适合标注数量 < 50 的典型场景。
4.2 箭头绘制的数学原理
箭头头部包含两个翼尖,通过三角函数计算:
typescript
const headLen = Math.max(12, lw * 3);
const angle = Math.atan2(y2 - y1, x2 - x1);
// 翼尖1: 逆时针30度
const hx1 = x2 - headLen * Math.cos(angle - Math.PI / 6);
const hy1 = y2 - headLen * Math.sin(angle - Math.PI / 6);
// 翼尖2: 顺时针30度
const hx2 = x2 - headLen * Math.cos(angle + Math.PI / 6);
const hy2 = y2 - headLen * Math.sin(angle + Math.PI / 6);
设计细节 :headLen = max(12, lw * 3) 使箭头大小随线宽自适应------线宽 2 时 headLen 为 12,线宽 8 时 headLen 为 24,视觉效果始终协调。
4.3 裁剪覆盖层的四区域遮罩
裁剪框外部使用四块半透明黑色矩形制造「暗角」效果:
| 区域 | 起点 | 尺寸 |
|---|---|---|
| 上 | (0, 0) | (cw, region.y) |
| 下 | (0, region.y+height) | (cw, ch - region.y - height) |
| 左 | (0, region.y) | (region.x, height) |
| 右 | (region.x+width, region.y) | (cw - region.x - width, height) |
搭配蓝色虚线边框 (#0078D4)、四角 8×8 白色手柄方块、以及上方的 宽度 × 高度 实时尺寸标注,提供了专业级别的裁剪交互体验。
4.4 文字标注的背景垫片设计
文字标注绘制使用「背景垫片」方案:
typescript
// 先绘制半透明黑色背景
ctx.fillStyle = '#80000000';
ctx.roundRect(x - pad, y - pad, tw + pad * 2, th + pad * 2, 4);
ctx.fill();
// 再绘制带颜色的文字
ctx.fillStyle = color;
ctx.fillText(text, x, y);
这确保了文字在任何颜色的截图背景上都清晰可辨。
4.5 延时截图与倒计时
提供 6 个延时档位:即时 / 3s / 5s / 10s / 15s / 30s。
核心逻辑在 captureScreen() 中通过 for 循环 + await this.sleep(1000) 实现逐秒倒计时。UI 层同步展示:
- 标题栏右侧
LoadingProgress+ 数字倒计时 - 截图按钮在倒计时期间禁用
- 状态栏文字更新提示
4.6 截图尺寸适配
Canvas 默认尺寸为 800×500,截图通过 drawImage 自动缩放显示。裁剪时需通过比例换算还原实际像素坐标:
typescript
const scaleX = this.screenshotWidth / this.canvasWidth;
const scaleY = this.screenshotHeight / this.canvasHeight;
const cropX = Math.round(region.x * scaleX);
const cropY = Math.round(region.y * scaleY);
4.7 图像编码与保存流程
PixelMap → ImagePacker.packing() → ArrayBuffer
→ fileIo.openSync(CREATE|WRITE_ONLY)
→ fileIo.writeSync() → 磁盘文件
使用 PNG 无损格式 + quality: 100,文件名使用时间戳保证唯一性。保存成功后弹出对话框提供「好的」和「分享」两个选项。
五、UI 构建与交互设计
5.1 界面布局(纵向 Column)
┌──────────────────────────────────────┐
│ 📸 截图工具 PC 端截图与标注工具 │ ← 标题栏
│ ⏱ [即时|3s|5s|...] [📷截取][🔄重截] │ ← 截图控制栏
│ ┌──────────────────────────────────┐ │
│ │ Canvas 截图 + 标注显示区域 │ │ ← 画布区域 (核心)
│ └──────────────────────────────────┘ │
│ [✂️应用裁剪] [取消裁剪] │ ← 裁剪操作按钮
│ [选择][裁剪]│[箭头][矩形][文字]│[撤]...│ ← 工具栏
│ 🎨 颜色 ●●●● │ 📏 线宽 ▬▬ │ 🔠 字号│ ← 样式选择栏
│ 状态文字... 1920×1080 │ ← 状态栏
└──────────────────────────────────────┘
5.2 响应式状态管理
工具使用 18 个 @State 变量管理所有 UI 状态:
| 类别 | 变量 | 作用 |
|---|---|---|
| 截图 | currentScreenshot、screenshotWidth/Height |
截图图像与尺寸 |
| 工具 | currentTool、selectedColor/LW/FS |
当前工具和样式 |
| 标注 | annotations |
标注列表 |
| 裁剪 | cropRegion、isCropped |
裁剪区域与状态 |
| 延时 | delayIndex、countdown、isCapturing |
倒计时与截图状态 |
| 交互 | showTextInput、textInputValue |
文字输入弹窗 |
| Canvas | canvasWidth/Height |
画布尺寸 |
| 反馈 | statusText |
状态栏信息 |
@State 变量的任何变更都会自动触发 UI 重渲染,使得标注增删操作简洁高效:
typescript
// 添加标注 --- 展开新数组触发响应式更新
this.annotations = [...this.annotations, ann];
// 撤销标注 --- Array.slice 创建新数组
this.annotations = this.annotations.slice(0, -1);
5.3 三阶段鼠标事件处理
PC 端交互通过 Canvas 的 onMouse 事件实现:
| 阶段 | 动作 | 处理逻辑 |
|---|---|---|
Press 按下 |
记录起始坐标 | 进入绘图/裁剪状态 |
Move 移动 |
实时更新坐标 | 调用 renderCanvas() 渲染预览 |
Release 松开 |
判断有效性 | 距离 > 5px 则加入标注列表 |
特殊处理:
- 裁剪模式的 Move 事件更新
cropRegion变量而非绘制临时图形 - 文字模式的 Press 事件直接弹出输入框,不进入拖拽状态
5.4 抽象的 Builder 组件
typescript
@Builder toolButton(label, toolType) // 自动响应选中状态
@Builder toolDivider() // 竖向分隔线
@Builder actionButton(label, color, bg, action) // 通用操作按钮
toolButton 根据 currentTool 自动切换样式:选中态显示蓝色背景+边框,未选中态为灰色背景。
六、边界处理与容错
6.1 空状态引导
无截图时 Canvas 区域显示 📷 图标 + 引导文字「点击上方截取屏幕按钮」和功能介绍。
6.2 二次确认保护
不可逆操作(重新截图、清空标注)使用 AlertDialog 弹窗确认,防止误操作。
6.3 裁剪边界校验
裁剪区域 < 10×10px 时弹出 Toast 提示「裁剪区域太小」并拒绝执行。
6.4 异常捕获
所有异步操作(截图、裁剪、保存)均被 try/catch 包裹:
console.error输出详细日志promptAction.showToast显示友好提示statusText更新错误状态文字
6.5 按钮禁用逻辑
截图按钮: enabled={!this.isCapturing && this.countdown === 0}
重新截图: enabled={this.currentScreenshot !== null}
截图进行中或倒计时进行中,截图按钮自动禁用,防止重复触发。
七、部署配置
| 配置项 | 文件 | 内容 |
|---|---|---|
| 路由注册 | main_pages.json |
"pages/ScreenshotTool" |
| 权限声明 | module.json5 |
ohos.permission.CAPTURE_SCREEN |
| 权限理由 | string.json |
「用于截取屏幕内容并进行标注编辑」 |
| 启动页 | EntryAbility.ets |
loadContent('pages/ScreenshotTool') |
八、性能特征
| 操作 | 复杂度 | 说明 |
|---|---|---|
| 渲染截图 | O(1) | drawImage GPU 加速 |
| 渲染标注 | O(n) | n = 标注数量 |
| 裁剪预览 | O(1) | 4 次 fillRect + 1 次 strokeRect |
| 保存编码 | O(w×h) | 全像素编码,大图时较耗时 |
优化方向:
- 标注数量 > 100 时可改用双 Canvas 分离截图背景层和标注层
- 4K 分辨率保存时添加进度指示
- 裁剪拖拽中使用缩略图预览提升流畅度
九、总结
本工具使用纯 ArkTS 在 HarmonyOS NEXT 上实现了完整的截图标注工作流。项目的核心亮点:
- 一体化体验:截图 → 标注 → 保存/分享,全部在一个界面完成。
- 专业标注引擎:箭头 30° 翼尖角度、矩形半透明填充、文字背景垫片,均达到专业水准。
- 精细交互反馈:裁剪暗角遮罩、实时尺寸标注、倒计时动画、状态栏同步更新。
- 可靠错误处理:try/catch 异常捕获、二次确认弹窗、按钮禁用状态管理。
从路由配置到权限声明,从 Canvas 绘图到文件编码保存,本项目完整展示了 HarmonyOS NEXT 上 PC 端工具类应用的开发全流程。
后续可扩展方向:
- 接入
@ohos.multimedia.screenshot真实截图 API - 马赛克/模糊标注(隐私保护)
- 截图历史记录管理
- OCR 文字识别
- 云存储一键上传
本文由 AtomCode AI 助手生成,完整源码位于 entry/src/main/ets/pages/ScreenshotTool.ets。