这一篇文章会实现一个比较有意思的场景,就是在画布上任意绘画。首先会用arkui实现如图的绘画设置窗口,如下图所示:

这个窗口使用DrawingBoard组件完成,代码如下:
这段代码的实现原理如下:
1.整体结构:
这段代码定义了一个名为DrawingBoard的自定义组件,用于实现画板设置界面。它使用了ArkTS的装饰器语法,如@Entry和@Component。
2.状态管理:
组件使用@State装饰器定义了几个响应式状态变量,如strokeWidth、currentLineColor等。这些变量的变化会自动触发UI的更新。
3.UI结构:
界面使用嵌套的Column和Row组件构建。主要包括图形选择、线条颜色选择、线宽调节等部分。
4.图形选择:
使用GridRow和ForEach循环创建一个图形选择网格。每个图形都是一个按钮,点击时更新graphicName和currentGraphicName。
5.颜色选择:
通过CustomDialogController实现颜色选择器对话框。点击"线条颜色"按钮时打开对话框,选择颜色后更新currentLineColor。
6.线宽调节:
使用Slider组件实现线宽调节。滑动时实时更新strokeWidth值。
7.数据传递:
当点击"确定"按钮时,会调用drawingDataSelected回调函数,将选中的图形、线宽和颜色信息传递给父组件。
8.样式设置:
大量使用了链式调用来设置组件的样式,如字体大小、颜色、边距等。
9.响应式设计:
通过@State装饰器和状态变量的绑定,实现了界面的响应式更新。当用户进行操作时,相关的状态变量会更新,从而触发UI的重新渲染。
总的来说,这段代码展示了如何使用ArkTS和ArkUI框架创建一个交互式的画板设置界面。它利用了声明式UI、响应式编程和组件化的概念,使得代码结构清晰,易于理解和维护。
在这段代码中,drawingDataSelected是事件函数,当选择某个绘图模式,并点击确定按钮,就会调用这个函数。
在主程序中,会使用下面的drawingDataSelected函数响应点击”确定“按钮,代码如下。其中这段代码做了一些前期的设置公众,最后调用javascript函数configDrawing完成最终的设置。因为最终的绘制要依赖fabric完成。
configDrawing函数的代码如下:
这个函数名为 configDrawing,用于配置绘图的各种参数。让我详细解释一下它的功能:
1.函数接收三个参数:
- mode:可能是一个字符串,表示绘图模式(例如,“线条”、"矩形"等)。
- color:表示画笔颜色的字符串。
- lineWidth:表示线条宽度的数字或字符串。
2.首先调用 enableDrawing(mode) 函数。这个函数可能用于切换或启用特定的绘图模式。例如,它可能会改变鼠标事件的处理方式,以适应不同的绘图操作。
3.将传入的 color 赋值给 penColor 变量。这可能是一个全局变量,用于存储当前的画笔颜色。
4.将 lineWidth 赋值给 this.lineWidth。这里的 this 可能指向某个特定的对象,可能是整个绘图应用的一个实例或者某个特定的绘图工具对象。
5.设置 canvas.freeDrawingBrush.color 为 penColor。这里假设 canvas 是一个全局变量或者是当前上下文中可访问的对象,可能是一个 HTML5 Canvas 元素或者是某个绘图库(如 Fabric.js)的实例。freeDrawingBrush 通常用于自由绘制模式。
6.设置 canvas.freeDrawingBrush.width 为传入的 lineWidth。这决定了自由绘制时线条的粗细。
总的来说,这个函数的作用是集中配置绘图的各项参数,包括绘图模式、颜色和线宽。它可能是在用户通过UI更改绘图设置后被调用,以更新实际的绘图环境。这种设计使得绘图参数的管理变得集中和统一,便于维护和扩展。
最后,需要介绍enableDrawing函数,这是绘画的核心,通过这个函数,设置了fabric的各种事件,以及绘制当前设置的各种图形,该函数的代码如下:
function enableDrawing(mode) {
// 根据模式设置画布的绘图状态
if(mode != "mouse") {
canvas.enabledDrawing = true;
if(mode === 'drawing') {
canvas.isDrawingMode = true; // 自由绘制模式
} else {
canvas.isDrawingMode = false; // 其他绘图模式
}
} else {
canvas.enabledDrawing = false;
canvas.isDrawingMode = false; // 鼠标模式,禁用绘图
}
// 移除所有现有的鼠标事件监听器
canvas.off('mouse:down');
canvas.off('mouse:up');
canvas.off('mouse:move');
// 声明变量用于存储绘图起始点和当前图形
let startX, startY, shape;
// 鼠标按下事件处理
canvas.on('mouse:down', function (o) {
var pointer = canvas.getPointer(o.e); // 获取鼠标位置
startX = pointer.x;
startY = pointer.y;
// 根据不同的绘图模式创建相应的图形对象
if (mode === 'line') {
// 创建线条
shape = new fabric.Line([startX, startY, startX, startY], {
stroke: penColor,
strokeWidth: lineWidth,
selectable: true,
hasControls: true,
hasBorders: true,
perPixelTargetFind: true,
});
} else if (mode === 'circle') {
// 创建圆形
shape = new fabric.Circle({
left: startX,
top: startY,
radius: 1,
stroke: penColor,
strokeWidth: lineWidth,
selectable: true,
hasControls: true,
hasBorders: true,
perPixelTargetFind: true,
fill: ''
});
} else if (mode === 'rect') {
// 创建矩形
shape = new fabric.Rect({
left: startX,
top: startY,
originX: 'left',
originY: 'top',
width: 1,
height: 1,
stroke: penColor,
strokeWidth: lineWidth,
fill: '',
selectable: true,
hasControls: true,
hasBorders: true,
perPixelTargetFind: true,
});
} else if (mode === 'triangle') {
// 创建三角形
shape = new fabric.Triangle({
left: startX,
top: startY,
originX: 'left',
originY: 'top',
width: 1,
height: 1,
stroke: penColor,
strokeWidth: lineWidth,
fill: '',
selectable: true,
hasControls: true,
hasBorders: true,
perPixelTargetFind: true
});
} else if (mode === 'ellipse') {
// 创建椭圆
shape = new fabric.Ellipse({
left: startX,
top: startY,
originX: 'left',
originY: 'top',
rx: 0.5,
ry: 0.5,
stroke: penColor,
strokeWidth: lineWidth,
fill: '',
selectable: true,
hasControls: true,
hasBorders: true,
perPixelTargetFind: true
});
} else if (mode === 'polyline') {
// 多边形绘制逻辑
if (!isDrawingPolygon) {
isDrawingPolygon = true;
polygonPoints = [];
clearTempCircles();
}
tempCircleColor = 'red';
if (polygonPoints.length > 0) {
tempCircleColor = 'blue';
}
// 创建临时圆点标记点击位置
tempCircle = new fabric.Circle({
radius: 5,
fill: tempCircleColor,
left: pointer.x,
top: pointer.y,
originX: 'center',
originY: 'center',
selectable: false,
hasBorders: false,
hasControls: false,
tempPoint: true
});
canvas.add(tempCircle);
// 添加点到点列表
polygonPoints.push({ x: pointer.x, y: pointer.y });
// 检查是否完成多边形绘制
if (polygonPoints.length > 4 && Math.abs(pointer.x - polygonPoints[0].x) < 10 && Math.abs(pointer.y - polygonPoints[0].y) < 10) {
// 创建多边形
const polygon = new fabric.Polygon(polygonPoints.slice(0, -1), {
stroke: penColor,
strokeWidth: lineWidth,
fill: 'transparent',
selectable: true,
hasBorders: true,
hasControls: true
});
canvas.add(polygon);
// 清理和重置
clearTempCircles();
isDrawingPolygon = false;
polygonPoints = [];
mode = "mouse";
canvas.enabledDrawing = false;
}
} else if (mode === 'bezier') {
// 贝塞尔曲线绘制逻辑
if (!isDrawingBezier) {
isDrawingBezier = true;
bezierPoints = [];
clearTempCircles();
}
tempCircleColor = 'red';
if (polygonPoints.length > 0) {
tempCircleColor = 'blue';
}
let tempCircle = new fabric.Circle({
radius: 5,
fill: tempCircleColor,
left: pointer.x,
top: pointer.y,
originX: 'center',
originY: 'center',
selectable: true,
hasBorders: false,
hasControls: false,
tempPoint: true
})
canvas.add(tempCircle);
bezierPoints.push(tempCircle);
controlCircles.push(tempCircle);
points.push(pointer);
// 检查是否完成贝塞尔曲线绘制
if (bezierPoints.length === 3) {
const bezierCurve = new fabric.Path(`M ${bezierPoints[0].left} ${bezierPoints[0].top} Q ${bezierPoints[1].left} ${bezierPoints[1].top}, ${bezierPoints[2].left} ${bezierPoints[2].top}`, {
stroke: penColor,
strokeWidth: lineWidth,
fill: null,
selectable: true
});
canvas.add(bezierCurve);
// 清理和重置
clearTempCircles();
controlCircles = [];
bezierPoints = [];
isDrawingBezier = false;
mode = "mouse";
canvas.enabledDrawing = false;
}
}
// 将创建的图形添加到画布
if (shape) {
canvas.add(shape);
}
});
// 鼠标移动事件处理
canvas.on('mouse:move', function (o) {
if (!shape) return;
var pointer = canvas.getPointer(o.e);
// 根据不同的绘图模式更新图形尺寸和位置
if (['line', 'rect', 'triangle'].indexOf(mode) > -1) {
var width = pointer.x - startX;
var height = pointer.y - startY;
var params = {
width: Math.abs(width),
height: Math.abs(height)
};
if (width < 0) {
params.left = pointer.x;
}
if (height < 0) {
params.top = pointer.y;
}
shape.set(params);
} else if (mode === 'ellipse') {
var rx = Math.abs(pointer.x - startX) / 2;
var ry = Math.abs(pointer.y - startY) / 2;
shape.set({ rx: rx, ry: ry });
if (pointer.x < startX) {
shape.set({ originX: 'right' });
} else {
shape.set({ originX: 'left' });
}
if (pointer.y < startY) {
shape.set({ originY: 'bottom' });
} else {
shape.set({ originY: 'top' });
}
}
else if (mode === 'circle') {
var radius = Math.sqrt((pointer.x - startX) ** 2 + (pointer.y - startY) ** 2);
shape.set({ radius: radius });
} else if (mode === 'polyline') {
shape.points[1] = {
x: pointer.x,
y: pointer.y
};
}
// 重新渲染画布
canvas.renderAll();
});
// 鼠标松开事件处理
canvas.on('mouse:up', function () {
shape = null;
canvas.selection = true;
canvas.enabledDrawing = false;
canvas.isDrawingMode = false;
if(mode != 'polyline' && mode != 'bezier') {
mode = "mouse";
}
});
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
- 251.
- 252.
- 253.
- 254.
- 255.
- 256.
- 257.
- 258.
- 259.
- 260.
- 261.
- 262.
- 263.
- 264.
- 265.
- 266.
- 267.
- 268.
- 269.
下面对这段代码进行深入介绍:
1.线条绘制(Line):
fabric.Line 创建一条线。它接受两个点的坐标(起点和终点)。初始时,起点和终点相同,后续通过鼠标移动更新终点。
- stroke: 线条颜色
- strokeWidth: 线条宽度
- selectable: 允许选择
- hasControls: 显示控制点
- hasBorders: 显示边框
- perPixelTargetFind: 启用像素级的点击检测
1.圆形绘制(Circle):
fabric.Circle 创建一个圆。初始半径设为1,后续根据鼠标移动调整。
- left 和 top: 圆心位置
- radius: 半径
- fill: 填充颜色(这里设为空字符串,表示无填充)
1.矩形绘制(Rectangle):
fabric.Rect 创建一个矩形。初始宽高为1,后续根据鼠标移动调整。
- originX 和 originY: 定义矩形的原点(这里设为左上角)
- width 和 height: 矩形的宽和高
1.三角形绘制(Triangle):
fabric.Triangle 创建一个三角形。参数与矩形类似,Fabric.js 会自动计算三角形的形状。
1.椭圆绘制(Ellipse):
shape = new fabric.Ellipse({
left: startX,
top: startY,
originX: ‘left’,
originY: ‘top’,
rx: 0.5,
ry: 0.5,
stroke: penColor,
strokeWidth: lineWidth,
fill: ‘’,
selectable: true,
hasControls: true,
hasBorders: true,
perPixelTargetFind: true
});
fabric.Ellipse 创建一个椭圆。
- rx 和 ry: 分别表示椭圆在 x 和 y 方向上的半径
1.多边形绘制(Polyline):
多边形绘制较为复杂,它通过多次点击来创建点,最后形成多边形。
fabric.Polygon 创建多边形,接受一个点数组作为参数。
1.贝塞尔曲线绘制(Bezier):
贝塞尔曲线通过三个点来定义。
这里使用 fabric.Path 来创建贝塞尔曲线。路径字符串使用 SVG 路径语法:
- M: 移动到起点
- Q: 表示二次贝塞尔曲线,后面跟着控制点和终点的坐标
鼠标事件处理:
1.mouse:down
事件:
- 记录起始点
- 根据当前模式创建相应的图形对象
- 对于多边形和贝塞尔曲线,处理点的添加逻辑
2.mouse:move
事件:
- 更新图形的尺寸和位置
- 对不同图形类型有特定的更新逻辑,如更新矩形的宽高,圆的半径等
3.mouse:up
事件:
- 重置绘图状态
- 对于大多数图形,将模式切换回 “mouse”
这个实现展示了 Fabric.js 强大的图形处理能力。它不仅支持基本的图形绘制,还能处理复杂的交互逻辑,如多边形和贝塞尔曲线的创建。通过事件驱动和实时更新,提供了流畅的用户绘图体验。
绘图效果如下图所示:
