前言

最近在学习qml,发现了一个很好用的库FluentUI,这个库是一个基于qml的Fluent Design风格的UI库,可以实现一些美观的界面。在这里记录一些我学习过程中写的一些小例子。

例子

悬停变色的网格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import FluentUI 1.0

FluWindow {
id: window
property int row: 20
property int column: 20
property var colors: ['#e74c3c', '#8e44ad', '#3498db', '#e67e22', '#2ecc71']

Grid {
anchors.centerIn: parent
rows: window.row
columns: window.column
spacing: 5
Repeater {
model: window.row * window.column
FluRectangle{
id: rect
width: height
height: window.height / window.row / 2
color: "#fff"

// 好像有内存泄露,暂时不用
// FluShadow{
// id: shadow
// radius: 10
// elevation: 0
// }

MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onEntered: {
colorAnimation.stop()
// shadow.elevation = 10
rect.color = window.get_random_color()
}
onExited: {
colorAnimation.start()
// shadow.elevation = 0
}
}

ColorAnimation {
id: colorAnimation
target: rect
property: "color"
to: "#fff"
duration: 1000
running: false
}
}
}
}

function get_random_color() {
return colors[Math.floor(Math.random() * 5)]
}
}

渐变背景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import FluentUI 1.0

FluWindow {
FluFrame {
id: window
width: parent.width * 0.8
height: parent.height * 0.8
anchors.centerIn: parent
clip: true
FluText {
text: "这是一个不停变换的渐变背景"
anchors.centerIn: parent
font: FluTextStyle.Title
z: 1
}

Item {
id: gradientArea
width: window.width
height: window.height * 2
clip: true

Rectangle {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.00; color: "#27ae60" }
GradientStop { position: 0.25; color: "#2980b9" }
GradientStop { position: 0.50; color: "#8e44ad" }
GradientStop { position: 0.75; color: "#f794a4" }
GradientStop { position: 1.00; color: "#f5576c" }
}
}

SequentialAnimation {
running: true
loops: Animation.Infinite

NumberAnimation {
target: gradientArea
property: "y"
from: -(gradientArea.height - window.height)
to: 0
duration: 10000
}

NumberAnimation {
target: gradientArea
property: "y"
from: 0
to: -(gradientArea.height - window.height)
duration: 10000
}
}
}
}
}

不偷看密码的猫头鹰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import FluentUI 1.0

FluWindow{
title: "不偷看密码的猫头鹰"

Rectangle {
id: window
anchors.fill: parent
state: "noInput"
gradient: Gradient {
GradientStop { position: 0.00; color: "#4facfe" }
GradientStop { position: 1.00; color: "#00f2fe" }
}

FluRectangle {
id: owlHandLeft
z: 3
color: "#472d20"
width: 34
height: 34
radius: [17, 17, 17, 17]
transform: [Scale{ yScale: 0.6 }, Translate{ x: (window.width - owlArmLeft.width) / 2 - 80; y: (window.height - owlArmLeft.height) / 2 - 30}]
}

FluImage {
id: owlArmLeft
z: 2
source: "qrc:/res/owl-login-arm.png"
fillMode: Image.PreserveAspectCrop
transform: [Translate{ x: (window.width - owlArmLeft.width) / 2 - 80; y: (window.height - owlArmLeft.height) / 2 - 25}]
}

FluImage {
id: owlHead
anchors.centerIn: parent
source: "qrc:/res/owl-login.png"
transform: [Translate{y: -90}]
}

FluImage {
id: owlArmRight
z: 2
source: "qrc:/res/owl-login-arm.png"
fillMode: Image.PreserveAspectCrop
transform: [Scale{ xScale: -1 }, Translate{ x: (window.width - owlArmLeft.width) / 2 + owlArmLeft.width + 80; y: (window.height - owlArmLeft.height) / 2 - 25}]
}

FluRectangle {
id: owlHandRight
z: 3
color: "#472d20"
width: 34
height: 34
radius: [17, 17, 17, 17]
transform: [Scale{ yScale: 0.6 }, Translate{ x: (window.width - owlArmLeft.width) / 2 + 80; y: (window.height - owlArmLeft.height) / 2 - 30}]
}

Column {
id: content
z: 2
spacing: 10
anchors.top: owlHead.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.centerIn: parent
anchors.topMargin: height / 2 - 5 - spacing

FluTextBox {
id: name
padding: 10
placeholderText: "请输入用户名"
}

FluTextBox {
id: password
padding: 10
placeholderText: "请输入密码"
echoMode: TextInput.Password
onFocusChanged: {
window.state = window.state === "noInput" ? "input" : "noInput"
}
}
}

Column {
id: buttons
spacing: 10
anchors.top: content.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 20

FluFilledButton {
width: 100
text: "登录"
onClicked: {
}
}

FluButton {
width: 100
text: "注册"
onClicked: {
}
}
}

transitions: [
Transition {
NumberAnimation {
targets: [owlHandLeft, owlHandRight]
properties: "x, y, scale, z"
duration: 300
}
NumberAnimation {
targets: [owlArmLeft, owlArmRight]
properties: "x, y, scale, z"
duration: 300
}
}
]

states: [
State {
name: "noInput"
PropertyChanges {
target: owlHandLeft
x: 0; y: 0; scale: 1; z: 3
}
PropertyChanges {
target: owlHandRight
x: 0; y: 0; scale: 1; z: 3
}
PropertyChanges {
target: owlArmLeft
x: 0; y: 0
}
PropertyChanges {
target: owlArmRight
x: 0; y: 0
}
},
State {
name: "input"
PropertyChanges {
target: owlHandLeft
x: 46; y: -20; scale: 0.6; z: 1
}
PropertyChanges {
target: owlHandRight
x: -46; y: -20; scale: 0.6; z: 1
}
PropertyChanges {
target: owlArmLeft
x: 50; y: -35
}
PropertyChanges {
target: owlArmRight
x: -50; y: -35
}
}
]
}
}

跟随鼠标的纸飞机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import FluentUI 1.0

FluWindow {
id: window
title: "始终飞向鼠标的纸飞机"

Rectangle {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.00; color: "#d4fc79" }
GradientStop { position: 1.00; color: "#96e6a1" }
}
}

FluImage {
id: plane
width: 70
height: 70
source: "qrc:/res/plane.svg"
}

ParallelAnimation {
id: moveAnimation
PropertyAnimation {
target: plane
property: "x"
to: mouseArea.mouseX - plane.width / 2
duration: 100
easing.type: Easing.Linear
}
PropertyAnimation {
target: plane
property: "y"
to: mouseArea.mouseY - plane.height / 2
duration: 100
easing.type: Easing.Linear
}
}

MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true

onPositionChanged: {
var targetX = mouseX - plane.width / 2;
var targetY = mouseY - plane.height / 2;
var dx = targetX - plane.x;
var dy = targetY - plane.y;
plane.rotation = Math.atan2(dy, dx) * 180 / Math.PI + 45;
if (!moveAnimation.running) {
moveAnimation.start();
}
}
}
}

滚动特效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import FluentUI 1.0

FluWindow {
id: window
title: "滚动特效"
property var colors: ['#e74c3c', '#8e44ad', '#3498db', '#e67e22', '#2ecc71']

Flickable {
id:flickview
clip: true
anchors.fill: parent
contentWidth: parent.width
contentHeight: column.height
ScrollBar.vertical: FluScrollBar {}

Column {
id: column
anchors.horizontalCenter: parent.horizontalCenter
spacing: 20

Repeater {
model: 40
delegate: FluRectangle {
property bool below: flickview.contentY + window.height < y

width: flickview.width - 200
height: 200
color: get_random_color()
y: index * (height + column.spacing)
scale: below ? 0 : 1
opacity: below ? 0 : 1

Behavior on scale { NumberAnimation { duration: 500 } }
Behavior on opacity { NumberAnimation { duration: 500 } }
}
}
}
}

function get_random_color() {
return colors[Math.floor(Math.random() * 5)]
}
}

波浪小球

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import FluentUI 1.0

FluWindow {
title: "水波效果"

ColumnLayout {
anchors.centerIn: parent
spacing: 50
FluClip {
width: 200
height: 200
radius: [100, 100, 100, 100]
color: "#333"
FluRectangle {
id: waterBall
width: 800
height: 800
radius: [300, 300, 300, 300]
color: "#0078D4"
anchors.horizontalCenter: parent.horizontalCenter
y: 240 - slider.value * 2.4

RotationAnimation {
loops: Animation.Infinite
running: true
target: waterBall
from: 0
to: 360
duration: 5000
}
}
}

FluSlider {
id: slider
Layout.alignment: Qt.AlignHCenter
from: 0
to: 100
stepSize: 1
}
}
}