最终成果
我们要模仿的对象:
资源文件
代码中会用到一些图片,我已将示例代码上传到码,文章最后会提供链接。
代码中用到的资源文件:
如何找到需要的资料
Qt的资料很多,想要看完是不可能的,遇到问题,先百度,看看前辈们是怎么解决的,解决方案中可能会有一些示例代码,copy过来运行一把,能用当然后好,如果不能用再到官网上查找相应类的文档。
可以到下面的网站中搜索类,或者如果有经验的话,在这里搜索关键字也是可以的。
QML所有类型:https://doc.qt.io/qt-6/qmltypes.html
QT官网的查找功能
不知道是什么原因,这功能时好时不好(可能是因网络太差了吧,毕竟天朝访问国外的网站是诸多限制)
显示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;
}
}
显示背景动图
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 {
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行,为了让底部的圆角不被覆盖,四周有一些留白(背景图片会漏出来)。
为解决这个问题,我们只好再它的背后再填充一层背景
// 下半部分
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
}
或许你已经想到了,对!又产生了另一个问题
所以还得加一层背景
// 下半部分
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"
}
// 下半部分
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
// 关闭按钮
// 最小化
// 设置
}
}
标题栏--左侧
// 左侧
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
时发现了一个更合适的类
相关文档: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
有必要说明一下我是怎么找到这个文档的
- 这个也是无意中发现的,虽然之前有看过但是没没有点击来看...
工具按钮的布局--其他
说是其它,其是就是最小化和 设置
按钮,和关闭按钮一个模式。
需要注说的是控件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
文章对应的提交记录