最终成果

QML 仿qq登录界面(2) -- gif背景, 主界面布局和标题按钮实现_登录界面

我们要模仿的对象:

QML 仿qq登录界面(2) -- gif背景, 主界面布局和标题按钮实现_qml_02

资源文件

代码中会用到一些图片,我已将示例代码上传到码,文章最后会提供链接。

代码中用到的资源文件:

QML 仿qq登录界面(2) -- gif背景, 主界面布局和标题按钮实现_登录界面_03

  • background.gif中从某个网站上得到的(具体哪个不记得了,要收费的)
  • 所有的​​.svg​​​图片都是从​​https://www.iconfont.cn/​​这里获得(免费的)

如何找到需要的资料

Qt的资料很多,想要看完是不可能的,遇到问题,先百度,看看前辈们是怎么解决的,解决方案中可能会有一些示例代码,copy过来运行一把,能用当然后好,如果不能用再到官网上查找相应类的文档。

可以到下面的网站中搜索类,或者如果有经验的话,在这里搜索关键字也是可以的。

QML所有类型:​​https://doc.qt.io/qt-6/qmltypes.html​

QT官网的查找功能

QML 仿qq登录界面(2) -- gif背景, 主界面布局和标题按钮实现_登录界面_04

不知道是什么原因,这功能时好时不好(可能是因网络太差了吧,毕竟天朝访问国外的网站是诸多限制)

显示GIF背景

先上代码:

// 背景
Rectangle{
id: rect_background
radius: window.radius
color: "#FFFFFF"
anchors.fill: parent
focus: true
Keys.onEscapePressed: Qt.quit()
MouseArea {
property point clickPos: "0,0"
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onPressed:(mouse)=>{
clickPos = Qt.point(mouse.x,mouse.y)
mouse.accepted = true;
}
onPositionChanged:(mouse)=>{
var delta = Qt.point(mouse.x-clickPos.x, mouse.y-clickPos.y)
window.setX(window.x + delta.x)
window.setY(window.y + delta.y)
mouse.accepted = true;
}
}

AnimatedImage {
id: image
smooth: true
visible: false
anchors.fill: parent
source: "qrc:/imgs/background.gif"
antialiasing: true
}

OpacityMask {
id: imageOpcityMask
anchors.fill: image
source: image
maskSource: parent
visible: true
antialiasing: true
}
}

鼠标和按键事件

  • 第4行 ​​​radius: window.radius​​​ , ​​window.radius​​​ 是新定义的属性值为​​2​
  • 第7行开始是处理按键和鼠标事件,按下​​Esc​​​退出,可以用鼠标拖动窗口。
focus: true
Keys.onEscapePressed: Qt.quit()
MouseArea {
property point clickPos: "0,0"
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onPressed:(mouse)=>{
clickPos = Qt.point(mouse.x,mouse.y)
mouse.accepted = true;
}
onPositionChanged:(mouse)=>{
var delta = Qt.point(mouse.x-clickPos.x, mouse.y-clickPos.y)
window.setX(window.x + delta.x)
window.setY(window.y + delta.y)
mouse.accepted = true;
}
}

显示背景动图

  • 第25行开始,显示背景动图
AnimatedImage  {
id: image
smooth: true
visible: false
anchors.fill: parent
source: "qrc:/imgs/background.gif"
antialiasing: true
}

不能使用​​Image​​,它只能显示gif的第一帧图片。

组件的详细信息,请参考:​​https://doc.qt.io/qt-6/qml-qtquick-animatedimage.html​

  • 显示圆角经过上面的设置后,运行发现圆角不见了,百度上找了一圈,大多是用​​OpacityMask ​​​解决,在导入​​GraphicalEffects​​​ ​发现报错,找不到模块,最后在官网上找到了解决方案:​​https://doc.qt.io/qt-6/qml-qt5compat-graphicaleffects-opacitymask.html​​​
    即​​​import Qt5Compat.GraphicalEffects​
OpacityMask {
anchors.fill: image
source: image
maskSource: parent
visible: true
antialiasing: true // 抗锯齿
}

  官网上用三个图来描述,很直观:​​https://doc.qt.io/qt-6/qml-qt5compat-graphicaleffects-opacitymask.html#details​

上下布局

  • 上半部,把要把背景漏出来,把背景色设置成透明,高度为:130
  • 下半部,背景为纯白色,填充剩余的部分

第一版代码

 // 上半部分
Rectangle {
id: window_top
height: 130
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
color: "#00000000"
}
// 下半部分
property color bottom_color: "#eeeeee"
Rectangle {
anchors.top: window_top.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: window.radius
anchors.topMargin:0
color: window.bottom_color

}

第16行,为了让底部的圆角不被覆盖,四周有一些留白(背景图片会漏出来)。

QML 仿qq登录界面(2) -- gif背景, 主界面布局和标题按钮实现_登录界面_05

为解决这个问题,我们只好再它的背后再填充一层背景

// 下半部分
property color bottom_color: "#eeeeee"
// 第一层背景
Rectangle {
anchors.top: window_top.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
radius: window.radius
color: window.bottom_color
}

// 内容...
Rectangle {
anchors.top: window_top.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: window.radius
anchors.topMargin:0
color: window.bottom_color

}

或许你已经想到了,对!又产生了另一个问题

QML 仿qq登录界面(2) -- gif背景, 主界面布局和标题按钮实现_登录界面_06

所以还得加一层背景

// 下半部分
property color bottom_color: "#eeeeee"
// 第一层背景
Rectangle {
anchors.top: window_top.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
radius: window.radius
color: window.bottom_color
}
// 第二层背景
Rectangle {
anchors.top: window_top.bottom
anchors.left: parent.left
anchors.right: parent.right
height: window.radius
color: window.bottom_color
}
// 内容...
Rectangle {
anchors.top: window_top.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: window.radius
anchors.topMargin:0
color: window.bottom_color

}

问题得以解决

QML 仿qq登录界面(2) -- gif背景, 主界面布局和标题按钮实现_登录界面_07

过程看似很繁琐,因为我也是刚接触,如果您有什么更好的办法,请在评论区留言,让我们同共致富。

最终的代码

// 上半部分
Rectangle {
id: window_top
height: 130
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
color: "#00000000"
}
// 下半部分
property color bottom_color: "#eeeeee"
Rectangle {
anchors.top: window_top.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
radius: window.radius
color: window.bottom_color
}
Rectangle {
anchors.top: window_top.bottom
anchors.left: parent.left
anchors.right: parent.right
height: window.radius
color: window.bottom_color
}

Rectangle {
anchors.top: window_top.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: window.radius
anchors.topMargin:0
color: window.bottom_color

}

代码的组织结构

我应该把上下部分,分别封装到不同的组件中,这更有利于后期的维护...但这只是一个练手的代码,所以不考虑这些(其实是我现在还不知道如何封装给件)。或许后面会封装吧...

标题栏和按钮

标题栏和按钮都属于布局的上半部分,所以它的整体布局大概是

// 上半部分
Rectangle {
id: window_top
height: 130
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
color: "#00000000"
// 标题栏
// 左侧
Item {
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: -2
// 图标

// QQ文字,也可以用图标,但是我没有找到合适的
}
// 右侧
Flow {
id: tool_bar
anchors.top: parent.top
anchors.right: parent.right
layoutDirection: Qt.RightToLeft
// 关闭按钮
// 最小化
// 设置
}
}
  • 标题栏左侧我仍然使用,锚定定位,因为两个控件大小不一样,使用锚定定位比较方便
  • 右侧,我使用了流定位器,方向:​​layoutDirection: Qt.RightToLeft​
  • 关于定位,请参考这个文档:​​https://doc.qt.io/qt-6/qtquick-positioning-topic.html​

标题栏--左侧

 // 左侧
Item {
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: -2
// 图标
Image {
id: top_left_icon
width: 40
height: 48
source: "qrc:/imgs/qq_white.svg"
}
// QQ文字,也可以用图标,但是我没有找到合适的
Text {
anchors.left: top_left_icon.right
anchors.leftMargin: -4
anchors.verticalCenter: top_left_icon.verticalCenter
text: qsTr("QQ")
font.pixelSize: 22
font.bold: false
color: "white"
}
}
 onClicked: (event) => {
console.log(left_item.width, left_item.height)
}

输出:qml: 0 0

标题栏--右侧

// 右侧
Flow {
id: tool_bar
anchors.top: parent.top
anchors.right: parent.right
layoutDirection: Qt.RightToLeft
// 关闭按钮

前面有讲过,使用了流定位器,方向:从右向左,第一个是关闭按钮,它被放在了最右侧,接下来是最小化按钮,接着是...

工具按钮的布局 -- 关闭

  • 有图标所以必须有一个image控件
  • 鼠标经过时,显示一个半透明的矩形,所以要有矩形和处理鼠标悬停的事件
  • 点击时触发相应的动作,要处理鼠标点击事件​​MouseArea​
Item {
width: 32
height: 32
Image {
width: tool_bar.icon_size
height: tool_bar.icon_size
anchors.centerIn: parent
source: "qrc:/imgs/close.svg"
}
Rectangle {
id: rect_hover
color:"red"
opacity: 0
anchors.fill: parent
}
MouseArea {
onClicked: (event) => {
Qt.quit()
event.accepted = true;
}
anchors.fill: parent
}
}

鼠标悬停怎么处理?

我首先相到的是用​​MouseArea.entered​​​信号(鼠标进入区域时,将rect_hover,的 opacity设置为0.8或者其它值),​​MouseArea.exited​​信号(离开时rect_hover.opacity设置为0)

我在搜索​​hover​​时发现了一个更合适的类

QML 仿qq登录界面(2) -- gif背景, 主界面布局和标题按钮实现_登录界面_08

相关文档:​​https://doc.qt.io/qt-6/qml-qtquick-hoverhandler.html#details​

属性​​hovered​​只读,当鼠标进入区域时,变为true.

所以把处理鼠标悬停事件的代码加上之后为

Item {
width: 32
height: 32
Image {
width: tool_bar.icon_size
height: tool_bar.icon_size
anchors.centerIn: parent
source: "qrc:/imgs/close.svg"
}
Rectangle {
id: rect_hover
color:"red"
opacity: mouse.hovered? 0.2: 0
anchors.fill: parent
}
// 处理悬停事件
HoverHandler {
id: mouse
}
MouseArea {
onClicked: (event) => {
Qt.quit()
event.accepted = true;
}
anchors.fill: parent
}
}

属性绑定

你也许奇怪,第13行为什么可以这样写?

我在这里找到了答案:​​https://doc.qt.io/qt-6/qtqml-syntax-propertybinding.html​

有必要说明一下我是怎么找到这个文档的

QML 仿qq登录界面(2) -- gif背景, 主界面布局和标题按钮实现_登录界面_09

  • 这个也是无意中发现的,虽然之前有看过但是没没有点击来看...

QML 仿qq登录界面(2) -- gif背景, 主界面布局和标题按钮实现_qml_10

  • 这个网页是介绍​​QML​​语言的

QML 仿qq登录界面(2) -- gif背景, 主界面布局和标题按钮实现_qml_11

工具按钮的布局--其他

说是其它,其是就是​​最小化和 设置​​按钮,和关闭按钮一个模式。

需要注说的是控件id的取名,不要重复了(ide会报错,所以这是废话),HoverHandler的对应关系不要搞错了。

如果封装到组件中,就不会有这样的问题了,最后所有工具按钮的布局如下

Flow {
id: tool_bar
anchors.top: parent.top
anchors.right: parent.right
layoutDirection: Qt.RightToLeft
property int icon_size: 22
Item {
id: btn_close
width: 32
height: 32
Image {
width: tool_bar.icon_size
height: tool_bar.icon_size
anchors.centerIn: parent
source: "qrc:/imgs/close.svg"
}
Rectangle {
color:"red"
opacity: mouse.hovered? 0.6: 0
anchors.fill: parent
}
HoverHandler {
id: mouse
}
MouseArea {
onClicked: (event) => { Qt.quit(); event.accepted = true;}
anchors.fill: parent
}
}
Item {
id: btn_close1
width: 32
height: 32
Image {
width: tool_bar.icon_size
height: tool_bar.icon_size
anchors.centerIn: parent
source: "qrc:/imgs/min.svg"
}
Rectangle {
color:"white"
opacity: mouse1.hovered? 0.2: 0
anchors.fill: parent
}
HoverHandler {
id: mouse1
}
MouseArea {
onClicked: (event) => {window.visibility="Minimized" ; event.accepted = true;}
anchors.fill: parent
}
}
Item {
width: 32
height: 32
Image {
width: tool_bar.icon_size
height: tool_bar.icon_size
anchors.centerIn: parent
source: "qrc:/imgs/setting.svg"
}
Rectangle {
color:"white"
opacity: mouse3.hovered? 0.2: 0
anchors.fill: parent
}
HoverHandler {
id: mouse3
}
MouseArea {
onClicked: (event) => {console.log(left_item.width, left_item.height) ; event.accepted = true;}
anchors.fill: parent
}
}

}

全部完整的代码

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
Window {
id: window
width: 430
height: 330
visible: true
title: qsTr("Positioner")
flags: Qt.FramelessWindowHint | Qt.Window
color: "#00000000"
property int radius: 4
// 背景
Rectangle{
id: rect_background
radius: window.radius
color: "#FFFFFF"
anchors.fill: parent
focus: true
Keys.onEscapePressed: Qt.quit()
MouseArea {
property point clickPos: "0,0"
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onPressed:(mouse)=>{
clickPos = Qt.point(mouse.x,mouse.y)
mouse.accepted = true;
}
onPositionChanged:(mouse)=>{
var delta = Qt.point(mouse.x-clickPos.x, mouse.y-clickPos.y)
window.setX(window.x + delta.x)
window.setY(window.y + delta.y)
mouse.accepted = true;
}
}

AnimatedImage {
id: image
smooth: true
visible: false
anchors.fill: parent
source: "qrc:/imgs/background.gif"
antialiasing: true
}

OpacityMask {
anchors.fill: image
source: image
maskSource: parent
visible: true
antialiasing: true
}
}

// 分上下两部分
// 上半部分
Rectangle {
id: window_top
height: 130
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
color: "#00000000"
// 两列: 左侧是图标, 右侧是工具按钮
Item {
id: left_item
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: -2
Image {
id: top_left_icon
width: 40
height: 48
source: "qrc:/imgs/qq_white.svg"
}
Text {
anchors.left: top_left_icon.right
anchors.leftMargin: -4
anchors.verticalCenter: top_left_icon.verticalCenter
text: qsTr("QQ")
font.pixelSize: 22
font.bold: false
color: "white"
}
}

Flow {
id: tool_bar
anchors.top: parent.top
anchors.right: parent.right
layoutDirection: Qt.RightToLeft
property int icon_size: 22
Item {
id: btn_close
width: 32
height: 32
Image {
width: tool_bar.icon_size
height: tool_bar.icon_size
anchors.centerIn: parent
source: "qrc:/imgs/close.svg"
}
Rectangle {
color:"red"
opacity: mouse.hovered? 0.6: 0
anchors.fill: parent
}
HoverHandler {
id: mouse
}
MouseArea {
onClicked: (event) => { Qt.quit(); event.accepted = true;}
anchors.fill: parent
}
}
Item {
id: btn_close1
width: 32
height: 32
Image {
width: tool_bar.icon_size
height: tool_bar.icon_size
anchors.centerIn: parent
source: "qrc:/imgs/min.svg"
}
Rectangle {
color:"white"
opacity: mouse1.hovered? 0.2: 0
anchors.fill: parent
}
HoverHandler {
id: mouse1
}
MouseArea {
onClicked: (event) => {window.visibility="Minimized" ; event.accepted = true;}
anchors.fill: parent
}
}
Item {
width: 32
height: 32
Image {
width: tool_bar.icon_size
height: tool_bar.icon_size
anchors.centerIn: parent
source: "qrc:/imgs/setting.svg"
}
Rectangle {
color:"white"
opacity: mouse3.hovered? 0.2: 0
anchors.fill: parent
}
HoverHandler {
id: mouse3
}
MouseArea {
onClicked: (event) => {console.log(left_item.width, left_item.height) ; event.accepted = true;}
anchors.fill: parent
}
}

}
}
// 下半部分
property color bottom_color: "#eeeeee"
Rectangle {
anchors.top: window_top.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
radius: window.radius
color: window.bottom_color
}
Rectangle {
anchors.top: window_top.bottom
anchors.left: parent.left
anchors.right: parent.right
height: window.radius
color: window.bottom_color
}

Rectangle {
anchors.top: window_top.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: window.radius
anchors.topMargin:0
color: window.bottom_color

}
}

码云仓库

​https://gitee.com/luan-it/qml_-qqlogin​

文章对应的提交记录

QML 仿qq登录界面(2) -- gif背景, 主界面布局和标题按钮实现_登录界面_12