使用QML实现扫雷功能案例,使用QML界面实现翻牌特效,以及随机的,从左到右,从中心向两边加载界面的特效实现,简单的示例NumberAnimation,PropertyAnimation,SequentialAnimation实现动画的效果,QML篇
导读
- QML图片特效
- 点击翻转特效
- 点击绕中心旋转
- 点击连续翻转效果
- 点击切牌
- 动态加载界面特效
- 随机加载卡牌特效
- 从左到右加载特效
- 从中心向左右两边散开特效
- 从中心向四周散开特效
- QML完整案例
- 最终效果
- QML文件完整源码
QML图片特效
QML 动画效果建议参考:QML动画 这篇文章;
其中主要用到了以下方法函数:
- NumberAnimation 它定义了当数值改变时应用的动画。
- PropertyAnimation 提供一种将对属性值的更改动画化的方法。
- SequentialAnimation和ParallelAnimation类型允许多个动画一起运行。在SequentialAnimation中定义的动画一个接一个地运行,而在ParallelAnimation中定义的动画是同时运行的。
通过修改 duration 属性[保存动画的持续时间,以毫秒为单位。] 实现特效。
点击翻转特效
在transform: Rotation
设置图片的旋转轴为Y轴,默认angle
(角度)属性值为0
NumberAnimation
方法设置angle
属性在 2000毫秒内从0度翻转到360度
Image {
id: imgitem1
property string image_path:"qrc:/Minesweeper_datas/card-ace-spades.png"
source: image_path
width:100
height: 100
fillMode: Image.PreserveAspectFit // 保持图片比例,适应尺寸
scale: 1.0 // 图片的缩放比例
smooth: true // 是否使用平滑缩放(默认为 true)。
ToolTip{
id:tooltip;
anchors.centerIn: parent
text: "点击翻牌"
timeout: 5000
}
transform: Rotation { //设置旋转的轴
id:rotation
origin.x:imgitem1.width/2
origin.y:imgitem1.height/2
axis{x:0;y:1;z:0}
angle:0
}
//设置翻转 2000毫秒内从0度翻转到360度
NumberAnimation {
id:start_flop
target: rotation
property: "angle"
from:0
to:360
duration: 2000
}
Component.onCompleted:
{
tooltip.open();
}
MouseArea
{
anchors.fill: parent
acceptedButtons: Qt.AllButtons //检查按钮类型
onPressed: {
start_flop.start();
tooltip.close();
}
}
}
效果:
点击绕中心旋转
transform: Rotation
设置旋转中心,沿中心旋转,正值顺时针
NumberAnimation
方法设置angle
属性在 2000毫秒内从0度翻转到360度
Image {
id: imgitem2
property string image_path:"qrc:/Minesweeper_datas/card-ace-spades.png"
source: image_path
width:100
height: 100
fillMode: Image.PreserveAspectFit // 保持图片比例,适应尺寸
scale: 1.0 // 图片的缩放比例
smooth: true // 是否使用平滑缩放(默认为 true)。
ToolTip{
id:tooltip2;
anchors.centerIn: parent
text: "点击绕中心旋转"
timeout: 5000
}
transform: Rotation { //设置旋转的轴
id:rotation2
origin.x:imgitem2.width/2
origin.y:imgitem2.height/2
angle:0
}
//设置翻转 2000毫秒内从0度翻转到360度
NumberAnimation {
id:start_flop2
target: rotation2
property: "angle"
from:0
to:360
duration: 2000
}
Component.onCompleted:
{
tooltip2.open();
}
MouseArea
{
anchors.fill: parent
acceptedButtons: Qt.AllButtons //检查按钮类型
onPressed: {
start_flop2.start();
tooltip2.close();
}
}
}
效果:
点击连续翻转效果
通过 SequentialAnimation
执行一系列 PropertyAnimation
方法
↓在300毫秒内沿中心对称轴从0度旋转到45度
↓300毫秒内沿中心对称轴从45度旋转到0度
↓在300毫秒内沿中心对称轴从0度旋转到-45度
↓在300毫秒内沿中心对称轴从-45度旋转到0度
设置loops属性为3,只执行3次
Image {
id: imgitem3
property string image_path:"qrc:/Minesweeper_datas/card-ace-spades.png"
source: image_path
width:100
height: 100
fillMode: Image.PreserveAspectFit // 保持图片比例,适应尺寸
scale: 1.0 // 图片的缩放比例
smooth: true // 是否使用平滑缩放(默认为 true)。
ToolTip{
id:tooltip3;
anchors.centerIn: parent
text: "点击连续翻转效果"
timeout: 5000
}
transform: Rotation { //设置旋转的轴
id:rotation3
origin.x:imgitem2.width/2
origin.y:imgitem2.height/2
axis{x:0;y:1;z:0}
angle:0
}
//抖动
SequentialAnimation
{
loops: 3
id:image_shake
PropertyAnimation {
target: rotation3
property: "angle"
from:0
to:45
duration: 300
}
PropertyAnimation {
target: rotation3
property: "angle"
from:45
to:0
duration: 300
}
PropertyAnimation {
target: rotation3
property: "angle"
from:0
to:-45
duration: 300
}
PropertyAnimation {
target: rotation3
property: "angle"
from:-45
to:0
duration: 300
}
running: false;
}
//设置抖动
Component.onCompleted:
{
tooltip3.open();
}
MouseArea
{
anchors.fill: parent
acceptedButtons: Qt.AllButtons //检查按钮类型
onPressed: {
image_shake.start();
tooltip3.close();
}
}
}
效果:
点击切牌
同样是通过 SequentialAnimation
执行一系列 PropertyAnimation
方法:
↓先在250毫秒内沿中心对称轴从0度旋转到90度,
↓ 再修改图片路径,
↓再250毫秒内沿中心对称轴从90度旋转到0度,实现一个切牌的效果。
Image {
id: imgitem4
property string image_path:"qrc:/Minesweeper_datas/card-ace-spades.png"
source: image_path
width:100
height: 100
fillMode: Image.PreserveAspectFit // 保持图片比例,适应尺寸
scale: 1.0 // 图片的缩放比例
smooth: true // 是否使用平滑缩放(默认为 true)。
ToolTip{
id:tooltip4;
anchors.centerIn: parent
text: "点击切牌"
timeout: 5000
}
transform: Rotation { //设置旋转的轴
id:rotation4
origin.x:imgitem1.width/2
origin.y:imgitem1.height/2
axis{x:0;y:1;z:0}
angle:0
}
SequentialAnimation
{
property int start_durationn:250
property int end_duration:250
property string new_image_path: "qrc:/Minesweeper_datas/card-9-spades.png"
loops: 1
id:image_overturn
///翻转缓冲
NumberAnimation{
id:start_animation
target: rotation4
property: "angle"
from:0
to:90
duration: image_overturn.start_durationn
}
PropertyAnimation {
target: imgitem4 ;
property: "source" ;
duration: 0;
to: image_overturn.new_image_path
}
NumberAnimation{
id:end_animation
target: rotation4
property: "angle"
from:90
to:0
duration: image_overturn.end_duration
}
running: false;
}
Component.onCompleted:
{
tooltip4.open();
}
MouseArea
{
anchors.fill: parent
acceptedButtons: Qt.AllButtons //检查按钮类型
onPressed: {
image_overturn.start();
tooltip4.close();
}
}
}
效果:
动态加载界面特效
这里用到的加载界面的翻转特效,实际上都是修改切换图片的时间;
通过PropertyAnimation 设置duration属性修改图片的source属性值实现动态加载特效
//初始化时 随机生成
SequentialAnimation
{
loops: 1
id:image_flicker
PropertyAnimation {
target: imgitem ;
property: "source" ;
duration: {
//randomNumber 根据索引获取时间差
if(imageRect.itemindex>=0 && imageRect.itemindex<operate_id.get_Plat_Cols()*operate_id.get_Plat_Rows())
randomNumber(imageRect.itemindex);
else
100;
}
from: "qrc:/Minesweeper_datas/square.png";
to: imgitem.image_path
}
//设置为true 加载时自启动启动
running: true;
}
随机加载卡牌特效
随机加载就是通过PropertyAnimation
方法给每张卡牌设置一个随机的0到1000毫秒内的duration值,
时间到了修改图片路径
function randomNumber(_index)
{
var randomNum = Math.floor(Math.random() * 1000) + 1; // 生成1到1000之间的随机整数
return randomNum;
}
效果:
从左到右加载特效
从左到右:根据索引获取所在列,再计算每列/总列数*800毫秒+50(基础毫秒)
得到每列加载时间。
function randomNumber(_index)
{
var row= Math.max((Math.ceil(_index/operate_id.get_Plat_Rows())-1),0);
var col=_index-row*operate_id.get_Plat_Cols();
return (col/operate_id.get_Plat_Cols())*800+50
}
效果:
从中心向左右两边散开特效
中心向左右两边散开:根据索引,获取所在列,
再根据列计算列与中心作差的绝对值
绝对值/总列数*800毫秒+50毫秒(基础值)
function randomNumber(_index)
{
var row= Math.max((Math.ceil(_index/operate_id.get_Plat_Rows())-1),0);
var col=_index-row*operate_id.get_Plat_Cols();
// console.log("_index ",_index,"col : ",col," abs ",Math.abs((operate_id.get_Plat_Cols()/2)-col))
return (Math.ceil((Math.abs((operate_id.get_Plat_Cols()/2)-col)/operate_id.get_Plat_Cols())*800)+50);
}
效果:
从中心向四周散开特效
从中心散开,包含了行的差值所占比例的时间差;
实际上也可以反过来作差在取绝对值
,变成由四边向中心聚集效果
var row= Math.max((Math.ceil(_index/operate_id.get_Plat_Rows())-1),0);
var col=_index-row*operate_id.get_Plat_Cols();
var col_time= (Math.ceil((Math.abs((operate_id.get_Plat_Cols()/2)-col)/operate_id.get_Plat_Cols())*500)+50);
var row_time= (Math.ceil((Math.abs((operate_id.get_Plat_Rows()/2)-row)/operate_id.get_Plat_Rows())*500)+50);
return (col_time+row_time)
效果:
QML完整案例
最终效果
最终试玩效果:
QML文件完整源码
main.qml
完整代码示例
运行环境:Qt creator 5.13.1 Mingw X64
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import DAL_Connector 1.0
Window {
property int dynamicRows: 2 // 动态行数
property int dynamicColumns: 2 // 动态列数
property int windows_width:340 //窗体宽度
property int windows_heigt:370 //窗体高度
property int banner:0 //旗子数量
id:window_id
visible: true
width: windows_width
height: windows_heigt
title: qsTr("扫雷小程序")
Operate_Connector{
id:operate_id
Component.onCompleted:
{
set_Bomb_Count(40);
set_Plat_Rows(16);
set_Plat_Cols(16);
//生成一个矩阵
reconstruction();
dynamicRows=operate_id.get_Plat_Rows();
dynamicColumns=operate_id.get_Plat_Cols()
windows_width=dynamicColumns*30+20+(dynamicRows-1)
windows_heigt=dynamicRows*30+20+(dynamicColumns-1)
banner=0
setTitleBombNum(banner)
}
}
function setTitleBombNum(_banner)
{
var _title="扫雷小程序 [ "+_banner+" / "+ operate_id.get_Bomb_Count()+" ]";
window_id.title=qsTr(_title)
}
//! 生成随机整数
function randomNumber(_index)
{
_index=_index+1;
switch(operate_id.get_animType())
{
case 0: //随机
var randomNum = Math.floor(Math.random() * 1000) + 1; // 生成1到1000之间的随机整数
return randomNum;
case 1: //从左到右
{
var row= Math.max((Math.ceil(_index/operate_id.get_Plat_Rows())-1),0);
var col=_index-row*operate_id.get_Plat_Cols();
return (col/operate_id.get_Plat_Cols())*800+50
}
case 2: //从中心向左右散开
{
// console.log(Math.max((Math.ceil(_index/operate_id.get_Plat_Rows())-1),0));
var row= Math.max((Math.ceil(_index/operate_id.get_Plat_Rows())-1),0);
var col=_index-row*operate_id.get_Plat_Cols();
// console.log("_index ",_index,"col : ",col," abs ",Math.abs((operate_id.get_Plat_Cols()/2)-col))
return (Math.ceil((Math.abs((operate_id.get_Plat_Cols()/2)-col)/operate_id.get_Plat_Cols())*800)+50);
// var randomNum = Math.floor(Math.random() * 1000) + 1; // 生成1到1000之间的随机整数
// return randomNum;
}
case 3: //从中心辐射散开
{
var row= Math.max((Math.ceil(_index/operate_id.get_Plat_Rows())-1),0);
var col=_index-row*operate_id.get_Plat_Cols();
// var diff=Math.abs((operate_id.get_Plat_Cols()/2)-col)-
var col_time= (Math.ceil((Math.abs((operate_id.get_Plat_Cols()/2)-col)/operate_id.get_Plat_Cols())*500)+50);
var row_time= (Math.ceil((Math.abs((operate_id.get_Plat_Rows()/2)-row)/operate_id.get_Plat_Rows())*500)+50);
// console.log("col_time ",col_time," row_time ", row_time," (col_time+row_time) ",(col_time+row_time))
return (col_time+row_time)
}
}
}
//固定高宽
onWidthChanged:
{
if (width !== windows_width) {
width = windows_width
}
}
onHeightChanged:
{
if (height !== windows_heigt) {
height = windows_heigt
}
}
//背景布局
Rectangle {
//下边框布局
id:rectview
anchors.fill:parent
color: "black"
//弹窗
Dialog {
property string dialog_title:"提示"
id: dialog
title: dialog_title
width: 300
height: 200
x:(parent.width-width)/2
y:(parent.height-height)/2
standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted: {
console.log("Dialog accepted")
operate_id.reconstruction();
repeaid.model=null;
repeaid.model=dynamicRows*dynamicColumns;
}
onRejected:{ console.log("Dialog rejected")
// Qt.quit();
}
Text {
id:textitem
text: "游戏结束"
anchors.centerIn: parent
}
function setText(_text)
{
textitem.text=_text
}
}
Menu
{
title: "Update"
id:contentmenus;
// Action MenuItem
Action {
id: settingsAction
text: "重新开始"
icon.name: "icon-settings"
icon.source: "qrc:/Minesweeper_datas/jigsaw-piece-hover.png"
//Shortcut: "Ctrl+X" //绑定按钮
onTriggered: {
//重新开始扫雷
operate_id.reconstruction();
repeaid.model=null;
repeaid.model=dynamicRows*dynamicColumns;
}
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton // **右键(别落下这个)
onClicked: {
if (mouse.button === Qt.RightButton) { // 右键菜单
//
contentmenus.popup()
}
}
}
//表格样式
Grid {
columns: dynamicColumns // 设置列数为3
rows: dynamicRows // 设置行数为3
anchors.fill: parent
anchors.margins: 10
spacing: 1 // 设置组件之间的间距
// 水平居中
anchors.horizontalCenter: parent.horizontalCenter
// 垂直居中
anchors.verticalCenter: parent.verticalCenter
//循环控件
Repeater {
id:repeaid
model: dynamicRows*dynamicColumns
///单个控件
Rectangle {
id: imageRect
width: 30;
height:30;
//图片控件状态
// 0 没有点击过
// 1 右键点击 插旗
// 2 右键第二次点击 疑问
// 3 左键点击 判断是否是炸弹
property int btn_type:0
property int itemindex:index
///刷新图片
function update_Imgae( _board,_duration)
{
if(btn_type!=3)
{
imgitem.rolling_overs(_board,_duration)
btn_type=3
}
}
///控件显示图片
Image {
id: imgitem
property string image_path:"qrc:/Minesweeper_datas/jigsaw-piece.png"
source: "qrc:/Minesweeper_datas/square.png"
width:parent.width
height: parent.width
fillMode: Image.PreserveAspectFit // 保持图片比例,适应尺寸
scale: 1.0 // 图片的缩放比例
smooth: true // 是否使用平滑缩放(默认为 true)。
transform: Rotation { //设置旋转的轴
id:rotation
origin.x:imgitem.width/2
origin.y:imgitem.height/2
axis{x:0;y:1;z:0}
angle:0
}
SequentialAnimation
{
property int start_durationn:250
property int end_duration:250
loops: 1
id:image_overturn
///翻转缓冲
NumberAnimation{
id:start_animation
target: rotation
property: "angle"
from:0
to:90
duration: image_overturn.start_durationn
}
PropertyAnimation {
target: imgitem ;
property: "source" ;
duration: 0;
to: imgitem.image_path
}
NumberAnimation{
id:end_animation
target: rotation
property: "angle"
from:90
to:0
duration: image_overturn.end_duration
}
running: false;
}
function rolling_over( _path)
{
image_path=_path;
image_overturn.start();
}
function rolling_overs( _board,_duration)
{
image_overturn.start_durationn=200;
image_overturn.end_duration=_duration;
//修改显示卡牌
switch(_board)
{
case 0:
image_path="qrc:/Minesweeper_datas/square.png";
image_overturn.start_durationn=200;
image_overturn.end_duration=200;
break;
case 1:
image_path="qrc:/Minesweeper_datas/card-ace-spades.png";
break;
case 2:
image_path="qrc:/Minesweeper_datas/card-2-spades.png";
break;
case 3:
image_path="qrc:/Minesweeper_datas/card-3-spades.png";
break;
case 4:
image_path="qrc:/Minesweeper_datas/card-4-spades.png";
break;
case 5:
image_path="qrc:/Minesweeper_datas/card-5-spades.png";
break;
case 6:
image_path="qrc:/Minesweeper_datas/card-6-spades.png";
break;
case 7:
image_path="qrc:/Minesweeper_datas/card-7-spades.png";
break;
case 8:
image_path="qrc:/Minesweeper_datas/card-8-spades.png";
break;
default:
image_path="qrc:/Minesweeper_datas/mace-head.png";
break;
}
image_overturn.start();
}
//初始化时 随机生成
SequentialAnimation
{
loops: 1
id:image_flicker
PropertyAnimation {
target: imgitem ;
property: "source" ;
duration: {
if(imageRect.itemindex>=0 && imageRect.itemindex<operate_id.get_Plat_Cols()*operate_id.get_Plat_Rows())
randomNumber(imageRect.itemindex);
else
100;
}
from: "qrc:/Minesweeper_datas/square.png";
to: imgitem.image_path
}
running: true;
}
//抖动
SequentialAnimation
{
loops: 1
id:image_shake
PropertyAnimation {
target: rotation
property: "angle"
from:0
to:45
duration: 300
}
PropertyAnimation {
target: rotation
property: "angle"
from:45
to:0
duration: 300
}
PropertyAnimation {
target: rotation
property: "angle"
from:0
to:-45
duration: 300
}
PropertyAnimation {
target: rotation
property: "angle"
from:-45
to:0
duration: 300
}
running: false;
}
function shake_over()
{
image_shake.running=true;
}
function shake_over_stop()
{
image_shake.running=false;
rotation.angle=0;
}
}
//Rectangle 鼠标事件
MouseArea
{
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.AllButtons
onEntered:{
if(btn_type!=3 && btn_type!=1 )
imgitem.shake_over();
}
onExited:{
if(btn_type!=3 && btn_type!=1)
imgitem.shake_over_stop();
}
onPressed: {
if(btn_type==3)
return;
var Old_btn_type=btn_type;
//先移除抖动效果
if(btn_type!=3 && btn_type!=1)
imgitem.shake_over_stop();
//开始翻牌
if(mouse.button===Qt.LeftButton){
if(btn_type!=3 && btn_type!=1)
{
operate_id.is_Bomb(index);
}
}
else if(mouse.button===Qt.RightButton)
{
btn_type++;
if(btn_type==3)
btn_type=0;
}
// console.log("Old_btn_type: ",Old_btn_type," btn_type: ",btn_type)
//检测旗帜的数量
if(Old_btn_type==1 && btn_type!=1)
{
window_id.banner--;
window_id.setTitleBombNum(window_id.banner)
}
else if(Old_btn_type!=1&& btn_type==1)
{
window_id.banner++;
window_id.setTitleBombNum(window_id.banner)
}
//修改图标状态
if(btn_type==1)
{
imgitem.rolling_over("qrc:/Minesweeper_datas/knight-banner.png");
}
else if(btn_type==2)
{
imgitem.rolling_over("qrc:/Minesweeper_datas/help.png");
}
else if(btn_type==0)
{
imgitem.rolling_over("qrc:/Minesweeper_datas/jigsaw-piece.png");
}
}
}
}
}
Connections
{
//信号 On+首字母大写
target:operate_id
onBlank_clearing:
{
var imageRect= repeaid.itemAt(index)
imageRect.update_Imgae(board,duration);
}
onGameWin:
{
dialog.setText("胜利!");
dialog.open();
console.log("胜利!");
}
onGameFailure:
{
dialog.setText("失败!");
dialog.open();
console.log("失败!");
}
}
}
}
}
C++ 数据处理篇:QML小案例 使用QML简单实现翻牌版扫雷游戏(一)