一、簡述
今天是新的一年第一篇博客,有大半個月沒有更新博客了。我想是時候,打開電腦、拿起鍵盤、開始在我的代碼之路上披荊斬棘,斬殺惡龍。
今天就繼續來分享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度時切換窗口,將登陸窗口切換至網絡設置窗口。但是在旋轉繪制過程中會發現有界面的線條會彎曲,在視覺上會有一點小遺憾,這個問題暫時沒有解決,后期如果能夠解決將會更新代碼。