QML Canvas绘制可以拖动的图形

QML Canvas绘制可以拖动的图形

  • 写在前面
  • 原理
  • 代码

写在前面

我觉得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的同类代码,我做的其实是迁移工作。这样画出来的图形,其实是自带层次的,越晚绘制的圆越在上层。

你可能感兴趣的:(QML学习,QT,QML,QTQuick,绘图,拖动)