版权声明:本文系做者原创。未经许可,不得转载。java
用Qt实现一个UI:一个圆形图标在圆圈内或圆圈上拖动,但不能拖出到圆圈外。当拖动到圆圈上时,高亮图标和圆圈。相似有RingLock。 一、继承QQuickPaintedItem类,该类为QQuickItem的子类。QQuickItem用于不用显示UI的供QML使用的组件;QQuickPaintedItem用于须要显示UI的供QML使用的组件。本案例中,须要画图,故而继承QQuickPaintedItem。 /*imagedragwidget.h*/ #ifndef IMAGEDRAGWIDGET_H #define IMAGEDRAGWIDGET_H #include <QQuickPaintedItem> #include <QtCore> #include <QtGui> class imageDragWidget : public QQuickPaintedItem { Q_OBJECT public: explicit imageDragWidget(QQuickPaintedItem *parent = 0); ~imageDragWidget(); signals: //鼠标按下 void dragPress(); //鼠标在圆圈内移动 void dragMoveIn(); //鼠标在圆圈上移动 void dragMoveOn(); //鼠标释放 void dragRelease(); //鼠标移出圆圈,确认关机 void dragOut(); public slots: protected: void paint(QPainter * painter); void mouseMoveEvent(QMouseEvent *event); void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); private: //判断鼠标和圆圈的位置关系:圆圈外、圆圈上、圆圈内 int circleContain(void); //判断鼠标和图标的位置关系:图标外、图标上、图标内 int powerContain(void); //获得鼠标与圆心连线和圆圈的交点 QPoint GetPoint(QPoint currentPoint, QPoint circleCenter, int raduis); private: QPixmap *circle_defaultImg; QPixmap *circle_boldImg; QPixmap *power_haloImg; QPixmap *power_solidImg; QPixmap *power_defaultImg; //当前圆圈图片 QPixmap *circleImg; //圆圈图片所在矩形 QRect *circleImgRect; //当前图标图片 QPixmap *powerImg; //图标图片所在矩形 QRect *powerImgRect; //当前鼠标所在位置 QPoint currentMousePoint; //图标所在位置 QPoint powerCenterPoint; //鼠标是否按下的标志 bool pressFlag; //鼠标是否移出的标志 bool isOut; //宽度缩放比例 double widthScale; //高度缩放比例 double heightScale; }; #endif // IMAGEDRAGWIDGET_H /*imagedragwidget.cpp*/ #include "imagedragwidget.h" #include <QDebug> imageDragWidget::imageDragWidget(QQuickPaintedItem *parent) : QQuickPaintedItem(parent) { //获得屏幕尺寸 QScreen *screen = QGuiApplication::primaryScreen(); int screen_width = screen->size().width(); int screen_height = screen->size().height(); qDebug()<<"屏幕尺寸: "<<screen_width<<"*"<<screen_height; //圆圈所在图片尺寸为:452*452; 图标所在图片尺寸为:350*350 //滑动图标的尺寸128*128 double widgetScale = (double)580/(double)720; qDebug()<<"控件占屏幕比例: "<<widgetScale; //设置控件尺寸 setContentsSize(QSize(screen_width*widgetScale, screen_width*widgetScale)); int widget_width = contentsSize().width(); int widget_height = contentsSize().height(); qDebug()<<"控件尺寸: "<<widget_width<<"*"<<widget_height; //接收鼠标左键 setAcceptedMouseButtons(Qt::LeftButton); circle_defaultImg = new QPixmap(":/images/circle_default.png"); circle_boldImg = new QPixmap(":/images/circle_bold.png"); power_haloImg = new QPixmap(":/images/power_halo.png");; power_solidImg = new QPixmap(":/images/power_solid.png"); power_defaultImg = new QPixmap(":/images/power_default.png"); isOut = false; circleImg = circle_defaultImg; int circle_width = circleImg->width(); int circle_height = circleImg->height(); //设置圆圈图片在实际屏幕上的尺寸 //滑动图标的尺寸128*128 int circle_width_in_widget = widget_width - 128*widgetScale; int circle_height_in_widget = widget_height - 128*widgetScale; qDebug()<<"滑动圆圈尺寸: "<<circle_width_in_widget<<"*"<<circle_height_in_widget; widthScale = (double)circle_width_in_widget/(double)circle_width; heightScale = (double)circle_height_in_widget/(double)circle_height; qDebug()<<"圆圈和图标宽度缩放比例为: "<<widthScale<<"高度缩放比例为: "<<heightScale; circleImgRect = new QRect(0, 0, circle_width*widthScale, circle_height*heightScale); //圆圈图片移到控件中心 circleImgRect->moveCenter(QPoint(widget_width/2, widget_height/2)); powerImg = power_defaultImg; int power_width = powerImg->width(); int power_height = powerImg->height(); powerImgRect = new QRect(0, 0, power_width*widthScale, power_height*heightScale); //图标图片移到控件中心 powerImgRect->moveCenter(circleImgRect->center()); powerCenterPoint = circleImgRect->center(); } void imageDragWidget::paint(QPainter *painter) { painter->drawPixmap(*circleImgRect, *circleImg); painter->drawPixmap(*powerImgRect, *powerImg); } void imageDragWidget::mouseMoveEvent(QMouseEvent *event) { if(pressFlag) { //鼠标已按下 int power_width = powerImgRect->width(); int power_height = powerImgRect->height(); int circle_width = circleImgRect->width(); int circle_height = circleImgRect->height(); currentMousePoint = event->pos(); int flag = circleContain(); if(flag < 0) { //鼠标在圆圈内,则图标移动到鼠标位置 powerImg = power_haloImg; circleImg = circle_defaultImg; powerImgRect->moveCenter(currentMousePoint); powerCenterPoint = currentMousePoint; isOut = false; } else if(flag == 0) { //鼠标在圆圈上,则图标移动到鼠标位置,同时更换图片 powerImg = power_solidImg; circleImg = circle_boldImg; powerImgRect->moveCenter(currentMousePoint); powerCenterPoint = currentMousePoint; isOut = true; } else { //鼠标在圆圈外 isOut = true; if(powerContain() > 0) { //鼠标在圆圈外且在图标外,则等同于鼠标释放。图标回到控件中心。 powerImg = power_defaultImg; powerImgRect->moveCenter(circleImgRect->center()); pressFlag = false; circleImg = circle_defaultImg; } else { //鼠标在圆圈外且不在图标外,则图标移到鼠标与控件中心连线和圆圈的交点。 powerImg = power_solidImg; circleImg = circle_boldImg; powerCenterPoint = GetPoint(currentMousePoint, circleImgRect->center(), circleImgRect->width()/2); powerImgRect->moveCenter(powerCenterPoint); } } powerImgRect->setHeight(power_height); powerImgRect->setWidth(power_width); circleImgRect->setHeight(circle_height); circleImgRect->setWidth(circle_width); update(); if(pressFlag&&(!isOut)) { //鼠标按下且在圆圈内 emit dragMoveIn(); } else if(pressFlag&&isOut){ //鼠标按下且在圆圈上 emit dragMoveOn(); } else if((!pressFlag)&&(isOut)){ //鼠标在圆圈外且在图标外,则等同于鼠标释放。 emit dragRelease(); } if(isOut&&(!pressFlag)) { //鼠标在圆圈外且在图标外,确认关机。 emit dragOut(); } } } void imageDragWidget::mousePressEvent(QMouseEvent *event) { currentMousePoint = event->pos(); if(powerContain() <= 0) { //鼠标进入到图标内,则表示按下 pressFlag = true; int power_width = powerImgRect->width(); int power_height = powerImgRect->height(); powerImg = power_haloImg; powerImgRect->setHeight(power_height); powerImgRect->setWidth(power_width); update(); emit dragPress(); } } void imageDragWidget::mouseReleaseEvent(QMouseEvent *event) { //鼠标释放,图标回到控件中心 currentMousePoint = event->pos(); pressFlag = false; int power_width = powerImgRect->width(); int power_height = powerImgRect->height(); powerCenterPoint = circleImgRect->center(); powerImg = power_defaultImg; powerImgRect->moveCenter(circleImgRect->center()); powerImgRect->setHeight(power_height); powerImgRect->setWidth(power_width); int circle_width = circleImgRect->width(); int circle_height = circleImgRect->height(); circleImg = circle_defaultImg; circleImgRect->setHeight(circle_height); circleImgRect->setWidth(circle_width); update(); emit dragRelease(); if(isOut) { emit dragOut(); } } //判断鼠标是否在圆圈内 //1:圆圈外;0:圆圈上;-1:圆圈内 int imageDragWidget::circleContain(void) { int delta = 0; int raduis = 0; QPoint p1 = QPoint(0, 0); QPoint p2 = QPoint(0, 0); int ret = 0; p1 = currentMousePoint; p2 = circleImgRect->center(); delta = qSqrt(qPow(p1.x() - p2.x(), 2) + qPow(p1.y() - p2.y(), 2)); raduis = circleImgRect->width()/2; if(delta > raduis) { ret = 1; } else if(delta < raduis) { ret = -1; } else { ret = 0; } return ret; } //判断鼠标是否在图标内 //1:图标外;0:图标上;-1:图标内 int imageDragWidget::powerContain(void) { int delta = 0; int raduis = 0; QPoint p1 = QPoint(0, 0); QPoint p2 = QPoint(0, 0); int ret = 0; p1 = currentMousePoint; p2 = powerCenterPoint; delta = qSqrt(qPow(p1.x() - p2.x(), 2) + qPow(p1.y() - p2.y(), 2)); raduis = (powerImgRect->width()/2)*128/350; //整张图片350px*350px,图标为128*128 if(delta > raduis) { ret = 1; } else if(delta < raduis) { ret = -1; } else { ret = 0; } return ret; } //求线段与圆圈的交点,该线段一个端点为圆心,另外一个端点在圆外。 QPoint imageDragWidget::GetPoint(QPoint currentPoint, QPoint circleCenter, int raduis) { int cx = circleCenter.x(); //圆心横坐标 int cy = circleCenter.y(); //圆心纵坐标 int edx = currentPoint.x(); //圆外点的横坐标 int edy = currentPoint.y(); //圆外点的纵坐标 int r = raduis; //半径 int x = 0; //交点横坐标 int y = 0; //交点纵坐标 QPoint p = QPoint(0, 0); //交点 if((edx>cx)&&(edy==cy)) { //右轴; x = cx+r; y = cy; } else if((edx==cx) && (edy<cy)) { //上轴; x = cx; y = cy - r; } else if((edx<cx)&&(edy==cy)) { //左轴; x = cx - r; y = cy; } else if((edx==cx)&&(edy>cy)) { //下轴; x = cx; y = cy + r; } else { //不在坐标轴上 //求得直线方程 double k = ((double)(edy - cy) ) / (edx - cx); double b = edy - k*edx; //列方程 /* (1 + k^2)*x^2 - x*(2*cx -2*k*(b -cy) ) + cx*cx + ( b - cy)*(b - cy) - r*r = 0 */ double x1 = 0, y1 = 0, x2 = 0, y2 = 0; double c = cx*cx + (b - cy)*(b- cy) - r*r; double a = (1 + k*k); double b1 = (2*cx - 2*k*(b - cy)); //获得下面的简化方程 // a*x^2 - b1*x + c = 0; double tmp = sqrt(b1*b1 - 4*a*c); x1 = ( b1 + tmp )/(2*a); y1 = k*x1 + b; x2 = ( b1 - tmp)/(2*a); y2 = k*x2 + b; if((edx>cx)&&(edy>cy)) { //第四象限; x = x1; y = y1; } else if((edx>cx)&&(edy<cy)) { //第一象限; x = x1; y = y1; } else if((edx<cx)&&(edy<cy)) { //第二象限; x = x2; y = y2; } else if((edx<cx)&&(edy>cy)) { //第三象限; x = x2; y = y2; } } p.setX(x); p.setY(y); return p; } imageDragWidget::~imageDragWidget() { delete circle_defaultImg; delete circle_boldImg; delete power_haloImg; delete power_solidImg; delete power_defaultImg; delete circleImgRect; delete powerImgRect; } 二、在主函数中将该组件注册成服务,以便QML可以看到。 /*main.cpp*/ #include "imagedragwidget.h" …… int main(int argc, char *argv[]) { …… qmlRegisterType<imageDragWidget>("cn.cmos.service.imageDragWidget", 1, 0, "ImageDragWidget"); …… } 3 、在QML中使用该组件。 /*ShutdownWidget.qml*/ import QtQuick 2.0 import cn.cmos.service.imageDragWidget 1.0 //圆圈、图标和标签的控件 Rectangle { id: shutdownWidget anchors.fill: parent color: "black" //确认关机 signal pullOutside //鼠标按下 signal dragPress //鼠标在圆圈内移动 signal dragMoveIn //鼠标在圆圈上移动 signal dragMoveOn //鼠标释放 signal dragRelease onDragPress: { dragOutText.visible = false } onDragMoveIn: { dragOutText.visible = false powerOffText.visible = false } onDragMoveOn: { powerOffText.visible = true } onDragRelease: { dragOutText.visible = true powerOffText.visible = false } //“拖动关机”的标签 Text { id: dragOutText y: parent.width/6 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.pixelSize:35 color: "white" anchors.horizontalCenter: parent.horizontalCenter } //“确认关机”的标签 Text { id: powerOffText visible: false horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.pixelSize:35 color: "white" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } //圆圈和图标的控件 ImageDragWidget { id: imagedragitem anchors.fill: parent Component.onCompleted: { // 链接控件信号和组件的槽 this.dragOut.connect(shutdownWidget.pullOutside); this.dragPress.connect(shutdownWidget.dragPress); this.dragMoveIn.connect(shutdownWidget.dragMoveIn); this.dragMoveOn.connect(shutdownWidget.dragMoveOn); this.dragRelease.connect(shutdownWidget.dragRelease); console.log("open shutdown ImageDragWidget...") } } //该控件的信号 signal languageChanged(string name) //该控件的相应信号的响应函数 onLanguageChanged: { translator() } Component.onCompleted: { // 链接组件信号和控件的槽 cmostranslate.langChanged.connect(shutdownWidget.languageChanged); // 调用组件函数初始化语言名称 cmostranslate.trans(); } //翻译 function translator() { dragOutText.text = qsTr("Drag out to power off") powerOffText.text = qsTr("Power off") } } 附注:该需求亦可用QML和javasript来实现。 /*Circle.js*/ //判断点是否在圆内 function contain(x1, y1, x2, y2, raduis) { var delta = 0; var ret = 0; delta = Math.round(Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2))); if(delta > raduis) { ret = 1; } else if(delta < raduis) { ret = -1; } else { ret = 0; } return ret; } //求线段与圆圈的交点,该线段一个端点为圆心,另外一个端点在圆外。 function getPoint(currentPointX, currentPointY, circleCenterX, circleCenterY, raduis) { var cx = circleCenterX; //圆心横坐标 var cy = circleCenterY; //圆心纵坐标 var edx = currentPointX; //圆外点的横坐标 var edy = currentPointY; //圆外点的纵坐标 var r = raduis; //半径 var x = 0; //交点横坐标 var y = 0; //交点纵坐标 if((edx>cx)&&(edy===cy)) { //右轴; x = cx+r; y = cy; } else if((edx===cx) && (edy<cy)) { //上轴; x = cx; y = cy - r; } else if((edx<cx)&&(edy===cy)) { //左轴; x = cx - r; y = cy; } else if((edx===cx)&&(edy>cy)) { //下轴; x = cx; y = cy + r; } else { //不在坐标轴 //求得直线方程 var k = (edy - cy) / (edx - cx); var b = edy - k*edx; //列方程 /* (1 + k^2)*x^2 - x*(2*cx -2*k*(b -cy) ) + cx*cx + ( b - cy)*(b - cy) - r*r = 0 */ var x1 = 0, y1 = 0, x2 = 0, y2 = 0; var c = cx*cx + (b - cy)*(b- cy) - r*r; var a = (1 + k*k); var b1 = (2*cx - 2*k*(b - cy)); //获得下面的简化方程 // a*x^2 - b1*x + c = 0; var tmp = Math.sqrt(b1*b1 - 4*a*c); x1 = ( b1 + tmp )/(2*a); y1 = k*x1 + b; x2 = ( b1 - tmp)/(2*a); y2 = k*x2 + b; if((edx>cx)&&(edy>cy)) { //第四象限; x = x1; y = y1; } else if((edx>cx)&&(edy<cy)) { //第一象限; x = x1; y = y1; } else if((edx<cx)&&(edy<cy)) { //第二象限; x = x2; y = y2; } else if((edx<cx)&&(edy>cy)) { //第三象限; x = x2; y = y2; } } return [Math.round(x), Math.round(y)]; } /*ShutdownArea.qml*/ import QtQuick 2.2 import "Circle.js" as Circle Rectangle { id:shutdownArea color: "black" anchors.fill: parent //"确认关机"的信号 signal pullOutside //圆圈中心坐标 property real circle_centerX: width/2 property real circle_centerY: height/2 //图标中心坐标 property point powerImageCenter: Qt.point(0, 0); //鼠标是否按下的标志 property bool pressFlag: false //鼠标是否移出的标志 property bool isOut: false Timer { id: shutdownAreaTimer property int stateFlag: 0 interval: 1000; running: true; repeat: true; triggeredOnStart: true onTriggered: { console.log("shutdownDragTimer trigger") //定时器切换状态,随后关掉定时器 stateFlag = stateFlag + 1 if(stateFlag == 1) { shutdownArea.state = "begin" } else if(stateFlag == 2) { shutdownArea.state = "middle" } else if(stateFlag == 3) { shutdownArea.state = "end" } else { shutdownAreaTimer.stop() } } } //“拖动关机”和“确认关机”的标签 Text { id: dragText y: (parent.height-height)/align text: dragOutText property string dragOutText: "" property string powerOffText: "" property int align: 4 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.pixelSize:35 color: "white" anchors.horizontalCenter: parent.horizontalCenter } Image { id: circleImage width: (452/580)*parent.width //滑动范围圆圈图片尺寸452*452, 滑动图标的尺寸128*128 // 图标所在图片尺寸为:350*350 height: width anchors.centerIn: parent source: circle_default_image property string circle_default_image: "qrc:/images/circle_default.png" property string circle_bold_image: "qrc:/images/circle_bold.png" } Image { id: powerImage //滑动范围圆圈图片尺寸452*452, 滑动图标的尺寸128*128 // 图标所在图片尺寸为:350*350 x: (580-350)/(2*580)*parent.width y: (580-350)/(2*580)*parent.height width: (350/580)*parent.width height: width source: power_default_image property string power_default_image: "qrc:/images/power_default.png" property string power_halo_image: "qrc:/images/power_halo.png" property string power_solid_image: "qrc:/images/power_solid.png" function moveCenter(centerX, centerY) { powerImage.x = centerX - width/2 powerImage.y = centerY - height/2 } } state: "initializtion" states: [ State { name: "initializtion" PropertyChanges { target: dragText; visible: false } PropertyChanges { target: dragText; opacity: 0.0 } PropertyChanges { target: circleImage; visible: false } PropertyChanges { target: circleImage; scale: 0.0 } PropertyChanges { target: powerImage; visible: false } PropertyChanges { target: powerImage; scale: 0.0 } }, State { name: "begin" PropertyChanges { target: dragText; visible: false } PropertyChanges { target: dragText; opacity: 0.0 } PropertyChanges { target: circleImage; visible: true } PropertyChanges { target: circleImage; scale: 0.0 } PropertyChanges { target: powerImage; visible: true } PropertyChanges { target: powerImage; scale: 0.0 } }, State { name: "middle" PropertyChanges { target: dragText; visible: false } PropertyChanges { target: dragText; opacity: 0.0 } PropertyChanges { target: circleImage; visible: true } PropertyChanges { target: circleImage; scale: 128/452 } PropertyChanges { target: powerImage; visible: true } PropertyChanges { target: powerImage; scale: 1.0 } }, State { name: "end" PropertyChanges { target: dragText; visible: true } PropertyChanges { target: dragText; opacity: 1.0 } PropertyChanges { target: circleImage; visible: true } PropertyChanges { target: circleImage; scale: 1.0 } PropertyChanges { target: powerImage; visible: true } PropertyChanges { target: powerImage; scale: 1.0 } } ] transitions: [ Transition { from: "initializtion"; to: "begin" }, Transition { from: "begin"; to: "middle" PropertyAnimation { target: circleImage properties: "scale"; duration: 1000 } PropertyAnimation { target: powerImage properties: "scale"; duration: 1000 } }, Transition { from: "middle"; to: "end" PropertyAnimation { target: circleImage properties: "scale"; duration: 1000 } PropertyAnimation { target: dragText properties: "opacity"; duration: 1000; easing.type: Easing.InExpo } }, Transition { from: "end"; to: "initializtion" } ] MouseArea { id: dragArea anchors.fill: parent onPressed: { var power_result = Circle.contain(mouse.x, mouse.y, circle_centerX, circle_centerY, (128/350)*powerImage.width/2); if(power_result !== 1) { //图标上或内 pressFlag = true; powerImage.source = powerImage.power_halo_image; dragText.text = ""; } } onReleased: { powerImage.moveCenter(circle_centerX, circle_centerY); pressFlag = false; powerImage.source = powerImage.power_default_image; circleImage.source = circleImage.circle_default_image; dragText.text = dragText.dragOutText; dragText.align = 4; if(isOut) { shutdownArea.pullOutside() } } onPositionChanged: { if(pressFlag) { var circle_result = Circle.contain(mouse.x, mouse.y, circle_centerX, circle_centerY, circleImage.width/2); if(circle_result === 1) { isOut = true; //圆圈外 var power_result = Circle.contain(mouse.x, mouse.y, powerImageCenter.x, powerImageCenter.y, (128/350)*powerImage.width/2); if(power_result === 1) { //圈圈外且图标外 powerImage.moveCenter(circle_centerX, circle_centerY); pressFlag = false; powerImage.source = powerImage.power_default_image; circleImage.source = circleImage.circle_default_image; dragText.text = dragText.dragOutText; dragText.align = 4; // shutdownArea.pullOutside() } else { //圈圈外且非图标外 var crossover_point = Circle.getPoint(mouse.x, mouse.y, circle_centerX, circle_centerY, circleImage.width/2); console.log("交点: " + crossover_point) powerImage.moveCenter(crossover_point[0], crossover_point[1]); powerImageCenter = Qt.point(crossover_point[0], crossover_point[1]) powerImage.source = powerImage.power_solid_image; circleImage.source = circleImage.circle_bold_image; dragText.text = dragText.powerOffText; dragText.align = 2; } } else if (circle_result === 0) { //圆圈上 powerImageCenter = Qt.point(mouse.x, mouse.y) powerImage.source = powerImage.power_solid_image; circleImage.source = circleImage.circle_bold_image; dragText.text = dragText.powerOffText; dragText.align = 2; isOut = true; } else { //圆圈内 powerImageCenter = Qt.point(mouse.x, mouse.y) powerImage.moveCenter(mouse.x, mouse.y) powerImage.source = powerImage.power_halo_image; circleImage.source = circleImage.circle_default_image; dragText.text = ""; isOut = false; } } if(isOut&&(!pressFlag)) { //鼠标在圆圈外且在图标外,确认关机。 shutdownArea.pullOutside() } } } //该控件的信号 signal languageChanged(string name) //该控件的相应信号的响应函数 onLanguageChanged: { translator() } Component.onCompleted: { // 链接组件信号和控件的槽 cmostranslate.langChanged.connect(shutdownArea.languageChanged); // 调用组件函数初始化语言名称 cmostranslate.trans(); } //翻译 function translator() { dragText.dragOutText = qsTr("Drag Down") dragText.powerOffText = qsTr("Power off") } }