動畫效果
來源地址: https://uimovement.com/media/resource_image/image_5213.gif.mp4
下圖是我仿製的動畫:
實現思路
動畫的實現
- 鎖圖標由白色變成了黑色. 鎖的圖標我們可以通過 Image 對象加載. 白色變黑色則通過附加在 Image 上的 ColorOverlay 實現.
- 密碼由星號變成明文. 爲了讓變化自然, 我們對星號漸隱, 明文漸入轉換.
- 白色矩形覆蓋填充到整個按鈕. 用 QML 來實現是比較簡單的, 就是一個屬性動畫. 我們對白色矩形 (初始時的圓形) 的各個子屬性添加屬性動畫, 指定它末狀態的值就能做出來.
- 眨眼動畫. 這個仔細看不是簡單的兩張靜態圖的切換, 我沒有找到合適的素材, 所以就用 gif 錄製工具把原版的眨眼截取下來了 (如下圖所示), 再把 gif 套到一個白色圓形中, 遮掩一下它方正的邊緣.
QML 佈局結構
對 QML 的組織採用面向對象的思想. 把每一個組件看作是對象, 組件之間嵌套組合構成了完整的 UI.
Window
Password
LockIcon
PwdText
EyesBlink
代碼及註釋
說明
- 詳細的說明以通過註釋的形式給出.
- 代碼中的屬性和變量命名風格做了自定義, 相關閱讀見此 (TODO).
- 深入瞭解 QML 對象的使用, 請查閱 Qt 助手工具.
目錄結構
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 個星號.
* 激活狀態: 明文顯示, 這裏用 "<this is the real password>" 簡單代替.
* 動畫:
* 密碼文字由不透明轉爲透明, 再由透明轉爲不透明. 當完全透明的那一刻, 迅
* 速將密碼文字由密文切換爲明文.
* 簡單來說就是密文隱去, 隨之明文漸現.
*/
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 = "<this is the real password>"
}
}
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