《qt quick核心编程》笔记三

9 C++和QML混合编程

9.1 在QML中使用C++类和对象

qt提供两种方式在QML环境中使用C++对象:

  1. 在C++中实现一个类,注册为QML环境的一个类型,在QML环境中使用该类型创建对象
  2. 在C++中构造一个对象,将这个对象设置为QML的上下文属性,在QML环境中直接使用该属性
9.1.1 前提条件:定义可以导出的C++类

一个C++类必须满足以下条件才能够导出到QML环境中使用:

  • QObjectQObject的派生类继承
  • 使用Q_OBJECT

QML中使用C++导出的类,只能使用以下特征的属性和方法:

  1. 信号和槽
  2. Q_INVOKABLE宏修饰的成员函数
  3. Q_ENUM宏修饰的枚举类型
  4. Q_PROPERTY宏修饰的成员属性(常用READ、WRITE、NOTIFY标记)

<colormaker.h>头文件

#ifndef COLORMAKER_H
#define COLORMAKER_H

#include <QObject>
#include <QColor>

class ColorMaker : public QObject
{
	Q_OBJECT
	Q_PROPERTY(QColor color READ getColor WRITE setColor NOTIFY colorChanged)
	Q_PROPERTY(QColor timeColor READ timeColor)

public:
	explicit ColorMaker(QObject *parent = nullptr);
	~ColorMaker();

	enum GenerateAlgorithm{ RandomRGB, RandomRed, RandomGreen, RandomBlue, LinearIncrease};
	Q_ENUM(GenerateAlgorithm)

	Q_INVOKABLE ColorMaker::GenerateAlgorithm algorithm() const;
	Q_INVOKABLE void setAlgorithm(ColorMaker::GenerateAlgorithm algorithm);

	QColor getColor() const;
	void setColor(const QColor &color);
	QColor timeColor() const;

signals:
	void colorChanged(const QColor &color);
	void currentTime(const QString &strTime);

public slots:
	void start();
	void stop();

protected:
 void timerEvent(QTimerEvent *e);


private:
	GenerateAlgorithm m_algorithm;
	QColor m_currentColor;
	int m_nColorTimer;
};

#endif // COLORMAKER_H

<colormaker.c>源文件

#include "colormaker.h"
#include <QDateTime>
#include <QRandomGenerator>
#include <QTimerEvent>

ColorMaker::ColorMaker(QObject *parent)
	: QObject(parent), m_algorithm(RandomRGB), m_currentColor(Qt::black),
	  m_nColorTimer(0) {}
ColorMaker::~ColorMaker() {}
QColor ColorMaker::getColor() const { return m_currentColor; }
void ColorMaker::setColor(const QColor &color) {
  m_currentColor = color;
  emit colorChanged(m_currentColor);
}
QColor ColorMaker::timeColor() const {
  QTime time = QTime::currentTime();
  int r = time.hour();
  int g = time.minute() * 2;
  int b = time.second() * 4;
  return QColor::fromRgb(r, g, b);
}

ColorMaker::GenerateAlgorithm ColorMaker::algorithm() const {
  return m_algorithm;
}

void ColorMaker::setAlgorithm(GenerateAlgorithm algorithm) {
  m_algorithm = algorithm;
}
void ColorMaker::start() {
  if (m_nColorTimer == 0) {
	m_nColorTimer = startTimer(1000);
  }
}

void ColorMaker::stop() {
  if (m_nColorTimer > 0) {
	killTimer(m_nColorTimer);
	m_nColorTimer = 0;
  }
}
void ColorMaker::timerEvent(QTimerEvent *e) {
  if (e->timerId() == m_nColorTimer) {
	switch (m_algorithm) {
	case RandomRGB:
	  m_currentColor.setRgb(QRandomGenerator::global()->bounded(255),
							QRandomGenerator::global()->bounded(255),
							QRandomGenerator::global()->bounded(255));
	  break;
	case RandomRed:
	  m_currentColor.setRed(QRandomGenerator::global()->bounded(255));
	  break;
	case RandomGreen:
	  m_currentColor.setGreen(QRandomGenerator::global()->bounded(255));
	  break;
	case RandomBlue:
	  m_currentColor.setBlue(QRandomGenerator::global()->bounded(255));
	  break;
	case LinearIncrease: {
	  int r = m_currentColor.red() + 10;
	  int g = m_currentColor.green() + 10;
	  int b = m_currentColor.blue() + 10;
	  m_currentColor.setRgb(r % 255, g % 255, b % 255);
	} break;
	}
	emit colorChanged(m_currentColor);
	emit currentTime(
		QDateTime::currentDateTime().toString("yyyy-MM-ddhh:mm:ss"));
  } else {
	QObject::timerEvent(e);
  }
}
9.1.2 使用方法1:将一个C++类注册为QML类型使用

首先定义出一个供QML访问的C++类,接下来需要将这个C++类型注册为QML类型,然后才能在QML中使用

注册QML类型的函数:
qmlRegisterSingletonType()注册表一个单例类型
qmlRegisterType()注册一个非单例类型
qmlRegisterTypeNotAvailable()注册一个类型用来占位
qmlRegisterUncreatableType()注册一个具有附加属性的附加类型

举例qmlRegisterType如下:

//用来注册一个新类型
template<typename T>
int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName);

//为特定的版本注册类型
template<typename T, int metaObjectRevision>
int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName);

uri:指定一个唯一的包名,"import QtQuick.Controls 1.2"中的QtQuick.Controls就是包名,1.2则是版本
versionMajor:包的主版本号,"import QtQuick.Controls 1.2"中的1.2中的1
versionMinor:包的子版本号,"import QtQuick.Controls 1.2"中的1.2中的2
qmlName:QML中可以使用的类名

<main.cpp>

#include <QtGui/QGuiApplication>
#include <QtQuick/QQuickView>
#include <QtQml>
#include "colorMaker.h"
int main(int argc, char *argv[])
{
	QGuiApplication app(argc, argv);
	qmlRegisterType<ColorMaker>("an.qt.ColorMaker", 1, 0,"ColorMaker");		//注册ColorMaker类
	QQuickView viewer;
	viewer.setResizeMode(QQuickView::SizeRootObjectToView);
	viewer.setSource(QUrl("qrc:///main.qml"));
	viewer.show();
	return app.exec();
}

后续即可直接在qml中使用:
<main.qml>

import QtQuick 2.2
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import an.qt.ColorMaker 1.0

Window {
	width: 360
	height: 360
	visible: true
	Text {
		id: timeLabel
		anchors.left: parent.left
		anchors.leftMargin: 4
		anchors.top: parent.top
		anchors.topMargin: 4
		font.pixelSize: 26
	}
	ColorMaker {
		id: colorMaker
		color: Qt.green
	}
	Rectangle {
		id: colorRect
		anchors.centerIn: parent
		width: 200
		height: 200
		color: "blue"
	}
	Button {
		id: start
		text: "start"
		anchors.left: parent.left
		anchors.leftMargin: 4
		anchors.bottom: parent.bottom
		anchors.bottomMargin: 4
		onClicked: {
			colorMaker.start()
		}
	}
	Button {
		id: stop
		text: "stop"
		anchors.left: start.right
		anchors.leftMargin: 4
		anchors.bottom: start.bottom
		onClicked: {
			colorMaker.stop()
		}
	}
	function changeAlgorithm(button, algorithm) {
		switch (algorithm) {
		case 0:
			button.text = "RandomRGB"
			break
		case 1:
			button.text = "RandomRed"
			break
		case 2:
			button.text = "RandomGreen"
			break
		case 3:
			button.text = "RandomBlue"
			break
		case 4:
			button.text = "LinearIncrease"
			break
		}
	}
	Button {
		id: colorAlgorithm
		text: "RandomRGB"
		anchors.left: stop.right
		anchors.leftMargin: 4
		anchors.bottom: start.bottom
		onClicked: {
			var algorithm = (colorMaker.algorithm() + 1) % 5
			changeAlgorithm(colorAlgorithm, algorithm)
			colorMaker.setAlgorithm(algorithm)
		}
	}
	Button {
		id: quit
		text: "quit"
		anchors.left: colorAlgorithm.right
		anchors.leftMargin: 4
		anchors.bottom: start.bottom
		onClicked: {
			Qt.quit()
		}
	}
	Component.onCompleted: {
		colorMaker.color = Qt.rgba(0, 180, 120, 255)
		colorMaker.setAlgorithm(ColorMaker.LinearIncrease)
		changeAlgorithm(colorAlgorithm, colorMaker.algorithm())
	}
	Connections {
		target: colorMaker
		function onCurrentTime(strTime) {
			timeLabel.text = strTime
			timeLabel.color = colorMaker.timeColor
		}
	}
	Connections {
		target: colorMaker
		function onColorChanged(color) {
			colorRect.color = color
		}
	}
}
9.1.3 使用方法2:将一个C++对象注册为QML属性使用

首先要先注册属性
<main.cpp>

#include <QtGui/QGuiApplication>
#include <QtQuick/QQuickView>
#include <QtQml>
#include "colorMaker.h"
int main(int argc, char *argv[])
{
	QGuiApplication app(argc, argv);
	QQuickView viewer;
	viewer.setResizeMode(QQuickView::SizeRootObjectToView);
	viewer.rootContext()->setContextProperty("colorMaker", new ColorMaker); //注册为属性
	viewer.setSource(QUrl("qrc:///main.qml"));
	viewer.show();
	return app.exec();
}

然后修改qml中的内容,无需import类,直接使用注册的属性名即可
<main.qml>

import QtQuick 2.2
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true
	Text {
		id: timeLabel
		anchors.left: parent.left
		anchors.leftMargin: 4
		anchors.top: parent.top
		anchors.topMargin: 4
		font.pixelSize: 26
	}

	Rectangle {
		id: colorRect
		anchors.centerIn: parent
		width: 200
		height: 200
		color: "blue"
	}
	Button {
		id: start
		text: "start"
		anchors.left: parent.left
		anchors.leftMargin: 4
		anchors.bottom: parent.bottom
		anchors.bottomMargin: 4
		onClicked: {
			colorMaker.start()
		}
	}
	Button {
		id: stop
		text: "stop"
		anchors.left: start.right
		anchors.leftMargin: 4
		anchors.bottom: start.bottom
		onClicked: {
			colorMaker.stop()
		}
	}
	function changeAlgorithm(button, algorithm) {
		switch (algorithm) {
		case 0:
			button.text = "RandomRGB"
			break
		case 1:
			button.text = "RandomRed"
			break
		case 2:
			button.text = "RandomGreen"
			break
		case 3:
			button.text = "RandomBlue"
			break
		case 4:
			button.text = "LinearIncrease"
			break
		}
	}
	Button {
		id: colorAlgorithm
		text: "RandomRGB"
		anchors.left: stop.right
		anchors.leftMargin: 4
		anchors.bottom: start.bottom
		onClicked: {
			var algorithm = (colorMaker.algorithm() + 1) % 5
			changeAlgorithm(colorAlgorithm, algorithm)
			colorMaker.setAlgorithm(algorithm)
		}
	}
	Button {
		id: quit
		text: "quit"
		anchors.left: colorAlgorithm.right
		anchors.leftMargin: 4
		anchors.bottom: start.bottom
		onClicked: {
			Qt.quit()
		}
	}
	Component.onCompleted: {
		colorMaker.color = Qt.rgba(0, 180, 120, 255)
		colorMaker.setAlgorithm(colorMaker.LinearIncrease)
		changeAlgorithm(colorAlgorithm, colorMaker.algorithm())
	}
	Connections {
		target: colorMaker
		function onCurrentTime(strTime) {
			timeLabel.text = strTime
			timeLabel.color = colorMaker.timeColor
		}
	}
	Connections {
		target: colorMaker
		function onColorChanged(color) {
			colorRect.color = color
		}
	}
}

9.2 在C++中使用QML对象

9.2.1 查找一个对象的孩子

QObject类的构造函数有一个parent参数,指定一个对象的父亲,QML中的对象借助这个组成了以根Item为根的对象树
QObject类中有一个objectName属性,代表这个对象的名字
通过对象名字可以用于findChild()findChildren()查找孩子

T QObject::findChild(const QString & name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const;
QList<T> QObject::findChildren(const QString & name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const;
QList<T> QObject::findChildren(const QRegExp & regExp, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const;
QList<T> QObject::findChildren(const QRegularExpression & re, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const;

示例:查找parentWidget的名为"button1",类型为QPushButton的孩子

QPushButton *button = parentWidget->findChild<QPushButton*>("button1");

示例:查找parentWidget的所有名为"widgetname"的QWidget类型的孩子列表

QList<QWidget *> widgets = parentWidget->findChildren<QWidget *>("widgetname");
9.2.2 使用元对象调用QML对象的方法

QMetaObject的invokeMethod()静态方法用来调用一个对象的信号、槽、可调用方法.

bool QMetaObject::invokeMethod(
	QObject * obj,
	const char * member,
	Qt::ConnectionType type,
	QGenericReturnArgument ret,
	QGenericArgument val0 = QGenericArgument( 0 ),
	QGenericArgument val1 = QGenericArgument(),
	QGenericArgument val2 = QGenericArgument(),
	QGenericArgument val3 = QGenericArgument(),
	QGenericArgument val4 = QGenericArgument(),
	QGenericArgument val5 = QGenericArgument(),
	QGenericArgument val6 = QGenericArgument(),
	QGenericArgument val7 = QGenericArgument(),
	QGenericArgument val8 = QGenericArgument(),
	QGenericArgument val9 = QGenericArgument()
	)

//QGenericReturnArgument 原型为:
QGenericReturnArgument Q_RETURN_ARG( Type, Type & value)

//QGenericArgument 原型为:
QGenericArgument Q_ARG( Type, const Type & value)

返回值:true调用成功,false调用失败
obj:被调用对象的指针
member:方法名字
type:连接类型

  • Qt::DirectConnection
  • Qt::AutoConnection
  • Qt::QueuedConnection

ret:接收调用方法的返回值
val0-val10:用于传递给调用方法的参数

调用方法:
假设一个对象有一个槽compute(QString, int, double),返回一个QString对象

QString retVal;
QMetaObject::invokeMethod(
	obj, 
	"compute", 
	Qt::DirectConnection,
	Q_RETURN_ARG(QString, retVal),
	Q_ARG(QString, "sqrt"),
	Q_ARG(int, 42),
	Q_ARG(double, 9.7)
	);

示例代码如下:
<changecolor.h>

#ifndef CHANGECOLOR_H
#define CHANGECOLOR_H

#include <QObject>
#include <QTimer>
class ChangeQmlColor : public QObject {
  Q_OBJECT
public:
  ChangeQmlColor(QObject *target, QObject *parent = 0);
  ~ChangeQmlColor();
protected slots:
  void onTimeout();

private:
  QTimer m_timer;
  QObject *m_target;
};
#endif // CHANGECOLOR_H

<changecolor.cpp>

#include "changeColor.h"
#include <QColor>
#include <QDateTime>
#include <QVariant>
ChangeQmlColor::ChangeQmlColor(QObject *target, QObject *parent)
	: QObject(parent), m_timer(this), m_target(target) {
  qsrand(QDateTime::currentDateTime().toTime_t());
  connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
  m_timer.start(1000);
}
ChangeQmlColor::~ChangeQmlColor() {}
void ChangeQmlColor::onTimeout() {
  QColor color = QColor::fromRgb(qrand() % 256, qrand() % 256, qrand() % 256);
  m_target->setProperty("color", color);
}

<main.cpp>

#include "changecolor.h"
#include <QApplication>
#include <QColor>
#include <QQmlApplicationEngine>
#include <QtQml>

int main(int argc, char *argv[]) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
  QApplication app(argc, argv);

  QQmlApplicationEngine engine;
  const QUrl url(QStringLiteral("qrc:/main.qml"));
  engine.load(url);

  QObject *root = NULL;
  QList<QObject *> rootObjects = engine.rootObjects();
  int count = rootObjects.size();
  for (int i = 0; i < count; i++) {
	if (rootObjects.at(i)->objectName() == "rootObject") {
	  root = rootObjects.at(i);
	  break;
	}
  }
  new ChangeQmlColor(root);
  QObject *quitButton = root->findChild<QObject *>("quitButton");
  if (quitButton) {
	QObject::connect(quitButton, SIGNAL(clicked()), &app, SLOT(quit()));
  }
  QObject *textLabel = root->findChild<QObject *>("textLabel");
  if (textLabel) {
	// this will failed
	bool bRet = QMetaObject::invokeMethod(textLabel, "setText",Q_ARG(QString, "world hello"));
	qDebug() << "call setText return - " << bRet;
	textLabel->setProperty("color", QColor::fromRgb(255, 0, 0));
	bRet = QMetaObject::invokeMethod(textLabel, "doLayout");
	qDebug() << "call doLayout return - " << bRet;
  }

  return app.exec();
}

<main.qml>

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	objectName: "rootObject"
	width: 360
	height: 360
	visible: true
	Text {
		objectName: "textLabel"
		text: "Hello World"
		anchors.centerIn: parent
		font.pixelSize: 26
	}
	Button {
		anchors.right: parent.right
		anchors.rightMargin: 4
		anchors.bottom: parent.bottom
		anchors.bottomMargin: 4
		text: "quit"
		objectName: "quitButton"
	}
}

10 动画

基本动画对象
PropertyAnimation:可以改变各种property产生动画
NumberAnimation:PropertyAnimation的派生类,专门改变数字类型的property产生动画
ColorAnimation:PropertyAnimation的派生类,专门改变color类型的property产生动画
RotationAnimation:PropertyAnimation的派生类,专门改变rotation的值产生动画
Vector3dAnimation:PropertyAnimation的派生类,再一个Vector3d发生变化时使用
PathAnimation:让对象沿着一个给定的路径运动
SmoothAnimation:允许一个property跟踪一个值,产生平滑动画
SpringAnimation:允许一个property跟踪一个值,动画效果类似于弹簧运动

组合动画对象:
SequentialAnimation:顺序执行一系列动画
ParallelAnimation:并行执行一系列动画

动画搭档:
Behavior:为Item的property变化绑定一个默认的动画对象
ParentAnimation:在改变一个Item的parent时使用,使得该Item从旧parent移动到新parent的过程更平滑,通常和Transition、State、ParentChange联合使用
AnchorAnimation:在改变一个Item的anchor时使用,平滑变化过程,通常和Transition、State、ParentChange联合使用
PauseAnimation:在动画过程中插入它,可以将动画过程暂停一段时间
PropertyAction:在动画执行过程中立即改变某个属性
ScriptAction:在动画执行过程中运行一段ECMAScript脚本

10.1 基本动画元素

Animation是Qt Quick中所有动画类的基类
有以下属性
running:布尔值,指示动画是否在运行,默认false,start()方法置为true,stop()方法置为false
loops:动画的执行次数,默认值1,赋值Animation.Infinite导致动画永远的循环执行下去
paused:布尔值,默认false,指定动画当前是否处于暂停状态,pause()方法置为true
alwaysRunToEnd:布尔值,默认false,是否即使stop()或设置running为false,动画也要执行完

有以下方法:
start():启动一个动画,如果动画已经执行,则什么也不干
stop():终止一个动画,如果动画没执行完,那么动画操作的属性可能就是某个中间值
restart():等同于先调用stop(),在调用start()
pause():暂停一个动画,如果动画已经暂停,则什么也不干
resume():和pause()对应,让一个动画继续执行
complete():完成一个动画,如果动画执行到某个中间步骤,将使动画直接跳到结束状态

有以下信号:
started():动画开始时触发(只有顶层的动画才会触发信号,动画位于某个动画分组或Behavior、Transition中,则不会触发)
stopped():动画(手动或自动)执行完毕进入停止状态时触发,只有单独的顶层动画才会触发

启动动画的方法:

  • 调用start()方法
  • 设置running为true
  • 为running绑定表达式,表达式求值结果为true

停止动画的方法:

  • 调用stop()方法
  • 设置running为false
  • 触发running绑定表达式重新求值,且返回值为false
  • 调用complete()方法
10.1.2 PropertyAnimation(属性变化动画基类)
单独使用:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true

	Rectangle {
		width: 360
		height: 240
		color: "#EEEEEE"
		id: rootItem
		Rectangle {
			id: rect
			width: 50
			height: 150
			anchors.centerIn: parent
			color: "blue"
			PropertyAnimation {
				id: animation
				target: rect
				property: "width"
				to: 150
				duration: 1000
			}
			MouseArea {
				anchors.fill: parent
				onClicked: animation.running = true
			}
		}
	}
}

//同时改变两个属性
PropertyAnimation {
	id: animation;
	target: rect;
	properties: "width,height";
	to: 220;
	duration: 1000;
}

//同时改变两个对象
PropertyAnimation {
	id: animation;
	targets: [rectA , rectB];
	properties: "width";
	to: 150;
	duration: 1000;
}

target:要操作的对象
property:要改变目标的哪个属性
from:要改变目标的初始值(默认,目标的当前值)
to:指定目标的目标值
duration:动画时间
easing:动画的松弛曲线

在信号处理器中使用
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true

	Rectangle {
		id: rect
		width: 50
		height: 150
		anchors.centerIn: parent
		color: "blue"
		MouseArea {
			anchors.fill: parent
			onClicked: PropertyAnimation {
				target: rect
				properties: "width"
				to: 150
				duration: 1000
			}
		}
	}
}
使用Animation on <property>
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true

	Rectangle {
		id: rect
		width: 50
		height: 150
		anchors.centerIn: parent
		color: "blue"
		MouseArea {
			anchors.fill: parent
			id: mouseArea
		}
		PropertyAnimation on width {
			to: 150
			duration: 1000
			running: mouseArea.pressed
		}
	}
}

如果不设置running属性,则动画对象将立即执行

使用start()和stop()信号
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true

	Rectangle {
		id: rect
		width: 50
		height: 150
		anchors.centerIn: parent
		color: "blue"
		property var animation
		PropertyAnimation {
			id: toSquare
			target: rect
			property: "width"
			to: 150
			duration: 1000
			onStarted: {
				rect.animation = toSquare
				rect.color = "red"
			}
			onStopped: {
				rect.color = "blue"
			}
		}
		PropertyAnimation {
			id: toRect
			target: rect
			property: "width"
			to: 50
			duration: 1000
			onStarted: {
				rect.animation = toRect
				rect.color = "red"
			}
			onStopped: {
				rect.color = "blue"
			}
		}
		MouseArea {
			anchors.fill: parent
			onClicked: {
				if (rect.animation === toRect || rect.animation === undefined) {
					toSquare.start()
				} else {
					toRect.start()
				}
			}
		}
	}
}

10.1.3 NumberAnimation

NumberAnimation是PropertyAnimation的派生类,专门用于处理数字类型的property
NumberAnimation重写了from和to两个属性类型为real,因此动画将会更加精细顺滑

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true

	Rectangle {
		id: rect
		color: "blue"
		width: 50
		height: 50
		x: 0
		y: 95
		MouseArea {
			id: mouseArea
			anchors.fill: parent
			onClicked: {
				animationX.start()
				animationRotation.running = true
				animationRadius.start()
			}
		}
		NumberAnimation {
			id: animationX
			target: rect
			property: "x"
			to: 310
			duration: 3000
			easing.type: Easing.OutCubic
		}
		NumberAnimation {
			id: animationRotation
			target: rect
			property: "rotation"
			to: 1080
			duration: 3000
			running: false
			easing.type: Easing.OutInQuad
		}
		NumberAnimation on radius {
			id: animationRadius
			to: 25
			duration: 3000
			running: false
		}
	}
}
10.1.4 ColorAnimation

ColorAnimation是PropertyAnimation的派生类,专门用于处理Color类型的property
ColorAnimation重写了from和to两个属性类型为color

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true

	Rectangle {
		id: rect
		color: "red"
		width: 60
		height: 60
		radius: 30
		anchors.centerIn: parent
		MouseArea {
			id: mouseArea
			anchors.fill: parent
			onClicked: ColorAnimation {
				target: rect
				property: "color"
				to: "green"
				duration: 3000
			}
		}
	}
}
10.1.5 RotationAnimation

RotationAnimation是PropertyAnimation的派生类,专门处理rotation和angle属性
RotationAnimation重写了from和to两个属性类型为real

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true

	Rectangle {
		id: rect
		color: "red"
		width: 120
		height: 60
		anchors.left: parent.left
		anchors.leftMargin: 20
		anchors.verticalCenter: parent.verticalCenter
		MouseArea {
			anchors.fill: parent
			onClicked: RotationAnimation {
				target: rect
				to: 90
				duration: 1500
				direction: RotationAnimation.Counterclockwise
			}
		}
	}
	Rectangle {
		id: blueRect
		color: "blue"
		width: 120
		height: 60
		anchors.right: parent.right
		anchors.rightMargin: 40
		anchors.verticalCenter: parent.verticalCenter
		transformOrigin: Item.TopRight
		MouseArea {
			anchors.fill: parent
			onClicked: anim.start()
		}
		RotationAnimation {
			id: anim
			target: blueRect
			to: 60
			duration: 1500
		}
	}
}

RotationAnimation新增一个direction属性,指定旋转方向

  • RotationAnimation.Numerical:默认值,在from和to之间进行线性插值旋转
  • RotationAnimation.Clockwise:在两个角度之间顺时间旋转
  • RotationAnimation.Counterclockwise:在两个角度之间逆时针旋转
  • RotationAnimation.Shortest:选取两个角度之间的最短路径旋转

RotationAnimation旋转一个Item时以Item的transformOrigin属性指定的点为中心旋转

使用RotationAnimation时不需要指定property属性,因为是固定的

10.1.6 PathAnimation

PathAnimation是Animation继承而来,它让目标对象沿着一个既定的路径运动

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true

	Canvas {
		anchors.fill: parent
		onPaint: {
			var ctx = getContext("2d")
			ctx.lineWidth = 4
			ctx.strokeStyle = "red"
			ctx.beginPath()
			ctx.arc(200, 0, 160, Math.PI * 2, 0, false)
			ctx.stroke()
		}
		Rectangle {
			id: rect
			width: 40
			height: 40
			color: "blue"
			x: 20
			y: 0
			MouseArea {
				anchors.fill: parent
				id: mouseArea
				onClicked: pathAnim.start()
			}
			PathAnimation {
				id: pathAnim
				target: rect
				duration: 6000
				anchorPoint: "20,20"
				orientationEntryDuration: 200
				orientationExitDuration: 200
				easing.type: Easing.InOutCubic
				orientation: PathAnimation.TopFirst
				path: Path {
					startX: 40
					startY: 0
					PathArc {
						x: 360
						y: 0
						useLargeArc: true
						radiusX: 160
						radiusY: 160
						direction: PathArc.Counterclockwise
					}
				}
			}
		}
	}
}

path:构造的路径
easing:指定动画的松弛曲线
anchorPoint:指定目标对象的那个点锚定在路径上
orienttation:控制目标沿着路径移动的旋转策略

  • PathAnimation.Fixed,orientation的默认值,在运动过程中保持物体方位不旋转。
  • PathAnimation.RightFirst,旋转目标对象时努力使目标对象的右侧贴合路径。
  • PathAnimation.LeftFirst,旋转目标对象时努力使目标对象的左侧贴合路径。
  • PathAnimation.BottomFirst,旋转目标对象时努力使目标对象的底部贴合路径。
  • PathAnimation.TopFirst,旋转目标对象时努力使目标对象的顶部贴合路径。
10.1.7 SmoothedAnimation

SmoothedAnimation是NumberAnimation的派生类
默认将easing.type设置为Easing.InOutQuad,在from和to之间产生平滑的动画效果

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true

	Rectangle {
		id: rect
		width: 80
		height: 60
		x: 20
		y: 20
		color: "red"
	}
	SmoothedAnimation {
		id: smoothX
		target: rect
		property: "x"
		duration: 1000
		velocity: -1
	}
	SmoothedAnimation {
		id: smoothY
		target: rect
		property: "y"
		velocity: 100
	}
	MouseArea {
		anchors.fill: parent
		onClicked: {
			smoothX.from = rect.x
			smoothX.to = mouse.x + 4
			smoothX.start()
			smoothY.from = rect.y
			smoothY.to = mouse.y + 4
			smoothY.start()
		}
	}
}

easing.type:默认设置为Easing.InOutQuad
duration:动画周期,单位毫秒,默认为-1
velocity:速率,默认速率200units/秒,设置为-1时禁用速率,如果from和to之间很小,则自行调整

durationvelocity同时设置时,自动根据from、to之间举例和速率计算动画完成所需时间,用这个时间和duration比较,如果duration段,则用duration,否则使用velocity

10.1.8 SpringAnimation

SpringAnimation用于模仿弹簧的震荡效果

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true

	Rectangle {
		id: rect
		width: 40
		height: 40
		x: 20
		y: 20
		color: "red"
	}
	SpringAnimation {
		id: springX
		target: rect
		property: "x"
		spring: 3
		damping: 0.06
		epsilon: 0.25
	}
	SpringAnimation {
		id: springY
		target: rect
		property: "y"
		spring: 3
		damping: 0.06
		epsilon: 0.25
	}
	MouseArea {
		anchors.fill: parent
		onClicked: {
			springX.from = rect.x
			springX.to = mouse.x - 20
			springX.start()
			springY.from = rect.y
			springY.to = mouse.y - 20
			springY.start()
		}
	}
}

spring:控制动画的加速度,默认0,0-5.0之间取值
damping:衰减系数,值越大震荡越快平复,默认0,0-1.0之间取值
epsilon:设定一个最接近0的阈值来代替0,默认0.01,调整可带来性能提升
velocity:设定动画的最大速率,默认值为0,没有限制

10.2 组合动画

组合动画包括:ParallelAnimation和SequentialAnimation

10.2.1 ParallelAnimation

ParallelAnimation允许将多个动画元素组合在一起执行
ParallelAnimation中定义的多个动画将并行执行
ParallelAnimation从Animation继承而来,没有额外的属性,本身单独使用没有意义

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true

	Rectangle {
		id: rect
		color: "blue"
		width: 50
		height: 50
		x: 0
		y: 95
		MouseArea {
			id: mouseArea
			anchors.fill: parent
			onClicked: {
				if (anim.paused) {
					anim.resume()
				} else if (anim.running) {
					anim.pause()
				} else {
					anim.start()
				}
			}
		}
		ParallelAnimation {
			id: anim
			loops: Animation.Infinite
			NumberAnimation {
				target: rect
				property: "x"
				to: 310
				duration: 3000
			}
			NumberAnimation {
				target: rect
				property: "rotation"
				to: 360
				duration: 1000
				loops: 3
			}
			NumberAnimation {
				target: rect
				property: "radius"
				to: 25
				duration: 3000
			}
		}
	}
}
10.2.2 SequentialAnimation

SequentialAnimation中定义的动画将顺序执行

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true

	Rectangle {
		id: rect
		color: "blue"
		width: 50
		height: 50
		x: 0
		y: 95
		MouseArea {
			id: mouseArea
			anchors.fill: parent
			onClicked: {
				if (anim.paused) {
					anim.resume()
				} else if (anim.running) {
					anim.pause()
				} else {
					rect.radius = 0
					rect.x = 0
					rect.rotation = 0
					anim.start()
				}
			}
		}
		SequentialAnimation {
			id: anim
			NumberAnimation {
				target: rect
				property: "x"
				to: 310
				duration: 3000
			}
			NumberAnimation {
				target: rect
				property: "rotation"
				to: 360
				duration: 1000
				loops: 3
			}
			NumberAnimation {
				target: rect
				property: "radius"
				to: 25
				duration: 3000
			}
		}
	}
}

10.3 State

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true
	color: "#EEEEEE"

	Text {
		id: centerText
		text: "A Single Text."
		anchors.centerIn: parent
		font.pixelSize: 24
		MouseArea {
			id: mouseArea
			anchors.fill: parent
			onReleased: {
				centerText.state = "redText"
			}
		}
		states: [
			State {
				name: "redText"
				changes: [
					PropertyChanges {
						target: centerText
						color: "red"
					}
				]
			},
			State {
				name: "blueText"
				when: mouseArea.pressed
				PropertyChanges {
					target: centerText
					color: "blue"
					font.bold: true
					font.pixelSize: 32
				}
			}
		]
		state: "redText"
	}
}

Item有一个state属性,字符串,默认是空串
Item还有一个states属性,list<State>,保存为这个Item定义的所有的状态

应用一种State的两种方式:

  1. 显式改变Item的state属性
  2. 将State的when属性绑定到一个表达式上

State类型:
name:字符串,保存状态的名字
when:布尔值,描述状态在什么时候应用,一般绑定在ECMAScript表达式上,true时应用
extened:字符串,指向当前状态的"基类"的名字
changes:list<Change>,保存应用于这种状态的所有变化,当进入一种State后,这个列表中的Change对象会依次顺序执行

Change对象的类型有以下几种:

  • PropertyChanges,用来改变一个对象的属性,对应的C++类为QQuickPropertyChanges,是- QQuickStateOperation的派生类。
  • ParentChange,用来改变一个对象的父,对应的C++类为QQuickParentChange,是QQuickStateOperation的派生类。
  • AnchorChanges,用来改变一个对象的锚布局参数,对应的C++类为QQuickAnchorChanges,是QQuickStateOperation的派生类。
  • StateChangeScript,用来执行一个ECMAScript脚本,对应的C++ 类 为 QQuickStateChangeScript , 是QQuickStateOperation的派生类。
10.3.1 PropertyChanges
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
	width: 360
	height: 360
	visible: true
	color: "#EEEEEE"

	Rectangle {
		id: rect
		color: "blue"
		width: 200
		height: 200
		anchors.centerIn: parent
		MouseArea {
			id: mouseArea
			anchors.fill: parent
		}
		states: [
			State {
				name: "resetwidth"
				when: mouseArea.pressed
				PropertyChanges {
					target: rect
					restoreEntryValues: false
					color: "red"
					width: parent.width
				}
			}
		]
	}
}

target:指向要改变的的目标对象
restoreEntryValues:布尔值,离开本状态时,是否将本状态改变的那些属性的值重置为进入本状态之前的值,默认true
explicit:如果设定目标属性时,使用表达式,是否将表达式视为一次性的赋值行为,还是每次都计算表达式。默认false,每次都计算

10.3.2 ParentChange

ParentChange用于改变一个对象的parent

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Rectangle {
	width: 360
	height: 360
	visible: true
	color: "#EEEEEE"
	id: "rootItem"

	Rectangle {
		id: blueRect
		width: 200
		height: 200
		color: "blue"
		x: 8
		y: 8
	}
	Rectangle {
		id: redRect
		color: "red"
		width: 100
		height: 100
		x: blueRect.x + blueRect.width + 8
		y: blueRect.y
		MouseArea {
			id: mouseArea
			anchors.fill: parent
			onClicked: {
				if (redRect.state == "" || redRect.state == "default") {
					redRect.state = "reparent"
				} else {
					redRect.state = "default"
				}
			}
		}
		states: [
			State {
				name: "reparent"
				ParentChange {
					target: redRect
					parent: blueRect
					width: 50
					height: 50
					x: 30
					y: 30
					rotation: 45
				}
			},
			State {
				name: "default"
				ParentChange {
					target: redRect
					parent: rootItem
					width: 100
					height: 100
					x: blueRect.x + blueRect.width + 8
					y: blueRect.y
				}
			}
		]
	}
}

target,指定要操作的目标对象
parent,指定目标对象的新parent
x,指定目标对象相对于新parent的x 位置
y,指定目标对象相对于新parent的y 位置
width,指定目标对象的宽度
height,指定目标对象的高度
rotation,指定目标对象的旋转角度
scale,指定目标对象的放大系数

10.3.3 AnchorChanges

AnchorChanges用于改变一个Item的锚布局属性

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Rectangle {
	id: rootItem
	width: 360
	height: 360
	visible: true
	color: "#EEEEEE"

	Rectangle {
		id: blueRect
		width: 200
		height: 180
		color: "blue"
		x: 8
		y: 8
	}
	Rectangle {
		id: redRect
		color: "red"
		width: 100
		height: 100
		anchors.left: blueRect.right
		anchors.leftMargin: 10
		anchors.top: blueRect.top
		MouseArea {
			id: mouseArea
			anchors.fill: parent
			onClicked: {
				if (redRect.state == "" || redRect.state == "default") {
					redRect.state = "reanchor"
				} else {
					redRect.state = "default"
				}
			}
		}
		states: [
			State {
				name: "reanchor"
				changes: [
					AnchorChanges {
						target: redRect
						anchors.top: blueRect.bottom
						anchors.left: rootItem.left
					},
					PropertyChanges {
						target: redRect
						height: 40
						anchors.topMargin: 4
					}
				]
			},
			State {
				name: "default"
				AnchorChanges {
					target: redRect
					anchors.left: blueRect.right
					anchors.top: blueRect.top
				}
			}
		]
	}
}

target,指向目标对象。
anchors.left
anchors.right
anchors.top
anchors.bottom
anchors.horizontalCenter
anchors.verticalCenter
anchors.baseline

10.3.4 StateChangeScript

StateChangeScript用于在状态改变时执行ECMAScript脚本
<colorMaker.js>

function changeColor(obj){
	obj.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1.0);
}

<main.qml>

import QtQuick 2.15
import QtQuick.Window 2.15
import "colorMaker.js" as ColorMaker

Window {
	width: 360
	height: 240
	color: "#EEEEEE"
	visible: true
	id: rootItem

	Rectangle {
		id: colorRect
		color: "red"
		width: 150
		height: 130
		anchors.centerIn: parent
		MouseArea {
			id: mouseArea
			anchors.fill: parent
		}
		states: [
			State {
				name: "default"
				when: mouseArea.pressed
				StateChangeScript {
					name: "changeColor"
					script: ColorMaker.changeColor(colorRect)
				}
			}
		]
	}
}

name:脚本名称,可以被ScriptAction对象引用,以便服复用代码
script:实际的脚本代码

10.4 Transition

Item的Transition属性是一个列表,保存为这个Item定义的所有Transition
当一个Item从一个State切换到另一个State时,Transition定义的动画会自动在两个State之间运行,从而使得State状态切换的更加平滑

//举例如下:
Item {
...
transitions: [
	Transition {
		from: "stateA";
		to: "stateB";
		NumberAnimation { 
			properties: "x,y"; 
			duration: 2000; 
		}
	},
	Transition {
		from: "stateB";
		to: "stateA";
		NumberAnimation {
			properties: "x,y"; 
			duration: 2000; 
		}
	}
]
...
}

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
	width: 360
	height: 240
	color: "#EEEEEE"
	visible: true
	id: rootItem

	Rectangle {
		id: rect
		color: "gray"
		width: 50
		height: 50
		anchors.centerIn: parent
		MouseArea {
			id: mouseArea
			anchors.fill: parent
		}
		states: State {
			name: "pressed"
			when: mouseArea.pressed
			PropertyChanges {
				target: rect
				color: "green"
				scale: 2.0
			}
		}
		//begin, 增加过渡动画
		transitions: Transition {
			NumberAnimation {
				property: "scale"
				easing.type: Easing.InOutQuad
				duration: 1000
			}
			ColorAnimation {
				duration: 600
			}
		}
		//end, 增加过渡动画
	}
}

Transition的属性及成员函数如下:
from:用来指定触发过度状态名称,默认值为"“,匹配所有状态
to:用来指定过滤的目标状态名称,默认值为”
",匹配所有状态
running:这个Transition是否正在运行
animations:列表,保存为一个Transition定义的所有Animations
reversible:指定触发Transition的条件反转时Transition是否自动反转,默认false

10.4.1 Transition综合示例
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
	width: 360
	height: 240
	color: "#EEEEEE"
	visible: true
	id: rootItem

	Text {
		id: linkText
		text: "I\'m web link."
		anchors.centerIn: parent
		font.pixelSize: 24
		property var hadClicked: false
		MouseArea {
			id: mouseArea
			anchors.fill: parent
			hoverEnabled: true
			onEntered: {
				linkText.state = linkText.hadClicked == true ? "clickedHover" : "hover"
			}
			onExited: {
				linkText.state = linkText.hadClicked == true ? "clicked" : "initial"
			}
			onClicked: {
				if (linkText.hadClicked == false) {
					linkText.hadClicked = true
				}
				linkText.state = "clicked"
			}
		}
		states: [
			State {
				name: "initial"
				changes: [
					PropertyChanges {
						target: linkText
						color: "blue"
					}
				]
			},
			State {
				name: "hover"
				PropertyChanges {
					target: linkText
					color: "#87CEFA"
					font {
						italic: true
						pixelSize: 36
						underline: true
					}
				}
			},
			State {
				name: "clicked"
				PropertyChanges {
					target: linkText
					color: "#8B4513"
					font {
						pixelSize: 24
					}
				}
			},
			State {
				name: "clickedHover"
				PropertyChanges {
					target: linkText
					color: "#D2691E"
					font {
						italic: true
						pixelSize: 36
						underline: true
					}
				}
			}
		]
		state: "initial"
		transitions: [
			Transition {
				from: "initial"
				to: "hover"
				reversible: true
				NumberAnimation {
					property: "font.pixelSize"
					duration: 800
				}
				ColorAnimation {
					duration: 800
				}
			},
			Transition {
				from: "hover"
				to: "clicked"
				NumberAnimation {
					property: "font.pixelSize"
					duration: 800
				}
				ColorAnimation {
					duration: 800
				}
			},
			Transition {
				from: "clicked"
				to: "clickedHover"
				reversible: true
				SequentialAnimation {
					NumberAnimation {
						property: "font.pixelSize"
						duration: 800
					}
					ColorAnimation {
						duration: 800
					}
				}
			}
		]
	}
}

10.5 协同动画元素

Behavior对象用于给Item的某个属性绑定默认动画
ParentAnimation 、 AnchorAnimation通 常 需要和 Transition 、State联合使用
PauseAnimation可以插入在多个动画之间产生暂停效果
PropertyAction可以插入在多个动画之间来立即改变某个属性
ScriptAction用于在动画执行过程中运行一段ECMAScript脚本

10.5.1 Behavior
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
	width: 360
	height: 240
	color: "#EEEEEE"
	visible: true
	id: rootItem

	Rectangle {
		id: rect
		width: 160
		height: 100
		color: "red"
		anchors.centerIn: parent
		Behavior on width {
			NumberAnimation {
				duration: 1000
			}
		}
		Behavior on height {
			NumberAnimation {
				duration: 1000
				easing.type: Easing.InCubic
			}
		}
		MouseArea {
			anchors.fill: parent
			onClicked: {
				rect.width = Math.random() * rootItem.width
				rect.height = Math.min(Math.random() * rootItem.height, rect.height * 1.5)
			}
		}
	}
}

10.5.2 ParentAnimation

ParentAnimation是Animation的派生类
ParentAnimation在改变一个Item的parent时使用,使得旧parent移动到新parent的过程更平滑

import QtQuick 2.2

Rectangle {
	width: 360
	height: 240
	color: "#EEEEEE"
	id: rootItem
	Rectangle {
		id: blueRect
		width: 200
		height: 200
		color: "blue"
		x: 8
		y: 8
	}
	Rectangle {
		id: redRect
		color: "red"
		state: "default"
		MouseArea {
			id: mouseArea
			anchors.fill: parent
			onClicked: {
				if (redRect.state == "" || redRect.state == "default") {
					redRect.state = "reparent"
				} else {
					redRect.state = "default"
				}
			}
		}
		states: [
			State {
				name: "reparent"
				ParentChange {
					target: redRect
					parent: blueRect
					width: 50
					height: 50
					x: 30
					y: 30
					rotation: 45
				}
			},
			State {
				name: "default"
				ParentChange {
					target: redRect
					parent: rootItem
					width: 100
					height: 100
					x: blueRect.x + blueRect.width + 8
					y: blueRect.y
				}
			}
		]
		transitions: Transition {
			ParentAnimation {
				NumberAnimation {
					properties: "x,y"
					duration: 1000
				}
			}
		}
	}
}

newParent:指定目标对象的新parent
target:指定目标对象
via:动画过程中参考的其他对象

10.5.3 AnchorAnimation

AnchorAnimation只能和Transition、AnchorChanges联合使用,不能在Behavior或其他动画元素中用

import QtQuick 2.2

Rectangle {
	width: 360
	height: 240
	color: "#EEEEEE"
	id: rootItem
	Rectangle {
		id: blueRect
		width: 200
		height: 180
		color: "blue"
		x: 8
		y: 8
	}
	Rectangle {
		id: redRect
		color: "red"
		width: 100
		height: 100
		anchors.leftMargin: 10
		MouseArea {
			id: mouseArea
			anchors.fill: parent
			onClicked: {
				if (redRect.state == "" || redRect.state == "default") {
					redRect.state = "reanchor"
				} else {
					redRect.state = "default"
				}
			}
		}
		states: [
			State {
				name: "reanchor"
				changes: [
					AnchorChanges {
						target: redRect
						anchors.top: blueRect.bottom
						anchors.left: rootItem.left
					},
					PropertyChanges {
						target: redRect
						height: 40
						anchors.topMargin: 4
					}
				]
			},
			State {
				name: "default"
				AnchorChanges {
					target: redRect
					anchors.left: blueRect.right
					anchors.top: blueRect.top
				}
			}
		]
		state: "default"
		transitions: Transition {
			AnchorAnimation {
				duration: 1000
				easing.type: Easing.OutInCubic
			}
		}
	}
}

使用AnchorAnimation可以设定duration、easing、targets属性,和Transition结合使用时,无需target属性