一、简述
今天是新的一年第一篇博客,有大半个月没有更新博客了。我想是时候,打开电脑、拿起键盘、开始在我的代码之路上披荆斩棘,斩杀恶龙。
今天就继续来分享QQ登录界面的那些事。QQ登录界面的标题栏有一个小三角的按钮,一般情况下,大家可能并不会点击这个按钮,因为正常情况下大家登录QQ都不需要进行网络设置,只有在网络有限制的情况下,我们需要设置一些代理来登录QQ。
当我们点击这个小三角按钮,我们会发现QQ的有一个旋转动画从登录界面跳转到网络设置界面。仔细看其实会发现登录界面和网络设置界面不是同一个窗口,而且两个界面的宽高各不相等,也就是差不多在旋转到90度时切换了窗口。
那么是不是可以用Qt实现类似的效果呢?万能的Qt告诉你,当然可以。下面就来看一看如何实现。
QQ登录界面点击小三角旋转到网络设置界面:

我的效果

二、代码之路
#ifndef ROTATEWIDGET_H#define ROTATEWIDGET_H#include <QStackedWidget>class LoginWindow;class LoginNetSetWindow;class RotateWidget : public QStackedWidget{ Q_OBJECTpublic: RotateWidget(QWidget *parent = NULL); ~RotateWidget();PRivate: // 初始化旋转的窗口; void initRotateWindow(); // 绘制旋转效果; void paintEvent(QPaintEvent* event);private slots: // 开始旋转窗口; void onRotateWindow(); // 窗口旋转结束; void onRotateFinished();private: // 当前窗口是否正在旋转; bool m_isRoratingWindow; // 登录界面; LoginWindow* m_loginWindow; // 网络设置界面; LoginNetSetWindow* m_loginNetSetWindow; int m_nextPageIndex;};#endif // ROTATEWIDGET_H
#include "rotatewidget.h"#include <QPropertyAnimation>#include "loginwindow.h"#include "loginnetsetwindow.h"RotateWidget::RotateWidget(QWidget *parent) : QStackedWidget(parent) , m_isRoratingWindow(false) , m_nextPageIndex(0){ this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint| Qt::WindowMinimizeButtonHint); this->setAttribute(Qt::WA_TranslucentBackground); // 给窗口设置rotateValue属性; this->setProperty("rotateValue", 0); initRotateWindow();}RotateWidget::~RotateWidget(){}// 初始化旋转的窗口;void RotateWidget::initRotateWindow(){ m_loginWindow = new LoginWindow(this); // 这里定义了两个信号,需要自己去发送信号; connect(m_loginWindow, SIGNAL(rotateWindow()), this, SLOT(onRotateWindow())); connect(m_loginWindow, SIGNAL(closeWindow()), this, SLOT(close())); m_loginNetSetWindow = new LoginNetSetWindow(this); connect(m_loginNetSetWindow, SIGNAL(rotateWindow()), this, SLOT(onRotateWindow())); connect(m_loginNetSetWindow, SIGNAL(closeWindow()), this, SLOT(close())); this->addWidget(m_loginWindow); this->addWidget(m_loginNetSetWindow); // 这里宽和高都增加,是因为在旋转过程中窗口宽和高都会变化; this->setFixedSize(QSize(m_loginWindow->width() + 20, m_loginWindow->height() + 100));}// 开始旋转窗口;void RotateWidget::onRotateWindow(){ // 如果窗口正在旋转,直接返回; if (m_isRoratingWindow) { return; } m_isRoratingWindow = true; m_nextPageIndex = (currentIndex() + 1) >= count() ? 0 : (currentIndex() + 1); QPropertyAnimation *rotateAnimation = new QPropertyAnimation(this, "rotateValue"); // 设置旋转持续时间; rotateAnimation->setDuration(600); // 设置旋转角度变化趋势; rotateAnimation->setEasingCurve(QEasingCurve::InCubic); // 设置旋转角度范围; rotateAnimation->setStartValue(0); rotateAnimation->setEndValue(180); connect(rotateAnimation, SIGNAL(valueChanged(QVariant)), this, SLOT(repaint())); connect(rotateAnimation, SIGNAL(finished()), this, SLOT(onRotateFinished())); // 隐藏当前窗口,通过不同角度的绘制来达到旋转的效果; currentWidget()->hide(); rotateAnimation->start();}// 旋转结束;void RotateWidget::onRotateFinished(){ m_isRoratingWindow = false; setCurrentWidget(widget(m_nextPageIndex)); repaint();}// 绘制旋转效果;void RotateWidget::paintEvent(QPaintEvent* event){ if (m_isRoratingWindow) { // 小于90度时; int rotateValue = this->property("rotateValue").toInt(); if (rotateValue <= 90) { QPixmap rotatePixmap(currentWidget()->size()); currentWidget()->render(&rotatePixmap); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QTransform transform; transform.translate(width() / 2, 0); transform.rotate(rotateValue, Qt::YAxis); painter.setTransform(transform); painter.drawPixmap(-1 * width() / 2, 0, rotatePixmap); } // 大于90度时 else { QPixmap rotatePixmap(widget(m_nextPageIndex)->size()); widget(m_nextPageIndex)->render(&rotatePixmap); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QTransform transform; transform.translate(width() / 2, 0); transform.rotate(rotateValue + 180, Qt::YAxis); painter.setTransform(transform); painter.drawPixmap(-1 * width() / 2, 0, rotatePixmap); } } else { return __super::paintEvent(event); }}
在LoginWindow和LoginNetSetWindow中实现鼠标拖拽窗口移动。
本窗口不需要移动,只需通知父窗口移动即可
void LoginNetSetWindow::mousePressEvent(QMouseEvent *event){ if (event->button() == Qt::LeftButton) { m_isPressed = true; m_startMovePos = event->globalPos(); } return QWidget::mousePressEvent(event);}void LoginNetSetWindow::mouseMoveEvent(QMouseEvent *event){ if (m_isPressed) { QPoint movePoint = event->globalPos() - m_startMovePos; QPoint widgetPos = this->parentWidget()->pos() + movePoint; m_startMovePos = event->globalPos(); this->parentWidget()->move(widgetPos.x(), widgetPos.y()); } return QWidget::mouseMoveEvent(event);}void LoginNetSetWindow::mouseReleaseEvent(QMouseEvent *event){ m_isPressed = false; return QWidget::mouseReleaseEvent(event);}// 在子窗口关闭时通知关闭父窗口;void LoginNetSetWindow::closeEvent(QCloseEvent *event){ emit closeWindow(); return __super::closeEvent(event);}注意:
在设置窗口宽高时添加了如下代码,至于为什么?请看下面图片效果。
// 这里宽和高都增加,是因为在旋转过程中窗口宽和高都会变化;this->setFixedSize(QSize(m_loginWindow->width() + 20, m_loginWindow->height() + 100));
我们知道使用QQ截图,在鼠标移动到某个窗口上时,QQ会自动将当前截图区域设置为这个窗口的大小,我们看到用QQ进行截图时,将鼠标放在QQ登录界面上选取的区域要大于实际的界面,所以QQ的登录界面并不是一个单独的窗口,而是后面那个透明窗口的子窗口,其实也就类似于我们将登录界面放在一个Widget 上,然后将这个Widget背景置为透明,至于为什么要加大宽高,是因为在旋转的过程中窗口的宽高发生了变化,然后不加大宽高,窗口将会被遮挡。
加长宽高用QQ截图显示效果:

不加长宽高的下方被遮挡:

我们发现窗口下方会被遮挡,这就是为什么要加长宽高了。至于为什么QQ窗口四周都会有多余空间,而我这里只有右方和下方是因为旋转效果不一样所以设置的不一样罢了。
尾
通过QPropertyAnimation类来改变旋转角度值,在paintEvent不断绘制不同角度下的窗口来达到旋转的效果。在旋转到90度时切换窗口,将登陆窗口切换至网络设置窗口。但是在旋转绘制过程中会发现有界面的线条会弯曲,在视觉上会有一点小遗憾,这个问题暂时没有解决,后期如果能够解决将会更新代码。