1. 前言
以前的弹窗效果有些看腻了,这次更新下界面。
教程被应用在MediaStateT中, Tag v1.5
MediaStateT Github项目地址: https://github.com/taxue-alfred/MediaStateT
MediaStateT Gitee项目地址: https://gitee.com/MediaState/MediaStateT
2. 效果展示
以前界面:
目前界面:
窗口绘制:
PopWindow
使用Loader
控件进行了模块化,非常好用,可以随便在Loader里面添加控件
需要注意的是,添加到Loader中的控件id不能被外部所访问,所以建议在外部先加载好控件然后传递到Loader
- 直接在
source_component
添加Conponent
控件即可
需要注意的是Component只能有一个控件,所以建议先创建一个大的Rectangle然后再在Rectangle里面创建其他控件,最后传递Component也就传递了Rectangle及其整个界面
- 文章最后有调用示例,可以参考
3. 代码参考
这次只是相较于上次添加了一些组件和虚化效果,还有主函数的调用逻辑都进行了更新。
整体的代码不是很难,必要的注释都写在代码里了。需要注意的是:
PopWindow.qml
的Window
属性的x
y
可以被外部调用的时候被覆盖.- 窗口的大小根据
Loader
里面content_loader
的内容大小改变 sign
这个变量只是一个标志,为了防止动画被多次执行。
PopWindow.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Window
Window{
id: pop_window
visible: true
visibility: Window.Windowed
color: "transparent"
// 透明度
opacity: 0
// 取消边框
flags:Qt.FramelessWindowsHint | Qt.ToolTip
// 设置为非模态
modality: Qt.NonModal
//设置初始位置(外部设置会覆盖此设置)
x: Screen.width - content_loader.width
y: 100
//根据Loader设置大小
width: content_loader.width
height: content_loader.height
MouseArea{
id:content_mouse
anchors.fill: parent
hoverEnabled: true
}
// 设置出现后显示时间的计时器
Timer{
id:show_timer
interval: 2500
repeat: true
onTriggered:{
//这个if实现了鼠标悬停保留窗口的效果
if(!loader_mouse.containsMouse){
hideWindow();
}
}
}
//绘制图形的Loader,供外部使用
property alias source_component:content_loader.sourceComponent
Loader{
id:content_loader
MouseArea{
id:loader_mouse
anchors.fill:parent
hoverEnabled:true
}
}
//---------------------------动画部分开始-------------------
//设置出现显示动画相对于设置的x值的偏移位置,这里变量外部进行了设置
property int x_offset: 0
property int x_origin: 0
//设置sign标志,防止多次showWindow调用造成多次动画显示
property int sign: 0
ParallelAnimation{
id: show_anim
// 透明度动画
PropertyAnimation{
target:pop_window
property: "opacity"
easing.type: Easing.InQuart
from:pop_window.opacity
to: 1
duration:350
}
// 位置移动动画
PropertyAnimation{
target:pop_window
property: "x"
easing.type: Easing.OutBounce
//从当前值开始移动
from: pop_window.x
to: Screen.width - content_loader.width - x_offset
duration:800
}
// 大小缩放动画
PropertyAnimation{
target:pop_window
property: "width"
easing.type: Easing.OutCirc
//从当前值开始移动
from: 0
to: content_loader.width
duration:200
}
// 大小缩放动画
PropertyAnimation{
target:pop_window
property: "height"
easing.type: Easing.OutCirc
//从当前值开始移动
from: 0
to: content_loader.height
duration:200
}
onStarted:{
pop_window.show()
}
//出现动画结束信号
onFinished:{
show_timer.restart()
}
}
//设置关闭显示动画
ParallelAnimation{
id: hide_anim
// 透明度动画
PropertyAnimation{
target:pop_window
property: "opacity"
easing.type: Easing.OutCubic
from:pop_window.opacity
to: 0
duration:800
}
//位置移动动画
PropertyAnimation{
target:pop_window
property: "x"
easing.type: Easing.InExpo
//从当前值开始移动
from: pop_window.x
to: x_origin
duration:800
}
//大小缩放动画
PropertyAnimation{
target:pop_window
property: "width"
easing.type: Easing.InQuint
//从当前值开始移动
from: content_loader.width + 10
to: 0
duration:1000
}
//大小缩放动画
PropertyAnimation{
target:pop_window
property: "height"
easing.type: Easing.InQuint
//从当前值开始移动
from: content_loader.height + 10
to: 0
duration:1000
}
//结束动画结束之后停止定时器
onFinished:{
show_timer.stop()
}
}
//显示弹窗
function showWindow(){
if(sign === 0){
hide_anim.stop()
show_anim.start()
sign = 1
}
}
//隐藏弹窗
function hideWindow(){
if(sign === 1){
show_anim.stop()
hide_anim.start()
sign = 0
}
}
//---------------------------动画部分结束-------------------
}
调用:
media_stateT.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Window
import Qt5Compat.GraphicalEffects
import Qt.labs.platform
import qt.txwh.MT_info_get 1.0
Item{
//绘制窗口,然后传到PopWindow模块的Loader里
Component{
id:window_draw
Rectangle{
id:bk_rectangle
width:200
height:70
radius:10
color: Qt.rgba(0.8,0.8,0.8,0.95)
Image{
id:img_background
width:bk_rectangle.width - 40
height:bk_rectangle.height - 16
source:"/icon/MtIcon.jpg"
smooth: true
mipmap:true
cache:false
visible:false
fillMode: Image.PreserveAspectCrop
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter : parent.horizontalCenter
anchors.right: parent.right
anchors.rightMargin: 6
}
Rectangle {
id: img_bck_mask
width: img_background.width
height: img_background.height
radius: 10
color: "red"
visible: false
}
OpacityMask {
anchors.fill: img_background
source: img_background
maskSource: img_bck_mask
}
FastBlur {
anchors.fill: img_background
source: img_background
radius: 32
transparentBorder:true
}
Image {
id: img
width: 60
height: 60
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin:6
source: "/icon/MtIcon.jpg"
sourceSize.width:1024
sourceSize.height:1024
smooth: true
mipmap:true
visible: false
cache:false
}
Rectangle {
id: img_mask
width: img.width
height: img.height
radius: 10
color: "red"
visible: false
}
OpacityMask {
anchors.fill: img
source: img
maskSource: img_mask
}
TextScroll{
id:music_name_label
width:110
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -16
anchors.left:img.right
anchors.leftMargin:10
fontPixelSize:17
text:qsTr("Music Name")
}
TextScroll{
id:player_name_label
width:110
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 16
anchors.left:img.right
anchors.leftMargin:10
fontPixelSize:13
text:qsTr("Player")
}
//获取信息类,这是一个C++类
MT_info_get{
id:mt_info_get
//存储上一次的history时间信息,用string是为了防止数字过大造成溢出
property string last_file_time;
//这边从C++传来的值进行赋值的时候需要写function()
//这里QT6和Qt5的Quick有些不同
//这样子加个function可以很明显的标识这是由emit传来的signal的参数
//详细链接可以参考这个: https://www.direktembedded.com/qt5-to-qt6-qml-pyside-porting/
onNe_file_time_got:
function(temp_time){
if(temp_time != last_file_time){
//获取信息
mt_info_get.music_info_get()
mt_info_get.player_info_get()
mt_info_get.img_download()
//显示窗口
pop_window.showWindow()
last_file_time = temp_time
}
}
onMusic_info_got:
function(music_name){
music_name_label.text = music_name
//重新获取图片
mt_info_get.img_download()
//由于QtImage存在缓存机制,需要cache:false并且先置空路径再设置路径才会加载图片,不改变路径直接调用上次的缓存
img.source = ""
img.source = "file:./album.jpg"
img_background.source = ""
img_background.source = "file:./album.jpg"
}
onPlayer_info_got:
function(player_name){
player_name_label.text = player_name
}
onAutostart_status:
function(value){
if (value){
menu_right.sws.checked = true
}else{
menu_right.sws.checked = false
}
}
onHot_key_activated:
function(value){
if(value === 1){
pop_window.showWindow()
}else if(value === 2){
mt_fre.showWindow()
}else if(value === 3){
mtBoardConnect.init();
menu_right.connectMSBoardState = true;
}
}
onGot_mouse_cursor:
//Qt6用法。qt5可忽略function
function(x,y,scale){
menu_right.menu_x = x / scale
menu_right.menu_y = y / scale
menu_right.show()
//展现在最上层
menu_right.raise()
}
//获取网易云音乐history的修改时间函数
function ne_file_time(){
mt_info_get.NE_file_time()
}
}
// 循环更新网易云歌曲等参数
Timer{
interval:500
repeat:true
running:true
onTriggered:{
mt_info_get.ne_file_time();
}
}
SystemTrayIcon{
id:system_icon
visible: true
icon.source: "qrc:/icon/MtIcon.jpg"
tooltip:"MediaStateT"
onActivated:
//reason是SystemTrayIcon固有属性,不同数值对应不同的按键事件
//来源:https://gist.github.com/kviktor/
function(reason){
//双击,单机,2,3,右键1
if(reason === 2 || reason === 3){
//显示窗口
pop_window.showWindow()
}else if(reason === 1){
//定义过的槽函数,执行操作后,然后发射到get_mouse_cursor
mt_info_get.get_mouse_cursor()
}
}
}
PageMenu{
id:menu_right
onSws_click:{
if(menu_right.sws.checked){
mt_info_get.create_sws()
// 再次进行状态检测确保添加成功
mt_info_get.check_start_with_system()
}else{
mt_info_get.remove_sws()
mt_info_get.check_start_with_system()
}
}
Component.onCompleted: mt_info_get.check_start_with_system()
onSw_click:{
pop_window.showWindow()
}
onSf_click:{
mt_fre.showWindow()
}
}
}
}
PopWindow{
id:pop_window
// 设置初始位置,对PopWindow里面的x,y进行了覆盖
x_offset: get_screen_pixel(0.005, Screen.width)
x: get_screen_pixel(0.94, Screen.width) //设置0.94不设置1是为了防止不同缩放下屏幕边缘乱跳的问题
y: get_screen_pixel(0.13, Screen.height)
x_origin: get_screen_pixel(0.94, Screen.width)
source_component:window_draw
// 为了适应不同的屏幕,需要使用百分比表示
function get_screen_pixel(percent, sum_pixel){
return percent * sum_pixel
}
}
PageAudioFrequency{
id:mt_fre
}
}
参考: