我觉得QT里面做可拖动图形,主要应该有两种方法,一种是将图形做成控件,直接就有现成的drop可以用,一种是在Canvas上画图,然后将每个图形保存下来,每次拖动都是重画,这两种方法说不上孰优孰劣,各有不同的长处,本篇介绍绘图法。
假设我们有一个按钮,每按动一次就在画布上添加一个实心圆,现在我们要求在添加N个圆后能将某个圆拖动。原理:每次添加,都将圆的信息(坐标,大小等)记录进一个列表里,在每次重绘事件发生时(画布大小改变,拖动某个圆),都调用一个方法将列表遍历,将所有圆重画出来。
话不多说上代码:
//drawmap.qml
import QtQuick 2.9
import QtQuick.Controls 2.5
import "paint.js" as Painter //将一些功能封装进js里面,稍后展示代码
Canvas {
id: mapdraw
anchors.fill: parent
//重绘事件发生时会调用onPaint(),这里是用于不使图形变形
onPaint: {
//将画笔和画布作为参数传递过去,传递可以酌情省略
Painter.setContext(mapdraw.getContext("2d"), mapdraw)
//重绘
Painter.drawCircles()
}
//放一个鼠标区域,相应鼠标事件
MouseArea {
id: mouseevent
anchors.fill: parent
//鼠标按下
onPressed: {
Painter.setContext(mapdraw.getContext("2d"), mapdraw)
//每次使用外部函数进行绘制前不要忘了这一句
mapdraw.requestPaint()
//这里是为了实现选取一个圆,鼠标坐标是为了计算鼠标是否在圆内
Painter.canvasClick(mouseX, mouseY)
}
//鼠标释放
onReleased: {
Painter.setContext(mapdraw.getContext("2d"), mapdraw)
mapdraw.requestPaint()
//停止拖动
Painter.stopDragging()
}
//这个槽默认状态下意思是鼠标既按下又拖动,可以设置为不按下就拖动
onPositionChanged: {
Painter.setContext(mapdraw.getContext("2d"), mapdraw)
mapdraw.requestPaint()
//拖动圆
Painter.dragCircle(mouseX, mouseY)
}
//设置一个按钮用于添加圆
Button {
id: button
width: 60
height: 40
anchors.left: parent.left
anchors.top: parent.top
text: "paint"
onClicked: {
Painter.setContext(mapdraw.getContext("2d"), mapdraw)
mapdraw.requestPaint()
//随机添加一些圆
Painter.addRandomCircle()
}
}
}
}
接下来是js文件,至于为什么要将这些函数另外封装进一个文件?因为qml里面对函数支持有限,比如说不能在函数外面放全局变量,要另外修饰,不如干脆封装成了纯js。
//存放圆的列表
var circles = [];
//画布
var canvas;
//画笔
var context;
//传参函数
function setContext(context_, mapdraw) {
canvas = mapdraw
context = context_;
}
//图形的定义,实际应用中想拖动什么图形,这里就可以自己定义
function Circle(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.isSelected = false;
}
//添加圆
function addRandomCircle() {
// 为圆圈计算一个随机大小和位置
var radius = randomFromTo(10, 60);
var x = randomFromTo(0, canvas.width);
var y = randomFromTo(0, canvas.height);
// 为圆圈计算一个随机颜色
var colors = ["green", "blue", "red", "yellow", "magenta", "orange", "brown", "purple", "pink"];
var color = colors[randomFromTo(0, 8)];
// 创建一个新圆圈
var circle = new Circle(x, y, radius, color);
//把它保存在数组中
circles.push(circle);
//重新绘制画布
drawCircles();
}
//这个功能没有在qml里调用,这里展示下如何从头开始,很简单
function clearCanvas() {
//去除所有圆圈
circles = [];
//重新绘制画布.
drawCircles();
}
function drawCircles() {
// 清除画布,准备绘制
context.clearRect(0, 0, canvas.width, canvas.height);
console.log("draw start")
// 遍历所有圆圈
for(var i=0; i<circles.length; i++) {
var circle = circles[i];
// 绘制圆圈
context.globalAlpha = 0.85;
context.beginPath();
context.arc(circle.x, circle.y, circle.radius, 0, Math.PI*2);
context.fillStyle = circle.color;
context.strokeStyle = "black";
if (circle.isSelected) {
context.lineWidth = 5;
}
else {
context.lineWidth = 1;
}
context.fill();
context.stroke();
}
}
var previousSelectedCircle;
var offsetX;
var offsetY;
function canvasClick(mouseX, mouseY) {
// 取得画布上被单击的点
var clickX = mouseX;
var clickY = mouseY;
// 查找被单击的圆圈
for(var i=circles.length-1; i>=0; i--) {
var circle = circles[i];
//使用勾股定理计算这个点与圆心之间的距离
var distanceFromCenter = Math.sqrt(Math.pow(circle.x - clickX, 2)
+ Math.pow(circle.y - clickY, 2))
// 判断这个点是否在圆圈中
if (distanceFromCenter <= circle.radius) {
// 清除之前选择的圆圈
if (previousSelectedCircle != null)
previousSelectedCircle.isSelected = false;
previousSelectedCircle = circle;
//选择新圆圈
circle.isSelected = true;
offsetX = clickX - circle.x
offsetY = clickY - circle.y
// 使圆圈允许拖拽
isDragging = true;
//更新显示
drawCircles();
//停止搜索
return;
}
}
}
//在某个范围内生成随机数
function randomFromTo(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
var isDragging = false;
function stopDragging() {
isDragging = false;
}
//拖动圆
function dragCircle(mouseX, mouseY) {
// 判断圆圈是否开始拖拽
if (isDragging == true) {
// 判断拖拽对象是否存在
if (previousSelectedCircle != null) {
// 取得鼠标位置
var x = mouseX;
var y = mouseY;
// 将圆圈移动到鼠标位置
previousSelectedCircle.x = x - offsetX;
previousSelectedCircle.y = y - offsetY;
// 更新画布
drawCircles();
}
}
}
以上代码我本地跑通,效果还行,其实这里的代码大部分来自HTML5的同类代码,我做的其实是迁移工作。这样画出来的图形,其实是自带层次的,越晚绘制的圆越在上层。