版权声明:本文系作者原创。未经许可,不得转载。
用Qt实现一个UI:一个圆形图标在圆圈内或圆圈上拖动,但不能拖出到圆圈外。当拖动到圆圈上时,高亮图标和圆圈。类似有RingLock。
1、继承QQuickPaintedItem类,该类为QQuickItem的子类。QQuickItem用于不用显示UI的供QML使用的组件;QQuickPaintedItem用于需要显示UI的供QML使用的组件。本案例中,需要画图,故而继承QQuickPaintedItem。
/*imagedragwidget.h*/
#ifndef IMAGEDRAGWIDGET_H
#define IMAGEDRAGWIDGET_H
#include
#include
#include
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
imageDragWidget::imageDragWidget(QQuickPaintedItem *parent) :
QQuickPaintedItem(parent)
{
//得到屏幕尺寸
QScreen *screen = QGuiApplication::primaryScreen();
int screen_width = screen->size().width();
int screen_height = screen->size().height();
qDebug()<<"屏幕尺寸: "<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()<<"滑动圆圈尺寸: "<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) && (edycy)) {
//下轴;
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)&&(edycy)) {
//第三象限;
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;
}
2、在主函数中将该组件注册成服务,以便QML能够看到。
/*main.cpp*/
#include "imagedragwidget.h"
……
int main(int argc, char *argv[]) {
……
qmlRegisterType("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) && (edycy)) {
//下轴;
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)&&(edycy)) {
//第三象限;
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")
}
}