来源地址: https://uimovement.com/media/resource_image/image_5213.gif.mp4
下图是我仿制的动画:
对 QML 的组织采用面向对象的思想. 把每一个组件看作是对象, 组件之间嵌套组合构成了完整的 UI.
Window
Password
LockIcon
PwdText
EyesBlink
说明
demo
|- ui
|- Main.qml # 这里是 ui 的主入口.
|- Password
|- Main.qml # 这里是 Password 组件的主入口, 将被加载到 ui/Main.qml 中.
| # 下面的 LockIcon.qml, PwdText.qml, EyesBlink.qml 则会被本
| # 文件引用.
|- LockIcon.qml
|- PwdText.qml
|- EyesBlink.qml
|- icon
|- lock-white.png
|- eyes-blink.gif
|- main.py
// === ui/Main.qml ===
import QtQuick.Window 2.14
import "./Password" as Password
Window {
color: "#EDEDED"
visible: true
width: 1200; height: 600
Password.Main {
id: _pwd
anchors.centerIn: parent
}
}
// === ui/Password/Main.qml ===
import Qt3D.Animation 2.14 // 动画模块
import QtQuick 2.14
Rectangle { // ui/Password/Main.qml 是密码框的主体. 在其内引用其他子组件文件.
id: _root
// 设置一个深蓝色的长条状的矩形作为密码框的主体.
color: "#172336"
radius: 24
width: 480; height: 80
// 声明两个自定义的变量.
property bool p_active: false // 是否处于激活状态. 默认为 false. 只有当密码框被点击时才会变成 true.
property int p_duration: 5000 // 动画的时长. 5000ms 的慢动作是为了便于调试时观察; 正式结果将改为 500ms.
// 定义激活时的状态. 在这里我们只定义了白色遮罩的激活状态 (也就是末状态). 其他组件 (锁, 密码文字, 眼睛) 则在各自的 qml 文件中定义, 不在这里写.
states: [
State {
when: p_active // 监听 p_active 变量, 当值为 true 时此状态被激发.
PropertyChanges { // 定义末状态的属性和值.
target: _rect_mask // 目标对象是白色遮罩的 id.
anchors.margins: 0 // 边距调为 0.
width: _root.width; height: _root.height; radius: _root.radius // 宽, 高, 弧度变为根对象的值.
x: _root.x; y: _root.y // 坐标 (左上角顶点的坐标) 也变为根对象的值.
}
}
]
// 当 states 列表的任意一个状态被激发时, transitions 就会因此产生动画效果.
transitions: [
Transition {
// 我们定义一个数字类型的属性动画. 因为宽, 高, 弧度等值都是数字类型的.
NumberAnimation {
target: _rect_mask
duration: p_duration // 动画时长. 就是我们刚才定义的 5000ms.
easing.type: Easing.OutQuart // 为了让动画看起来自然, 我们使用非线性插值器. Easing.OutQuart 的效果是开始时快, 结束时非常缓慢, 适合表现飞入视界并获取焦点的效果.
properties: "anchors.margins,height,radius,width,x,y" // 指定白色遮罩对象的这些属性发生变化.
}
}
]
// 白色遮罩. 始状态是一个圆形, 位于密码框的右侧.
Rectangle {
id: _rect_mask
// 对齐: 对齐到根对象右侧, 边距为 24px, 与根对象垂直居中.
anchors.margins: 24
anchors.right: _root.right
anchors.verticalCenter: _root.verticalCenter
color: "white"
width: 48; height: 48; radius: 24 // 注意看这里, 当 width == height 且 radius == 1/2 width 时, 矩形就是一个圆形.
// 绑定点击区域.
MouseArea {
anchors.fill: _rect_mask
onClicked: {
p_active = true // 当白色遮罩被点击时, p_active 变为 true. 这时候我们再去看 states. State 的 when 属性会自动监听到这个变化, 并激发这个状态, 从而引起 transitions 动画生效, 整个动画开始发生.
}
}
}
// 右侧的眨眼动画. 因为这个 gif 是方形的, 所以和白色遮罩叠在一起, 把方形边缘遮住.
EyesBlink {
id: _eye
// 这个对齐值是反复调整出来的. 最终要的效果是: 看起来要比白色遮罩小, 不能把方形边缘漏出来, 还要看起来位于其中心.
anchors.right: _root.right
anchors.rightMargin: 34
anchors.top: _lock.top
anchors.topMargin: 4
width: 28; height: 28
// 把根对象的 p_active 绑定到动画播放属性上. 这样点击时才会播放眨眼动作.
p_active: _root.p_active
speed: 4 // 注意 EyesBlink 的动画时长不遵循 p_duration, 而是其 gif 文件的时长除以 speed. speed 默认为 1, 这里被我设置成了 4, 为了看起来更快一点.
}
// 锁图标的组件. 这里只覆写了锚点, 尺寸和变量属性. 详见 ui/Password/LockIcon.qml.
LockIcon {
id: _lock
anchors.left: _root.left
anchors.margins: 24
anchors.verticalCenter: _root.verticalCenter
p_active: _root.p_active
p_duration: _root.p_duration
obj_Image {
width: 32; height: 32
}
}
// 密码文字的组件. 这里只覆写了锚点和变量属性. 详见 ui/Password/PwdText.qml.
PwdText {
anchors.left: _lock.right
anchors.leftMargin: 12
anchors.verticalCenter: _root.verticalCenter
p_active: _root.p_active
p_duration: _root.p_duration
}
}
// === ui/Password/LockIcon.qml ===
import QtGraphicalEffects 1.14 // 用于制作 ColorOverlay
import QtQuick 2.14
Item {
width: _icon.width; height: _icon.height
property alias obj_Image: _icon // 将子对象图标暴露给外部. 从而使父级可以引用 (因为我们想在父级定义它的宽度和高度).
property bool p_active: false // 激活状态. 默认为 false. 同样被父级定义, 此属性会被绑定到父级的 p_active 属性上.
property int p_duration: 0 // 动画时长. 同样被父级定义.
// 透明背景的锁形图标.
Image {
id: _icon
source: "../../icon/lock-white.png"
}
// 由于锁图标是白色的, 我们需要在 p_active = true 状态将它变成黑色, 所以使用 ColorOverlay 实现.
// ColorOverlay 可以覆盖目标对象的颜色, 并且我们还可以对 ColorOverlay 的 color 属性绑定一个过渡动画.
ColorOverlay {
id: _overlay
source: _icon
anchors.fill: _icon
//color: "white"
}
// 定义默认状态和激活状态的 ColorOverlay.
states: [
State {
name: "defaultState"
when: !p_active
PropertyChanges {
target: _overlay
color: "white"
}
},
State {
name: "activeState"
when: p_active
PropertyChanges {
target: _overlay
color: "black"
}
}
]
// 当状态发生变化时, Transition 会被自动触发, 实现动画过程.
transitions: [
Transition {
ColorAnimation {
target: _overlay
duration: p_duration
}
}
]
}
// === ui/Password/PwdText.qml ===
import QtQuick 2.14
Text {
/* 密码文字存在两种状态:
* 默认状态: 密文显示. 以星号 (*) 显示, 密文的长度是 12 个星号.
* 激活状态: 明文显示, 这里用 "" 简单代替.
* 动画:
* 密码文字由不透明转为透明, 再由透明转为不透明. 当完全透明的那一刻, 迅
* 速将密码文字由密文切换为明文.
* 简单来说就是密文隐去, 随之明文渐现.
*/
id: _root
color: "#585DC5"
font.pixelSize: 24
opacity: 1 // 不透明度. 数值从 0.0 到 1.0. (1.0 为完全显示.)
text: "* ".repeat(p_pwdLength) // 为了让星号之间空隙大一点, 我夹了空格.
property bool p_active: false // 激活状态. 默认为 false. 同样被父级定义, 此属性会被绑定到父级的 p_active 属性上.
property int p_duration: 0 // 动画时长. 同样被父级定义.
property int p_pwdLength: 12
// 我们监听 opacity 属性的变化, 当 opacity 变化时, 此信号会被自动触发.
onOpacityChanged: {
// 进入一个判断逻辑: 当完全透明且处于 p_active 状态, 则将 text 变成明文.
if (opacity == 0 && p_active) {
text = ""
}
}
states: [
State {
name: "showPwdInPlainText"
when: p_active
PropertyChanges {
target: _root
opacity: 1
}
}
]
transitions: [
Transition { // 使用序列动画. 前 20% 时间是渐隐动画, 后 80% 时间是渐入动画.
SequentialAnimation {
NumberAnimation {
duration: p_duration * 0.2
easing.type: Easing.OutQuart
properties: "opacity"
from: 1; to: 0
} // 注意这里不要有逗号.
NumberAnimation {
duration: p_duration * 0.8
easing.type: Easing.OutQuart
properties: "opacity"
from: 0; to: 1
}
}
}
]
}
// === ui/Password/EyesBlink.qml ===
import QtQuick 2.14
AnimatedImage { // AnimatedImage 对象专用于加载可动的图像. 详见 Qt 助手 QML > AnimatedImage.
id: _img
playing: p_active // 初始化载入时, gif 动画设为暂停状态.
source: "../../icon/eyes-blink.gif"
property bool p_active: false // 激活状态. 默认为 false. 同样被父级定义, 此属性会被绑定到父级的 p_active 属性上.
// 当 AnimatedImage 播放时, 其 currentFrame 会发生变化, 此信号的内置监听方法 onCurrentFrameChanged 会被自动触发.
// 我们判断当 currentFrame 播放到最后一帧时, 停止动画, 以免陷入循环播放.
onCurrentFrameChanged: {
if (currentFrame == frameCount - 1) { // 因为 currentFrame 是从 0 开始数的, 所以这里要减一.
playing = false
// paused = true
}
}
}
最后是 main.py 代码:
# === main.py ===
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtWidgets import QApplication
app = QApplication()
engine = QQmlApplicationEngine('./ui/Main.qml')
app.exec_()
源码及图标文件以打包, 下载链接见此: https://lanzous.com/ica5cla