最近准备把前这个月做的Qt小demo都发出来供大家参考和指正,今天发的是上个月想突然想做的一个美观点的游戏登陆界面(其实还有一个原因是刷b站看到了一个大神用html+css+js写的登陆页面,太酷了,于是自己也想用Qt写一个)。
这次的界面主要是用到了Qt的动画类QPropertyAnimation,包含了控件移动,控件缩放两个主要动画特效。对于输入框的设计思路就是先自定义一个继承于QWidget类的控件MyInputTag和一个继承于QLineEdit类的控件MyLineEdit,然后为MyInputTag设置两个子对象分别是一个QLabel和一个MyLineEdit类的实例,这时注意不要给这个父对象加布局,不然一会播放动画时,这两个控件的位置和大小都会被布局管理器控制,而不受QPropertyAnimation控制。
为MyInputTag添加两个槽函数,一个用来处理播放子对象MyLineEdit获得焦点时播放的动画,一个用来处理其失去焦点时播放的动画,然后再为MyLineEdit添加两个信号,并重写获得焦点事件和失去焦点事件,当控件获得或失去焦点时分别发送对应的信号,然后交给MyInputTag处理。这其中要注意重写QLineEdit的焦点事件时,如果不把QFocusEvent*参数传递给父类处理,会导致当控件获得焦点时没有光标和光标不闪烁等令人头疼的问题!这边附上源码让不会将事件转发给父类处理的人看一下怎么写
//获得焦点事件
void MyLineEdit::focusInEvent(QFocusEvent *e)
{
emit focusIn();
QLineEdit::focusInEvent(e);
}
当MyInputTag收到focusIn信号时,需要为两个子对象设置动画,对于label只需要设置pos类型的动画,而对于MyLineEdit需要设置geometry。label只需要将其y坐标移动到它自身宽度的相反数即可,因为label的父对象是MyInputTag,所以正好可以跑出父对象的范围,导致逐渐消失,而MyLineEdit只需要将他的起始位置设置为当前位置,结束位置设置为整个MyInputTag的大小即可,当然这里面还有许多对于细节的把控,就不细说了,直接看源码或许要清晰一点。
最后就是关于MyLineEdit的下划线实现思路,先给MyLineEdit添加一个成员属性value,然后重写鼠标进入事件,当鼠标进入,检查状态,然后启动一个定时器QTimer,定时器每次对value++,然后调用update函数更新控件paintEvent,然后在paintEvent函数里面根据value的值在控件的最下方绘制一条一定长度的直线(这里要稍微算一下value和直线长度的关系,不是很难,用一次函数自己带入两个特殊点就可以解出来关系),这里要注意几点,首先是有可能用户会反复让鼠标快速进出控件,导致上一次的timer还没有结束,下一次timer又要启动,所以在每次进出new出来timer前要检查timer是否存在或者正在运行中,如果在运行中,就要把他先停掉,再启动新的;其次,绘制直线时要注意如果你设置了lineedit圆角,你绘制的直线可能会超出圆角部分,所以要根据自己的圆角大小重新计算value和绘制直线的长度关系(准确来讲应该是位置关系);还有最后一个也是最令我痛苦的一个bug,就是当你用鼠标在输入框里面按下不松开然后把鼠标移动到整个应用程序窗口外再松开时,会连续触发两次鼠标离开事件,当时找这一个问题用了整整一天的时间才找到。
另外,其实由于整个demo大量用到了指针,当时在改bug时出现问题最多的地方就是内存泄漏和野指针问题导致程序崩溃异常,而且对于变量命名也不规范,代码仍有重复冗余的地方,还望各路大神指正,也欢迎各位热爱Qt的小伙伴私信与我交流哦!
最后附上源码:
loginpage.h文件:
#ifndef LOGINPAGE_H
#define LOGINPAGE_H
#include
#include
#include
#include
#include
namespace Ui {
class LoginPage;
}
class LoginPage : public QWidget
{
Q_OBJECT
public:
explicit LoginPage(QWidget *parent = nullptr);
~LoginPage();
//绘图事件
void paintEvent(QPaintEvent *);
//关闭事件
void closeEvent(QCloseEvent *);
//显示事件
void showEvent();
//检查本地网络是否通畅
inline bool checkNetworkConnection();
//登陆失败,携带失败信息
void LoginFailed(const QString failure_information);
//登陆成功
void LoginSucceed();
public slots:
//检查输入合法性和本地网络是否畅通
void enterLegitimacy();
signals:
//本地信息全部检查完毕信号,附带用户输入的三条数据
void LocalInformationChecked(QVector v);
private slots:
private:
int times = 0;
Ui::LoginPage *ui;
};
#endif // LOGINPAGE_H
loginpage.cpp文件:
#include "loginpage.h"
#include "ui_loginpage.h"
#include
LoginPage::LoginPage(QWidget *parent) :
QWidget(parent),
ui(new Ui::LoginPage)
{
ui->setupUi(this);
setWindowOpacity(0);
//连接登录按钮和检查输入合法性槽函数
connect(ui->toolButton,&QToolButton::clicked,this,&LoginPage::enterLegitimacy);
//设置登陆frame的阴影
QGraphicsDropShadowEffect *qss = new QGraphicsDropShadowEffect(this);
qss->setOffset(10,10);
qss->setBlurRadius(20);
ui->frame->setGraphicsEffect(qss);
//设置登陆frame的边框
ui->frame->setStyleSheet("QFrame#frame{background-color: rgba(46,47,48,200);border-radius: 20px;}");
//设置登陆界面样式
ui->label->setStyleSheet("color: #ff8099");
ui->widget->setLabelText("昵称:");
ui->widget_2->setLabelText("域名:");
ui->widget_3->setLabelText("端口:");
ui->widget->setInputBackText("请输入玩家昵称");
ui->widget_2->setInputBackText("请输入服务器IP");
ui->widget_3->setInputBackText("请输入服务器端口");
}
LoginPage::~LoginPage()
{
delete ui;
}
//绘图事件
void LoginPage::paintEvent(QPaintEvent *)
{
QPixmap pix(":/image/login_background.jpg");
QPainter painter(this);
painter.drawPixmap(0,0,width(),height(),pix,(pix.width()-width())/2,(pix.height()-height())/2,(pix.width()-width())/2+width(),(pix.height()-height())/2+height());
}
//关闭事件
void LoginPage::closeEvent(QCloseEvent *)
{
for(double val = 1.0;val > 0.0;val -= 0.07)
{
setWindowOpacity(val);
Sleep(2);
}
}
void LoginPage::showEvent()
{
for(double val = 0.07;val < 1.0;val += 0.07)
{
setWindowOpacity(val);
Sleep(3);
}
}
//检查本地网络是否通畅
inline bool LoginPage::checkNetworkConnection()
{
return QNetworkConfigurationManager().isOnline();
}
//登陆失败,携带失败信息
void LoginPage::LoginFailed(const QString failure_information)
{
//提示一个messagebox
QMessageBox::critical(this,"登陆失败","失败信息:" + failure_information);
}
//登陆成功
void LoginPage::LoginSucceed()
{
}
//检查输入合法性和本地网络是否畅通
void LoginPage::enterLegitimacy()
{
//如果三个输入框中任何一个是空,就提示一个messagebox并结束
if(ui->widget->getLineEditInput() == "" || ui->widget_2->getLineEditInput() == "" || ui->widget_3->getLineEditInput() == "")
{
QMessageBox::critical(this,"非法输入","输入框不可为空!");
return ;
}
//检查本地网络是否通畅,就提示一个messagebox并结束
if(!(this->checkNetworkConnection()))
{
QMessageBox::critical(this,"网络异常","本地网络连接断开,请检查本地网络");
return ;
}
//准备信息数组v
QVector v;
//昵称
v.push_back(ui->widget->getLineEditInput());
//域名
v.push_back(ui->widget_2->getLineEditInput());
//端口
v.push_back(ui->widget_3->getLineEditInput());
//全部检查完毕,将数据发送给控制器
emit LocalInformationChecked(v);
}
myinputtag.h文件:
#ifndef MYINPUTTAG_H
#define MYINPUTTAG_H
#include
#include
#include
#include"mylineedit.h"
class MyInputTag : public QWidget
{
Q_OBJECT
public:
explicit MyInputTag(QWidget *parent = nullptr);
//设置标签文本
void setLabelText(const QString &text);
//设置输入框背景信息
void setInputBackText(const QString &text);
//大小变化事件
void resizeEvent(QResizeEvent *) override;
//获取lineedit文本内容
QString getLineEditInput();
public slots:
//获得焦点播放动画
void getFocus();
//失去焦点播放动画
void loseFocus();
signals:
private:
//文本标签
QLabel *label;
//输入标签
MyLineEdit *lineEdit;
//是否获得焦点
bool isFocusIn;
};
#endif // MYINPUTTAG_H
myinputtag.cpp文件:
#include "myinputtag.h"
MyInputTag::MyInputTag(QWidget *parent) : QWidget(parent)
{
//初始化三个成员属性
isFocusIn = false;
lineEdit = new MyLineEdit(this);
label = new QLabel(this);
//处理lineedit获得焦点事件
connect(lineEdit,&MyLineEdit::focusIn,this,&MyInputTag::getFocus);
//处理lineedit失去焦点事件
connect(lineEdit,&MyLineEdit::focusOut,this,&MyInputTag::loseFocus);
//设置lineedit的字体
lineEdit->setFont(QFont("",12,1));
//设置label的字体和最小高度
QFont f("Microsoft YaHei UI",12,1);
f.setBold(true);
label->setStyleSheet("color: #ff8099");
label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
label->setFont(f);
label->setMinimumHeight(45);
}
//设置标签文本
void MyInputTag::setLabelText(const QString &text)
{
this->label->setText(text);
}
//设置输入框背景信息
void MyInputTag::setInputBackText(const QString &text)
{
this->lineEdit->setPlaceholderText(text);
}
//大小变化事件
void MyInputTag::resizeEvent(QResizeEvent *)
{
QFontMetrics fontMetrics(label->font());
label->resize(fontMetrics.width(label->text()) + 10,height()*0.4);
if(isFocusIn)//如果是焦点控件
{
//计算lineedit大小
lineEdit->resize(width()*0.9,height()*0.6);
//移动位置
label->move( -label->width() , (height()-label->height()) / 2 );
lineEdit->move( width()*0.05,height()*0.2 );
}
else//如果不是
{
//计算lineedit大小
lineEdit->resize(width()*0.8 - label->width(),height()*0.4);
//移动位置
label->move( width()*0.1 , (height()-label->height()) / 2 );
lineEdit->move( label->x() + label->width() , label->y() );
}
}
//获取lineedit文本内容
QString MyInputTag::getLineEditInput()
{
return lineEdit->text();
}
//获得焦点播放动画
void MyInputTag::getFocus()
{
isFocusIn = true;
QPropertyAnimation *animation_1 = new QPropertyAnimation(this);
QPropertyAnimation *animation_2 = new QPropertyAnimation(this);
animation_1->setPropertyName("pos");
animation_2->setPropertyName("geometry");
animation_1->setTargetObject(label);
animation_2->setTargetObject(lineEdit);
animation_1->setDuration(700);
animation_2->setDuration(700);
//设置label的动画起始结束位置
animation_1->setStartValue(label->pos());
animation_1->setEndValue(QPoint(-label->width(),label->y()));
//设置lineedit的动画起始结束位置
animation_2->setStartValue(QRect(lineEdit->pos(),lineEdit->size()));
animation_2->setEndValue(QRect(QPoint(width()*0.05,height()*0.2),QSize(width()*0.9,height()*0.6)));
//启动动画
animation_1->start();
animation_2->start();
}
//失去焦点播放动画
void MyInputTag::loseFocus()
{
isFocusIn = false;
QPropertyAnimation *animation_1 = new QPropertyAnimation(this);
QPropertyAnimation *animation_2 = new QPropertyAnimation(this);
animation_1->setPropertyName("pos");
animation_2->setPropertyName("geometry");
animation_1->setTargetObject(label);
animation_2->setTargetObject(lineEdit);
animation_1->setDuration(700);
animation_2->setDuration(700);
//设置label的动画起始结束位置
animation_1->setStartValue(label->pos());
animation_1->setEndValue(QPoint(width()*0.1,(height()-label->height())/2));
//设置lineedit的动画起始结束位置
animation_2->setStartValue(QRect(lineEdit->pos(),lineEdit->size()));
animation_2->setEndValue(QRect(QPoint(width()*0.1+label->width(),label->y()),QSize(width()*0.8 - label->width(),height()*0.4)));
//启动动画
animation_1->start();
animation_2->start();
}
mylineedit.h文件:
#ifndef MYLINEEDIT_H
#define MYLINEEDIT_H
#include
#include
#include
#include
class MyLineEdit : public QLineEdit
{
Q_OBJECT
public:
explicit MyLineEdit(QWidget *parent = nullptr);
//绘图事件
void paintEvent(QPaintEvent *e);
//获得焦点事件
void focusInEvent(QFocusEvent *e) override;
//失去焦点事件
void focusOutEvent(QFocusEvent *e) override;
//鼠标进入事件
void enterEvent(QEvent *);
//鼠标离开事件
void leaveEvent(QEvent *);
signals:
//获得焦点信号
void focusIn();
//失去焦点信号
void focusOut();
private:
//时间对象
QTimer *timer_1 = nullptr,*timer_2 = nullptr;
//下边框长度
int b_length;
};
#endif // MYLINEEDIT_H
mylineedit.cpp文件:
#include "mylineedit.h"
MyLineEdit::MyLineEdit(QWidget *parent) : QLineEdit(parent)
{
this->b_length = 0;
setMinimumHeight(45);
setStyleSheet("MyLineEdit{border: none;border-radius: 15px;}");
}
//绘图事件
void MyLineEdit::paintEvent(QPaintEvent *e)
{
//将其他绘制事件交给父类处理
QLineEdit::paintEvent(e);
//如果下边框有值就画
if(b_length != 0)
{
//绘制自定义内容
QPainter painter(this);
QPen pen(QColor(65,200,255));
pen.setWidth(2);
painter.setPen(pen);
painter.drawLine(QPoint((b_length*(30-width())+100*width())/200,height()-1),QPoint(((width()-30)*b_length+100*width())/200,height()-1));
}
}
//获得焦点事件
void MyLineEdit::focusInEvent(QFocusEvent *e)
{
emit focusIn();
QLineEdit::focusInEvent(e);
}
//失去焦点事件
void MyLineEdit::focusOutEvent(QFocusEvent *e)
{
emit focusOut();
QLineEdit::focusOutEvent(e);
}
//鼠标进入事件
void MyLineEdit::enterEvent(QEvent *)
{
if(b_length != 0 && timer_2 != nullptr)
{
timer_2->stop();
delete timer_2;
timer_2 = nullptr;
}
timer_1 = new QTimer(this);
connect(timer_1,&QTimer::timeout,this,[=](){
b_length += 2;
update();
if(b_length >= 100)
{
timer_1->stop();
delete timer_1;
timer_1 = nullptr;
b_length = 100;
}
});
timer_1->start(1);
}
//鼠标离开事件
void MyLineEdit::leaveEvent(QEvent *)
{
if(b_length != 100 && timer_1 != nullptr)
{
timer_1->stop();
delete timer_1;
timer_1 = nullptr;
}
if(timer_2 != nullptr) return;
timer_2 = new QTimer(this);
connect(timer_2,&QTimer::timeout,this,[=](){
b_length -= 2;
update();
if(b_length <= 0)
{
timer_2->stop();
delete timer_2;
timer_2 = nullptr;
b_length = 0;
}
});
timer_2->start(1);
}
mytoolbutton.h文件:
#ifndef MYTOOLBUTTON_H
#define MYTOOLBUTTON_H
#include
class MyToolButton : public QToolButton
{
Q_OBJECT
public:
explicit MyToolButton(QWidget *parent = nullptr);
//鼠标进入事件
void enterEvent(QEvent *);
//鼠标离开事件
void leaveEvent(QEvent *);
signals:
};
#endif // MYTOOLBUTTON_H
mytoolbutton.cpp文件:
#include "mytoolbutton.h"
MyToolButton::MyToolButton(QWidget *parent) : QToolButton(parent)
{
setStyleSheet("MyToolButton{background-color: #008997;color: white;border-radius: 15px;}");
}
void MyToolButton::enterEvent(QEvent *)
{
setStyleSheet("MyToolButton{background-color: #0368a5;color: white;border-radius: 15px;}");
}
void MyToolButton::leaveEvent(QEvent *)
{
setStyleSheet("MyToolButton{background-color: #008997;color: white;border-radius: 15px;}");
}
最后附上使用方法:
LoginPage *login_page = new LoginPage;
login_page->showEvent();
login_page->show();
只需要在主函数中包含LoginPage的头文件,然后实例化对象,然后一定要先调用一次它的showEvent()成员函数(这个主要是为了让登陆界面显示是可以从透明状态逐渐变为不透明状态显示,这样更好看),然后再调用show()方法即可。
最后附上其他博主对QPropertyAnimation的介绍Qt动画的简单使用(QPropertyAnimation,含源码和注释)_lw向北.的博客-CSDN博客