之前我們已經學習了Image、Layout佈局、MouseArea、Button、GroupBox、FileDialog等控制元件.
所以本章綜合之前的每章的知識點,來做一個圖片瀏覽器,使用的Qt版本為Qt5.12
1.圖片瀏覽器介紹
該示例使用了兩個自定義控制元件:
- DynamicGroupBox (路徑:https://www.cnblogs.com/lifexy/p/14751099.html)
- DynamicBtn (路徑:https://www.cnblogs.com/lifexy/p/14671855.html)
介面截圖如下所示:
效果如下所示(動圖有點大,載入有點久):
快捷鍵說明:
- 如果滑鼠位於大圖瀏覽區,則可以通過滑鼠滑輪來放大縮小圖片,通過ctrl+滑輪則可以進行旋轉圖片,通過滑鼠左鍵按下則可以隨意拖動圖片
- 如果滑鼠位於多個圖片瀏覽區(最下面一排的圖片那裡),則可以通過滑鼠滑輪來進行切換上一張和下一張
2.程式碼介紹
- flick : 用來存放放置當前大圖的一個Flickable容器
- photoImage : 用來顯示當前大圖的一個Image
- fileGroup : 檔案選項組合框,裡面有"開啟檔案"、"上一張"、"下一張"按鈕
- ctrlGroup : 圖片控制組合框,裡面有"放大"、"旋轉"滑動條
- imageInfoGroup: 基本資訊組合框,裡面有"尺寸"、"路徑"文字
- authorInfoGroup: 關於組合框,裡面有筆者資訊
- images: 存放使用者開啟的所有圖片的瀏覽區
程式碼如下所示:
import QtQuick 2.14
import QtQuick.Window 2.0
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.14
import Qt.labs.platform 1.1
import QtGraphicalEffects 1.14
Window {
visible: true;
width: 1180
height: 770
minimumWidth: 1050
minimumHeight: 680
color: "#232324"
title: "圖片瀏覽器"
property string picturesLocation : "";
property var imageNameFilters : ["所有圖片格式 (*.png; *.jpg; *.bmp; *.gif; *.jpeg)"];
property var pictureList : []
property var pictureIndex : 0
property var scaleMax : 800 // 最大800%
property var scaleMin : 10 // 最小10%
property var titleColor : "#E8E8E8"
property var contentColor : "#D7D7D7"
property var ctrlSliderList : [
["放大", scaleMin, scaleMax , photoImage.scale * 100 , "%"],
["旋轉", -180, 180 , photoImage.rotation, "°"],
]
FileDialog {
id: fileDialog
title: "請開啟圖片(可以多選)"
fileMode: FileDialog.OpenFiles
folder: picturesLocation
nameFilters: imageNameFilters
onAccepted: {
pictureList = files
openNewImage(0)
}
onFolderChanged: picturesLocation = folder
}
ColumnLayout {
anchors.fill: parent
spacing: 2
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true
spacing: 1
Flickable { // 圖片瀏覽區
id: flick
Layout.fillHeight: true
Layout.fillWidth: true
MouseArea { // 設定滑輪效果
anchors.fill: parent
onWheel: {
if (wheel.modifiers & Qt.ControlModifier) { // ctrl + 滑輪 則進行旋轉圖片
photoImage.rotation += wheel.angleDelta.y / 120 * 5;
if (photoImage.rotation > 180)
photoImage.rotation = 180
else if (photoImage.rotation < -180)
photoImage.rotation = -180
if (Math.abs(photoImage.rotation) < 4) // 如果絕對值小於4°,則擺正圖片
photoImage.rotation = 0;
} else {
photoImage.scale += photoImage.scale * wheel.angleDelta.y / 120 / 10;
if (photoImage.scale > scaleMax / 100)
photoImage.scale = scaleMax / 100
else if (photoImage.scale < scaleMin / 100)
photoImage.scale = scaleMin / 100
}
}
}
Image {
id: photoImage
fillMode: Image.Pad
source: (typeof pictureList[pictureIndex] === 'undefined') ? "" : pictureList[pictureIndex]
smooth: true
mipmap: true
antialiasing: true
Component.onCompleted: {
x = parent.width / 2 - width / 2
y = parent.height / 2 - height / 2
pictureList.length = 0
}
PinchArea {
anchors.fill: parent
pinch.target: parent
pinch.minimumRotation: -180 // 設定拿捏旋轉圖片最大最小比例
pinch.maximumRotation: 180
pinch.minimumScale: 0.1 // 設定拿捏縮放圖片最小最大比例
pinch.maximumScale: 10
pinch.dragAxis: Pinch.XAndYAxis
}
MouseArea { // 設定拖動效果
anchors.fill: parent
drag.target: parent
drag.axis: Drag.XAndYAxis
drag.minimumX: 20 - photoImage.width
drag.maximumX: flick.width - 20
drag.minimumY: 20 - photoImage.height
drag.maximumY: flick.height - 20
}
}
}
Rectangle {
Layout.fillHeight: true
Layout.fillWidth: false
Layout.preferredWidth : 220
color: "#313131"
DynamicGroupBox {
id: fileGroup
title: "檔案選項"
width: parent.width
ColumnLayout {
anchors.centerIn: parent
spacing: 12
Repeater {
model : ListModel {
id: fileModel
ListElement { name: "開啟檔案"; }
ListElement { name: "上一張"; }
ListElement { name: "下一張"; }
}
DynamicBtn {
text: fileModel.get(index).name
backColor: "#3A3A3A"
fontColor: contentColor
fontPixelSize: 14
onPressed: fileGroupPressed(index)
}
}
}
Component.onCompleted: initGroupBox(this);
}
DynamicGroupBox {
id: ctrlGroup
title: "圖片控制"
width: parent.width
anchors.top: fileGroup.bottom
ColumnLayout {
anchors.centerIn: parent
spacing: 12
Repeater {
model : 2
RowLayout {
width: parent.width
Text {
color: contentColor
Layout.fillWidth: false
Layout.preferredWidth : 50
text: ctrlSliderList[index][0]
horizontalAlignment: Text.AlignRight
font.pixelSize: 14
}
DynamicSlider {
id: ctrlSlider
Layout.fillWidth: true
Layout.preferredWidth : 130
from: ctrlSliderList[index][1]
value: ctrlSliderList[index][3]
to: ctrlSliderList[index][2]
stepSize: 1
onMoved: setCtrlValue(index, value);
}
Text {
color: "#D4D4D4"
Layout.fillWidth: false
Layout.preferredWidth : 40
text: parseInt(ctrlSliderList[index][3].toString()) + ctrlSliderList[index][4]
}
}
}
}
Component.onCompleted: initGroupBox(this);
}
DynamicGroupBox {
id: imageInfoGroup
title: "基本資訊"
width: parent.width
height: 120
anchors.top: ctrlGroup.bottom
ColumnLayout {
width: parent.width
spacing: 16
Text {
color: contentColor
text: "尺寸: " + photoImage.sourceSize.width + "X" + photoImage.sourceSize.height
font.pixelSize: 14
}
Text {
color: contentColor
text: "路徑: " + ((typeof pictureList[pictureIndex] === 'undefined') ?
"等待開啟檔案..." : pictureList[pictureIndex].replace("file:///",""))
Layout.preferredWidth: parent.width - 20
Layout.preferredHeight: 60
wrapMode: Text.Wrap
font.pixelSize: 14
}
}
Component.onCompleted: initGroupBox(this);
}
DynamicGroupBox {
id: authorInfoGroup
title: "關於"
width: parent.width
height: 110
anchors.top: imageInfoGroup.bottom
ColumnLayout {
width: parent.width
spacing: 16
Text {
color: contentColor
text: "作者: 諾謙"
Layout.preferredWidth: parent.width - 20
wrapMode: Text.Wrap
font.pixelSize: 14
}
Text {
color: contentColor
text: "部落格: <font color=\"#D4D4D4\"><a href=\"http://www.cnblogs.com/lifexy/\">cnblogs.com/lifexy/</a></font>"
font.pixelSize: 14
onLinkActivated: Qt.openUrlExternally(link)
}
}
Component.onCompleted: initGroupBox(this);
}
}
}
Rectangle {
id: images
Behavior on Layout.preferredHeight { NumberAnimation { duration: 250 } }
Layout.fillHeight: false
Layout.fillWidth: true
Layout.preferredHeight: 130
LinearGradient {
anchors.fill: parent
source: parent
start: Qt.point(0, 0)
end: Qt.point(0, parent.height)
gradient: Gradient {
GradientStop { position: 0.0; color: "#484848" }
GradientStop { position: 0.01; color: "#373737" }
GradientStop { position: 1.0; color: "#2D2D2D" }
}
}
Button {
id: imageCtrlBtn
text: images.Layout.preferredHeight <= 30 ? "展開("+pictureList.length+")" :
"收起("+pictureList.length+")"
anchors.right: parent.right
anchors.rightMargin: 3
z: 100
background: Rectangle {
color: "transparent"
}
contentItem: Label { // 設定文字
id: btnForeground
text: parent.text
font.family: "Microsoft Yahei"
font.pixelSize: 14
color: imageCtrlBtn.hovered ? "#D7D7D7" : "#AEAEAE"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onPressed: {
if (text.indexOf("收起") >= 0) {
images.Layout.preferredHeight = 30
} else {
images.Layout.preferredHeight = 130
}
}
}
ScrollView {
id: imageScroll
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
wheelEnabled: true
WheelHandler {
onWheel: openNewImageAndUpdateScroll(event.angleDelta.y > 0 ? pictureIndex - 1 : pictureIndex + 1)
}
ScrollBar.horizontal.policy: ScrollBar.horizontal.size >= 1.0 ?
ScrollBar.AlwaysOff : ScrollBar.AlwaysOn
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
ScrollBar.horizontal.contentItem: Rectangle {
implicitHeight: 7
implicitWidth: 100
radius: height / 2
color: "#7D7C7C"
visible: images.Layout.preferredHeight <= 30 ? false : true
}
Row {
anchors.fill: parent
anchors.topMargin: 30
spacing: 20
Repeater {
model: pictureList.length
Button {
implicitWidth: 85
implicitHeight: 85
onPressed: openNewImage(index)
background: Rectangle {
color: "#202020"
border.color: pictureIndex == index ? "#2770DF" :
hovered ? "#6C6A6A" : "transparent"
radius: 5
border.width: 3
}
Image {
anchors.fill:parent
anchors.margins: 6
antialiasing: true
fillMode: Image.PreserveAspectFit
source: pictureList[index]
}
}
}
}
}
}
}
function initGroupBox(group) {
group.titleLeftBkColor = "#313131"
group.titleRightBkColor = "#474951"
group.titleColor = titleColor
group.contentBkColor = "#2A2A2A"
group.borderColor = "#454545"
group.titleFontPixel = 14
group.radiusVal = 0
group.borderWidth = 1
}
function fileGroupPressed(index) {
switch (index) {
case 0 : fileDialog.open(); break;
case 1 : openNewImageAndUpdateScroll(pictureIndex - 1); break;
case 2 : openNewImageAndUpdateScroll(pictureIndex + 1); break;
}
}
function setCtrlValue(index, value) {
switch (index) {
case 0 : photoImage.scale = value / 100; break;
case 1 : photoImage.rotation = value; break;
}
}
function openNewImage(index) {
if (index < 0 || index >= pictureList.length) {
}
pictureIndex = index
photoImage.x = flick.width / 2 - photoImage.width / 2
photoImage.y = flick.height / 2 - photoImage.height / 2
photoImage.scale = 1.0
photoImage.rotation = 0
}
function openNewImageAndUpdateScroll(index) {
if (index < 0 || index >= pictureList.length) {
return false
}
pictureIndex = index
photoImage.x = flick.width / 2 - photoImage.width / 2
photoImage.y = flick.height / 2 - photoImage.height / 2
photoImage.scale = 1.0
photoImage.rotation = 0
var scrollLen = 1.0 - imageScroll.ScrollBar.horizontal.size;
if (scrollLen > 0) {
scrollLen = scrollLen * pictureIndex / (pictureList.length - 1)
imageScroll.ScrollBar.horizontal.position = scrollLen
}
return true
}
}