《鸿蒙纪元》 是 张风捷特烈 计划打造的一套 HarmonyOS 开发系列教程合集。致力于创作优质的鸿蒙原生学习资源,帮助开发者进入纯血鸿蒙的开发之中。本系列的所有代码将开源在 HarmonyUnit 项目中:
github
: github.com/toly1994328…
gitee
: gitee.com/toly1994328…
鸿蒙纪元 系列文章列表可在《文章总集》 或 【github 项目首页】 查看。
上一篇,我们简单了解一下鸿蒙中使用 Canvas 自定义的方式,其中涉及到绘制操作在知识树中整理了一下,如下所示。但这只是绘制操作的很少一部分,绘制方面的知识以后有机会再详细介绍。这点
1. 需求分析与数据准备
这一篇将实现手势交互的画板基本功能,效果如下所示:
- 支持手指在屏幕上绘制线条;
- 右上角按钮可以弹出清除提示框,点击确认时清空画板。
绘制 | 清空绘制 |
---|---|
数据准备
界面上需要呈现多条线,而线是由若干点构成的。另外可以指定线的颜色和粗细。所以这里可以封装一个 Line
类维护这些数据;其中点的坐标通过 Point
类记录:
export class Line {
points: Point[];
color: string;
strokeWidth: number;
constructor(
points: Point[],
color: string = '#000000',
strokeWidth: number =2
) {
this.points = points;
this.color = color;
this.strokeWidth = strokeWidth;
}
}
export class Point {
readonly x: number = 0;
readonly y: number = 0;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
业务逻辑类
我们将数据的维护逻辑统一维护在 PainterBloc 中,目前的核心数据是 Line 数组
记录线列表信息;另外绘制的具体逻辑也可以放在其中,以减轻界面组件中的逻辑处理
import { Line, Point } from '../model/Line';
export class PainterBloc {
@Track lines: Line[] = []; // 线列表
private settings: RenderingContextSettings = new RenderingContextSettings(true);
@Track context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
}
线列表数据的维护和用户的拖拽事件息息相关:
- 用户开始拖拽开始时,需要创建 Line 对象,加入线列表。
- 用户拖拽过程中,将触点添加到线列表最后一条线中。
- 用户点击清除时,清空线列表。
2. 平移手势事件
鸿蒙开发中,手势事件 gesture
是所有组件的通用方法。平移手势事件通过 PanGesture
设置, 如下所示:
- onActionStart: 监听平移手势开始的时机。
- onActionUpdate: 监听平移手势更新的时机。
两个函数都会回调 GestureEvent
对象,通过它可以得到手势的具体信息,比如偏移量、落点等。也是我们数据的来源:
---->[pages/painter/view/Painter.ets]----
Canvas(this.model.context)
.width('100%')
.layoutWeight(1)
.backgroundColor('#fafafa')
.gesture(
PanGesture()
.onActionStart((e) => this.onPanStart(e))
.onActionUpdate((e) => this.onPanUpdate(e)),
)
onPanUpdate(e: GestureEvent): void {
// TODO
}
onPanStart(e: GestureEvent): void {
// TODO
}
在视图层中依赖 PainterBloc 对象,手势事件中回调对象的 fingerList 记录了触点信息,这里取第一个触点的坐标。通过 PainterBloc 维护坐标数据:
- onPanStart 开始平移时,通过
PainterBloc#newLine
添加一条线; - onPanUpdate 平移更新时,通过
PainterBloc#updateLine
更新最后一条线的点集;
@State model: PainterBloc = new PainterBloc();
---->[pages/painter/view/Painter.ets]----
onPanStart(e: GestureEvent): void {
if (e.fingerList.length < 0) {
return;
}
let x = e.fingerList[0].localX;
let y = e.fingerList[0].localY;
this.model.newLine(x, y);
}
onPanUpdate(e: GestureEvent): void {
if (e.fingerList.length < 0) {
return;
}
let x = e.fingerList[0].localX;
let y = e.fingerList[0].localY;
this.model.updateLine(x, y);
}
添加线时创建 Line 对象,添加到 lines
列表中;更新时,将当前的点放到最后一条线中。最后在每次更新时,重新绘制即可,这样就可以完成绘制的功能了:
---->[pages/painter/bloc/PainterBloc.ets]----
// 开始平移时,添加新线
newLine(x: number, y: number) {
this.lines.push(new Line([new Point(x, y)]));
}
// 平移更新时,为新线添加点
updateLine(x: number, y: number) {
this.lines[this.lines.length-1].points.push(new Point(x, y));
this.paint(this.context);
}
绘制逻辑也放在了 PainterBloc 中,根据 Line 数组创建一条条线路径完成线条绘制:
paint(canvas: CanvasRenderingContext2D) {
canvas.clearRect(0, 0, this.context.width, this.context.height);
for (let i = 0; i < this.lines.length; i++) {
let cur = this.lines[i];
this.drawLine(canvas, cur);
}
}
///根据点集绘制线
drawLine(canvas: CanvasRenderingContext2D, line: Line) {
canvas.strokeStyle = line.color;
canvas.lineWidth = line.strokeWidth;
canvas.beginPath();
let count = line.points.length;
if (count == 1) return;
let first = line.points[0];
canvas.moveTo(first.x, first.y);
for (let i = 1; i < count; i++) {
let cur = line.points[i];
canvas.lineTo(cur.x, cur.y);
}
canvas.stroke();
}
3. 对话框与清空绘制
通过 AlertDialog.show
方法可以弹出通用形式的对话框,能设置标题、信息、按钮等。点击确定时,通过 PainterBloc#clear 方法清空数据:
showClearDialog(): void {
AlertDialog.show(
{
title: '清空操作提示',
message: '您的当前操作会清空所有的绘制内容,是否确定继续!',
autoCancel: true,
cornerRadius: 8,
alignment: DialogAlignment.Center,
gridCount: 4,
primaryButton: { value: '取消', action: () => {} },
secondaryButton: {
enabled: true,
defaultFocus: true,
style: DialogButtonStyle.HIGHLIGHT,
backgroundColor: '#fe5455',
value: '确定',
action: () => this.model.clear()
}
}
);
}
清空绘制逻辑非常简单,就是将线数据置为空列表,再绘制即可。这里注意一下,和 html 的绘制一样,鸿蒙每次绘制并不会主动清空画板,如果希望画板内容由数据控制,最好在绘制前通过 canvas.clearRect
清空一下当前绘制内容:
clear(): void {
this.lines = [];
this.paint(this.context);
}
这里提交一个小里程碑 v18-手势交互、绘制
4. 尾声
到这里,我们就完成了画板绘制最基础的能力,接下来将继续实现线条粗细和颜色的切换功能。
更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。关注 公众号
并回复 鸿蒙纪元 可领取最新的 xmind 脑图电子版,让我们一起成长,变得更强。我们下次再见~