之前我们已经学习了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: 存放用户打开的所有图片的浏览区
代码如下所示:
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