版權聲明:本文系做者原創。未經許可,不得轉載。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") } }