之前我们已经学习了Image、Layout布局、MouseArea、Button、GroupBox、FileDialog等控件.

所以本章综合之前的每章的知识点,来做一个图片浏览器,笔者使用的Qt版本为Qt5.12

1.图片浏览器介绍

该示例使用了两个自定义控件:

界面截图如下所示:

23.Qt Quick QML-400行实现一个好看的图片浏览器-支持多个图片浏览、缩放、旋转、滑轮切换图片-LMLPHP

 效果如下所示(动图有点大,加载有点久):

23.Qt Quick QML-400行实现一个好看的图片浏览器-支持多个图片浏览、缩放、旋转、滑轮切换图片-LMLPHP

快捷键说明:

  • 如果鼠标位于大图浏览区,则可以通过鼠标滑轮来放大缩小图片,通过ctrl+滑轮则可以进行旋转图片,通过鼠标左键按下则可以随意拖动图片
  • 如果鼠标位于多个图片浏览区(最下面一排的图片那里),则可以通过鼠标滑轮来进行切换上一张和下一张

 

2.代码介绍

  • flick : 用来存放放置当前大图的一个Flickable容器
  • photoImage : 用来显示当前大图的一个Image
  • fileGroup : 文件选项组合框,里面有"打开文件"、"上一张"、"下一张"按钮
  • ctrlGroup : 图片控制组合框,里面有"放大"、"旋转"滑动条
  • imageInfoGroup: 基本信息组合框,里面有"尺寸"、"路径"文本
  • authorInfoGroup: 关于组合框,里面有笔者信息
  • images: 存放用户打开的所有图片的浏览区

代码如下所示:

  1 import QtQuick 2.14
  2 import QtQuick.Window 2.0
  3 import QtQuick.Controls 2.4
  4 import QtQuick.Layouts 1.14
  5 import Qt.labs.platform 1.1
  6 import QtGraphicalEffects 1.14
  7 Window {
  8     visible: true;
  9     width: 1180
 10     height: 770
 11     minimumWidth: 1050
 12     minimumHeight: 680
 13     color: "#232324"
 14     title: "图片浏览器"
 15
 16     property string picturesLocation : "";
 17     property var imageNameFilters : ["所有图片格式 (*.png; *.jpg; *.bmp; *.gif; *.jpeg)"];
 18     property var pictureList : []
 19     property var pictureIndex : 0
 20     property var scaleMax : 800           // 最大800%
 21     property var scaleMin : 10            // 最小10%
 22     property var titleColor : "#E8E8E8"
 23     property var contentColor : "#D7D7D7"
 24
 25
 26     property var ctrlSliderList : [
 27         ["放大", scaleMin, scaleMax , photoImage.scale * 100 , "%"],
 28         ["旋转", -180, 180 , photoImage.rotation, "°"],
 29     ]
 30
 31     FileDialog {
 32         id: fileDialog
 33         title: "请打开图片(可以多选)"
 34         fileMode: FileDialog.OpenFiles
 35         folder: picturesLocation
 36         nameFilters: imageNameFilters
 37         onAccepted: {
 38             pictureList = files
 39             openNewImage(0)
 40         }
 41         onFolderChanged: picturesLocation = folder
 42     }
 43
 44     ColumnLayout {
 45         anchors.fill: parent
 46         spacing: 2
 47         RowLayout {
 48             Layout.fillHeight: true
 49             Layout.fillWidth: true
 50             spacing: 1
 51
 52             Flickable {             // 图片浏览区
 53                 id: flick
 54                 Layout.fillHeight: true
 55                 Layout.fillWidth: true
 56
 57                 MouseArea {                     // 设置滑轮效果
 58                     anchors.fill: parent
 59                     onWheel: {
 60                         if (wheel.modifiers & Qt.ControlModifier) {     // ctrl + 滑轮 则进行旋转图片
 61                             photoImage.rotation += wheel.angleDelta.y / 120 * 5;
 62                             if (photoImage.rotation > 180)
 63                                 photoImage.rotation =  180
 64                             else if (photoImage.rotation < -180)
 65                                 photoImage.rotation =  -180
 66                             if (Math.abs(photoImage.rotation) < 4)  // 如果绝对值小于4°,则摆正图片
 67                                 photoImage.rotation = 0;
 68                         } else {
 69                             photoImage.scale += photoImage.scale * wheel.angleDelta.y / 120 / 10;
 70                             if (photoImage.scale > scaleMax / 100)
 71                                 photoImage.scale =  scaleMax / 100
 72                             else if (photoImage.scale < scaleMin / 100)
 73                                 photoImage.scale =  scaleMin / 100
 74                         }
 75                     }
 76                 }
 77                 Image {
 78                     id: photoImage
 79                     fillMode: Image.Pad
 80                     source:  (typeof pictureList[pictureIndex] === 'undefined') ? "" : pictureList[pictureIndex]
 81                     smooth: true
 82                     mipmap: true
 83                     antialiasing: true
 84                     Component.onCompleted: {
 85                         x = parent.width / 2 - width / 2
 86                         y = parent.height / 2 - height / 2
 87                         pictureList.length = 0
 88                     }
 89
 90                     PinchArea {
 91                         anchors.fill: parent
 92                         pinch.target: parent
 93                         pinch.minimumRotation: -180     // 设置拿捏旋转图片最大最小比例
 94                         pinch.maximumRotation: 180
 95                         pinch.minimumScale: 0.1         // 设置拿捏缩放图片最小最大比例
 96                         pinch.maximumScale: 10
 97                         pinch.dragAxis: Pinch.XAndYAxis
 98                     }
 99
100                     MouseArea {                     // 设置拖动效果
101                         anchors.fill: parent
102                         drag.target: parent
103                         drag.axis: Drag.XAndYAxis
104                         drag.minimumX: 20 - photoImage.width
105                         drag.maximumX: flick.width - 20
106                         drag.minimumY: 20 - photoImage.height
107                         drag.maximumY: flick.height - 20
108                     }
109                 }
110             }
111             Rectangle {
112                 Layout.fillHeight: true
113                 Layout.fillWidth: false
114                 Layout.preferredWidth : 220
115                 color: "#313131"
116                 DynamicGroupBox {
117                      id: fileGroup
118                      title: "文件选项"
119                      width: parent.width
120
121                      ColumnLayout {
122                          anchors.centerIn: parent
123                          spacing: 12
124                          Repeater {
125                              model : ListModel {
126                                  id: fileModel
127                                  ListElement { name: "打开文件";  }
128                                  ListElement { name: "上一张";  }
129                                  ListElement { name: "下一张"; }
130
131                              }
132                              DynamicBtn {
133                                  text: fileModel.get(index).name
134                                  backColor: "#3A3A3A"
135                                  fontColor: contentColor
136                                  fontPixelSize: 14
137                                  onPressed: fileGroupPressed(index)
138
139                              }
140                          }
141                      }
142                      Component.onCompleted: initGroupBox(this);
143                 }
144
145                 DynamicGroupBox {
146                      id: ctrlGroup
147                      title: "图片控制"
148                      width: parent.width
149                      anchors.top: fileGroup.bottom
150
151                      ColumnLayout {
152                          anchors.centerIn: parent
153                          spacing: 12
154                          Repeater {
155                              model : 2
156                              RowLayout {
157                                  width: parent.width
158                                  Text {
159                                     color: contentColor
160                                     Layout.fillWidth: false
161                                     Layout.preferredWidth : 50
162                                     text: ctrlSliderList[index][0]
163                                     horizontalAlignment: Text.AlignRight
164                                     font.pixelSize: 14
165                                  }
166                                  DynamicSlider {
167                                     id: ctrlSlider
168                                     Layout.fillWidth: true
169                                     Layout.preferredWidth : 130
170                                     from: ctrlSliderList[index][1]
171                                     value: ctrlSliderList[index][3]
172                                     to: ctrlSliderList[index][2]
173                                     stepSize: 1
174                                     onMoved: setCtrlValue(index, value);
175                                  }
176                                  Text {
177                                      color: "#D4D4D4"
178                                      Layout.fillWidth: false
179                                      Layout.preferredWidth : 40
180                                      text: parseInt(ctrlSliderList[index][3].toString()) + ctrlSliderList[index][4]
181                                  }
182                              }
183                          }
184                      }
185                      Component.onCompleted: initGroupBox(this);
186                 }
187
188                 DynamicGroupBox {
189                      id: imageInfoGroup
190                      title: "基本信息"
191                      width: parent.width
192                      height: 120
193                      anchors.top: ctrlGroup.bottom
194                      ColumnLayout {
195                          width: parent.width
196                          spacing: 16
197                          Text {
198                             color: contentColor
199                             text: "尺寸: " + photoImage.sourceSize.width + "X" + photoImage.sourceSize.height
200                             font.pixelSize: 14
201                          }
202                          Text {
203                             color: contentColor
204                             text: "路径: " + ((typeof pictureList[pictureIndex] === 'undefined') ?
205                                               "等待打开文件..." : pictureList[pictureIndex].replace("file:///",""))
206
207                             Layout.preferredWidth: parent.width - 20
208                             Layout.preferredHeight: 60
209                             wrapMode: Text.Wrap
210                             font.pixelSize: 14
211                          }
212                      }
213                      Component.onCompleted: initGroupBox(this);
214                 }
215                 DynamicGroupBox {
216                      id: authorInfoGroup
217                      title: "关于"
218                      width: parent.width
219                      height: 110
220                      anchors.top: imageInfoGroup.bottom
221
222                      ColumnLayout {
223                          width: parent.width
224                          spacing: 16
225                          Text {
226                             color: contentColor
227                             text: "作者: 诺谦"
228                             Layout.preferredWidth: parent.width - 20
229                             wrapMode: Text.Wrap
230                             font.pixelSize: 14
231                          }
232                          Text {
233                             color: contentColor
234                             text: "博客: <font color=\"#D4D4D4\"><a href=\"http://www.cnblogs.com/lifexy/\">cnblogs.com/lifexy/</a></font>"
235                             font.pixelSize: 14
236                             onLinkActivated: Qt.openUrlExternally(link)
237                          }
238                      }
239                      Component.onCompleted: initGroupBox(this);
240                 }
241             }
242         }
243
244         Rectangle {
245             id: images
246             Behavior on Layout.preferredHeight { NumberAnimation { duration: 250 } }
247             Layout.fillHeight: false
248             Layout.fillWidth: true
249             Layout.preferredHeight: 130
250             LinearGradient {
251                 anchors.fill: parent
252                 source: parent
253                 start: Qt.point(0, 0)
254                 end: Qt.point(0, parent.height)
255                 gradient: Gradient {
256                     GradientStop { position: 0.0; color: "#484848" }
257                     GradientStop { position: 0.01; color: "#373737" }
258                     GradientStop { position: 1.0; color: "#2D2D2D" }
259                 }
260             }
261             Button {
262                 id: imageCtrlBtn
263                 text:  images.Layout.preferredHeight <= 30 ? "展开("+pictureList.length+")" :
264                                                              "收起("+pictureList.length+")"
265                 anchors.right: parent.right
266                 anchors.rightMargin: 3
267                 z: 100
268                 background: Rectangle {
269                     color: "transparent"
270                 }
271                 contentItem: Label {                    // 设置文本
272                     id: btnForeground
273                     text: parent.text
274                     font.family: "Microsoft Yahei"
275                     font.pixelSize: 14
276                     color:  imageCtrlBtn.hovered ? "#D7D7D7" : "#AEAEAE"
277                     horizontalAlignment: Text.AlignHCenter
278                     verticalAlignment: Text.AlignVCenter
279                 }
280                 onPressed: {
281                     if (text.indexOf("收起") >= 0) {
282                         images.Layout.preferredHeight = 30
283                     } else {
284                         images.Layout.preferredHeight = 130
285                     }
286                 }
287             }
288             ScrollView {
289                   id: imageScroll
290                   anchors.fill: parent
291                   anchors.leftMargin: 10
292                   anchors.rightMargin: 10
293                   wheelEnabled: true
294                   WheelHandler {
295                         onWheel: openNewImageAndUpdateScroll(event.angleDelta.y > 0 ? pictureIndex - 1 : pictureIndex + 1)
296                   }
297                   ScrollBar.horizontal.policy: ScrollBar.horizontal.size >= 1.0 ?
298                                                  ScrollBar.AlwaysOff : ScrollBar.AlwaysOn
299                   ScrollBar.vertical.policy: ScrollBar.AlwaysOff
300
301                   ScrollBar.horizontal.contentItem: Rectangle {
302                       implicitHeight: 7
303                       implicitWidth: 100
304                       radius: height / 2
305                       color: "#7D7C7C"
306                       visible: images.Layout.preferredHeight <= 30 ? false : true
307                   }
308                   Row {
309                       anchors.fill: parent
310                       anchors.topMargin: 30
311                       spacing: 20
312                       Repeater {
313                           model: pictureList.length
314
315                           Button {
316                               implicitWidth: 85
317                               implicitHeight: 85
318                               onPressed: openNewImage(index)
319                               background: Rectangle {
320                                   color: "#202020"
321                                   border.color: pictureIndex == index ? "#2770DF" :
322                                                  hovered ? "#6C6A6A" : "transparent"
323                                   radius: 5
324                                   border.width: 3
325                               }
326                               Image {
327                                   anchors.fill:parent
328                                   anchors.margins: 6
329                                   antialiasing: true
330                                   fillMode: Image.PreserveAspectFit
331                                   source: pictureList[index]
332                               }
333                           }
334                       }
335                   }
336              }
337         }
338     }
339     function initGroupBox(group) {
340         group.titleLeftBkColor = "#313131"
341         group.titleRightBkColor = "#474951"
342         group.titleColor = titleColor
343         group.contentBkColor = "#2A2A2A"
344         group.borderColor = "#454545"
345         group.titleFontPixel = 14
346         group.radiusVal = 0
347         group.borderWidth = 1
348     }
349     function fileGroupPressed(index) {
350         switch (index) {
351             case 0 :  fileDialog.open(); break;
352             case 1 :  openNewImageAndUpdateScroll(pictureIndex - 1); break;
353             case 2 :  openNewImageAndUpdateScroll(pictureIndex + 1); break;
354         }
355     }
356     function setCtrlValue(index, value) {
357         switch (index) {
358             case 0 :  photoImage.scale  = value / 100; break;
359             case 1 :  photoImage.rotation = value; break;
360         }
361     }
362     function openNewImage(index) {
363         if (index < 0 || index >= pictureList.length) {
364         }
365         pictureIndex = index
366         photoImage.x = flick.width / 2 - photoImage.width / 2
367         photoImage.y = flick.height / 2 - photoImage.height / 2
368         photoImage.scale = 1.0
369         photoImage.rotation = 0
370
371     }
372     function openNewImageAndUpdateScroll(index) {
373         if (index < 0 || index >= pictureList.length) {
374             return false
375         }
376         pictureIndex = index
377         photoImage.x = flick.width / 2 - photoImage.width / 2
378         photoImage.y = flick.height / 2 - photoImage.height / 2
379         photoImage.scale = 1.0
380         photoImage.rotation = 0
381
382         var scrollLen = 1.0 - imageScroll.ScrollBar.horizontal.size;
383         if (scrollLen > 0) {
384             scrollLen =  scrollLen * pictureIndex  / (pictureList.length - 1)
385             imageScroll.ScrollBar.horizontal.position = scrollLen
386         }
387         return true
388     }
389 }

可以看到代码不到400行就完成了,可见Qt Quick的魅力所在

demo链接下载: https://download.csdn.net/download/qq_37997682/18652128

未完待续,下章学习Canvas

 

 

05-13 16:08