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


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

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

    1.图片浏览器介绍

    界面截图如下所示:

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

    快捷键说明:

    • 如果鼠标位于大图浏览区,则可以通过鼠标滑轮来放大缩小图片,通过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      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                       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                       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                                   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                       parent.width
    192                      height: 120
    193                      anchors.top: ctrlGroup.bottom
    194                      ColumnLayout {
    195                           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                       parent.width
    219                      height: 110
    220                      anchors.top: imageInfoGroup.bottom
    221 
    222                      ColumnLayout {
    223                           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. 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

    未完待续,下章学习24.Qt Quick QML-Canvas和Context2D详解

     

     


    人间有真情,人间有真爱。

    如果您喜欢这里,感觉对你有帮助,并且有多余的软妹币的话,不妨投个食吧,赞赏的时候,留下美句和你的博客地址哦~   戳这里看谁投食了


  • 相关阅读:
    Java Web学习笔记之---EL和JSTL
    Java Web学习笔记之---Servlet
    Java Web项目案例之---登录注册和增删改查(jsp+servlet)
    Java项目案例之---加法计算器(转发和重定向)
    Java Web项目案例之---登录和修改(JSP)
    Java Web学习笔记之---JSP
    Java项目案例之---定时器的使用
    Java项目案例之---常用工具类练习
    Java学习笔记之---API的应用
    这些年我对安全成熟度模型的一点点思考
  • 原文地址:https://www.cnblogs.com/lifexy/p/14762697.html
  • Copyright © 2020-2023  润新知