qml demo分析(photosurface-图片涅拉)

  阅读qml示例代码已有一小段时间,也陆续的写了一些自己关于qml示例代码的理解,可能由于自己没有大量的qml开发经验,总感觉复杂的ui交互qml处理起来可能会比较棘手,但事实总是会出人意料,今天我们就来分析一个关于油耗交互的qml代码。从毕业后就一直从事qt的相关开发,一直在使用QWidget窗口做产品,看多了qml后才发现原来还有这么好的ui开发利器,完全的数据和展示分离,ui可以做到想怎么变怎么变,没有了QBoxLayout的帮助(同样是约束),ui再也不用那么死板,对于灵活、绚丽的桌面程序个人觉着qml可以逐步取代QWidget窗口。

一、效果展示

  如图1所示是photosurface的效果展示,是不是觉着功能还挺强大,完成这些功能真正起作用的代码却没有几行,下边就让我们一起来分析下这个示例代码吧。

图1 photosurface效果展示

二、源码分析

  图片展示窗口默认拉取的系统目录为C:\Users\Administrator\Pictures,也就是我们通常所指的系统图库目录,如图2所示即是photosurface效果展示所拉取的图片数据。

图2 系统图库目录

  这个示例代码也是相对比较简单,主要的代码都是使用qml实现,C++部分提供了程序所能支持读取的图片文件格式和获取当前系统图库目录,最后使用QQmlContext的setContextProperty接口将获取到的值注册到qml上下文系统中。

1、获取支持的图片格式文件

 1 static QStringList imageNameFilters()
 2 {
 3     QStringList result;
 4     QMimeDatabase mimeDatabase;
 5     foreach (const QByteArray &m, QImageReader::supportedMimeTypes()) {
 6         foreach (const QString &suffix, mimeDatabase.mimeTypeForName(m).suffixes())
 7             result.append(QStringLiteral("*.") + suffix);
 8     }
 9     return result;
10 }

  如上代码是通过QImageReader获取当前所支持的图片格式后缀,并组装成*.suffixes字符串,例如:*.png。

2、获取当前系统目录

 1     QQmlApplicationEngine engine;
 2     QQmlContext *context = engine.rootContext();
 3
 4     QUrl picturesLocationUrl = QUrl::fromLocalFile(QDir::homePath());
 5     const QStringList picturesLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
 6     if (!picturesLocations.isEmpty()) {
 7         picturesLocationUrl = QUrl::fromLocalFile(picturesLocations.first());
 8         if (initialUrl.isEmpty()
 9             && !QDir(picturesLocations.first()).entryInfoList(nameFilters, QDir::Files).isEmpty()) {
10             initialUrl = picturesLocationUrl;
11         }
12     }
13
14     context->setContextProperty(QStringLiteral("contextPicturesLocation"), picturesLocationUrl);
15     context->setContextProperty(QStringLiteral("contextInitialUrl"), initialUrl);
16     context->setContextProperty(QStringLiteral("contextImageNameFilters"), nameFilters);

  qt5之后获取系统路径的类变为QStandardPaths,通过该类可以获取到系统图库目录、系统音乐目录等,具体参见StandardLocation参数,如图3所示

图3 支持系统目录

3、qml部分

  开篇我就感慨了下,就因为qml写ui时可以随意写,接下来就让我们一起看看有多么的随意

3.1打开目录选择对话框

1 FileDialog {//打开文件夹
2         id: fileDialog
3         title: "Choose a folder with some images"
4         selectFolder: true
5         folder: picturesLocation
6         onAccepted: folderModel.folder = fileUrl + "/"
7     }

  picturesLocation是通过C++程序获取的变量并注册到qml上下文系统中,该变量指明了当前对话框打开时默认选中的目录

3.2窗口底部文字说明

 1     //窗口最底下说明
 2     Text {
 3         anchors.bottom: parent.bottom
 4         anchors.left: parent.left
 5         anchors.right: parent.right
 6         anchors.margins: 10
 7         color: "darkgrey"
 8         wrapMode: Text.WordWrap
 9         font.pointSize: 8
10         text: "On a touchscreen: use two fingers to zoom and rotate, one finger to drag\n" +
11               "With a mouse: drag normally, use the vertical wheel to zoom, horizontal wheel to rotate, or hold Ctrl while using the vertical wheel to rotate"
12     }

  该组件使用了anchors锚点,可以始终停留在主窗口底部

3.3组件加载完毕动作

 1     Component.onCompleted: {
 2         if (typeof contextInitialUrl !== ‘undefined‘) {
 3             // Launched from C++ with context properties set.
 4             imageNameFilters = contextImageNameFilters;//QImageReader支持的图片格式
 5             picturesLocation = contextPicturesLocation;//组件加载完毕时  初始化打开图片路径
 6             if (contextInitialUrl == "")
 7                 fileDialog.open();//没有获取到系统图片路径 直接打开
 8             else
 9                 folderModel.folder = contextInitialUrl + "/";//指定对话框默认打开文件夹路径
10         } else {
11             // Launched via QML viewer without context properties set. 当没有使用C++代码设置context值时
12             fileDialog.open();
13         }
14     }

3.4左上角文件夹图标

 1 //左上角文件夹图标  点击弹出系统文件夹选择框
 2     Image {
 3         anchors.top: parent.top
 4         anchors.left: parent.left
 5         anchors.margins: 10
 6         source: "resources/folder.png"
 7         MouseArea {
 8             anchors.fill: parent
 9             anchors.margins: -10
10             onClicked: fileDialog.open()//点击打开文件夹对话框
11             hoverEnabled: true
12             onPositionChanged: {
13                 tooltip.visible = false//位置改变时 隐藏提示框
14                 hoverTimer.start()
15             }
16             onExited: {
17                 tooltip.visible = false//鼠标离开时 隐藏提示框
18                 hoverTimer.stop()
19             }
20             Timer {
21                 id: hoverTimer
22                 interval: 1000
23                 onTriggered: {
24                     tooltip.x = parent.mouseX
25                     tooltip.y = parent.mouseY
26                     tooltip.visible = true
27                 }
28             }
29             Rectangle {//提示框
30                 id: tooltip
31                 border.color: "black"
32                 color: "beige"
33                 width: tooltipText.implicitWidth + 8
34                 height: tooltipText.implicitHeight + 8
35                 visible: false
36                 Text {
37                     id: tooltipText
38                     anchors.centerIn: parent
39                     text: "Open an image directory (" + openShortcut.sequenceString + ")"
40                 }
41             }
42         }
43         Shortcut {
44             id: openShortcut
45             sequence: StandardKey.Open
46             onActivated: fileDialog.open()
47         }
48     }

  习惯了C++这种编译性语言,qml这种声明性语言用起来感觉好简单,就拿这段代码中的提示框来说,不管数据怎么样,提示框的行为和效果展示我们都可以随意定制,这在使用QWidget来实现时就没有这么容易。提示框的显示和隐藏只需要在交互合适的时机通过id来设置visible属性即可。

3.5滚动条

 1 //右侧垂直滚动条
 2 Rectangle {
 3     id: verticalScrollDecorator
 4     anchors.right: parent.right//锚定父窗口右侧
 5     anchors.margins: 2//距离边界2px
 6     color: "cyan"
 7     border.color: "black"//滚动条边框颜色
 8     border.width: 1//滚动条边框宽度
 9     width: 5//滚动条宽度
10     radius: 2//四角圆角半径
11     antialiasing: true//反锯齿
12     height: flick.height * (flick.height / flick.contentHeight) - (width - anchors.margins) * 2//滚动条高度
13     y:  flick.contentY * (flick.height / flick.contentHeight)//滚动条y值
14     NumberAnimation on opacity { id: vfade; to: 0; duration: 500 }//使用动画将滚动条透明度设置为0,即不可见
15     onYChanged: { opacity = 1.0; scrollFadeTimer.restart() }//启动滚动条小时定时器->调用水平和垂直透明度渐变动画,知道滚动条消失
16 }

  水平滚动条和垂直滚动条一样

 1 //水平底部滚动条
 2 Rectangle {
 3     id: horizontalScrollDecorator
 4     anchors.bottom: parent.bottom
 5     anchors.margins: 2
 6     color: "cyan"
 7     border.color: "black"
 8     border.width: 1
 9     height: 5
10     radius: 2
11     antialiasing: true
12     width: flick.width * (flick.width / flick.contentWidth) - (height - anchors.margins) * 2
13     x:  flick.contentX * (flick.width / flick.contentWidth)
14     NumberAnimation on opacity { id: hfade; to: 0; duration: 500 }
15     onXChanged: { opacity = 1.0; scrollFadeTimer.restart() }
16 }

3.6 可滑动区域

  最后一个,也是最重要的一个滑动区域

  1 Flickable {
  2     id: flick
  3     anchors.fill: parent
  4     contentWidth: width * surfaceViewportRatio//可展示组件区域宽度
  5     contentHeight: height * surfaceViewportRatio//可展示组件区域高度
  6     Repeater {
  7         model: FolderListModel {//FolderListModel:文件夹系统model
  8             id: folderModel
  9             objectName: "folderModel"
 10             showDirs: false
 11             nameFilters: imageNameFilters//过滤文件夹格式"*.png", "*.jpg", "*.gif"
 12         }
 13         Rectangle {
 14             id: photoFrame
 15             width: image.width * (1 + 0.10 * image.height / image.width)
 16             height: image.height * 1.10
 17             scale: defaultSize / Math.max(image.sourceSize.width, image.sourceSize.height)
 18             Behavior on scale { NumberAnimation { duration: 200 } }//缩放、x坐标和y坐标发生变化时都使用动画在200ms完成
 19             Behavior on x { NumberAnimation { duration: 200 } }
 20             Behavior on y { NumberAnimation { duration: 200 } }
 21             border.color: "black"//图片边框别景色
 22             border.width: 2
 23             smooth: true//平滑
 24             antialiasing: true//反锯齿
 25             Component.onCompleted: {
 26                 x = Math.random() * root.width - width / 2
 27                 y = Math.random() * root.height - height / 2
 28                 rotation = Math.random() * 13 - 6//随机一个角度
 29             }
 30             Image {
 31                 id: image
 32                 anchors.centerIn: parent
 33                 fillMode: Image.PreserveAspectFit//图片均匀缩放 不剪裁
 34                 source: folderModel.folder + fileName//filename是FolderListModel提供的属性
 35                 antialiasing: true
 36             }
 37             //通常和一个可见的 Item 配合使用来处理捏拉手势
 38             PinchArea {//http://blog.csdn.net/foruok/article/details/32078761
 39                 anchors.fill: parent
 40                 pinch.target: photoFrame//pinch 属性知名与捏拉手势的详情
 41                 pinch.minimumRotation: -360//涅拉逆向旋转最大360°
 42                 pinch.maximumRotation: 360//涅拉顺时旋转最大360°
 43                 pinch.minimumScale: 0.1//涅拉最小缩放到原始大小10%
 44                 pinch.maximumScale: 10//涅拉最大缩放到原始大小10倍
 45                 pinch.dragAxis: Pinch.XAndYAxis//拖拽x轴和y轴都可以
 46                 onPinchStarted: setFrameColor();//第一次识别到捏拉手势时发出 修改当前图片
 47                 property real zRestore: 0
 48                 onSmartZoom: {
 49                     if (pinch.scale > 0) {//放大
 50                         photoFrame.rotation = 0;
 51                         photoFrame.scale = Math.min(root.width, root.height) / Math.max(image.sourceSize.width, image.sourceSize.height) * 0.85
 52                         photoFrame.x = flick.contentX + (flick.width - photoFrame.width) / 2
 53                         photoFrame.y = flick.contentY + (flick.height - photoFrame.height) / 2
 54                         zRestore = photoFrame.z
 55                         photoFrame.z = ++root.highestZ;//涅拉时z值增大
 56                     } else {
 57                         photoFrame.rotation = pinch.previousAngle
 58                         photoFrame.scale = pinch.previousScale
 59                         photoFrame.x = pinch.previousCenter.x - photoFrame.width / 2
 60                         photoFrame.y = pinch.previousCenter.y - photoFrame.height / 2
 61                         photoFrame.z = zRestore//缩小时还原z值
 62                         --root.highestZ
 63                     }
 64                 }
 65
 66                 MouseArea {
 67                     id: dragArea
 68                     hoverEnabled: true
 69                     anchors.fill: parent
 70                     drag.target: photoFrame//拖拽对象  为Flickable中Model数据的绘制代理
 71                     scrollGestureEnabled: false  // 2-finger-flick gesture should pass through to the Flickable
 72                     onPressed: {
 73                         photoFrame.z = ++root.highestZ;
 74                         parent.setFrameColor();//鼠标按下图片区域时 重置当前图片currentFrame  主要重置属性z  代表图片显示层
 75                     }
 76                     onEntered: parent.setFrameColor();//鼠标进入图片区域时 重置当前图片currentFrame
 77                     onWheel: {//鼠标滚轮滚动时 如果按下contrl键 则当前图片进行旋转  否则进行缩放
 78                         if (wheel.modifiers & Qt.ControlModifier) {
 79                             photoFrame.rotation += wheel.angleDelta.y / 120 * 5;
 80                             if (Math.abs(photoFrame.rotation) < 4)
 81                                 photoFrame.rotation = 0;
 82                         } else {
 83                             photoFrame.rotation += wheel.angleDelta.x / 120;
 84                             if (Math.abs(photoFrame.rotation) < 0.6)
 85                                 photoFrame.rotation = 0;
 86                             var scaleBefore = photoFrame.scale;
 87                             photoFrame.scale += photoFrame.scale * wheel.angleDelta.y / 120 / 10;
 88                         }
 89                     }
 90                 }
 91                 function setFrameColor() {
 92                     if (currentFrame)
 93                         currentFrame.border.color = "black";//设置上一张图片边框颜色为黑色
 94                     currentFrame = photoFrame;
 95                     currentFrame.border.color = "red";//设置当前图片边框颜色为红色
 96                 }
 97             }
 98         }
 99     }
100 }

  源码中有大量的注释,针对大多数代码都有了注释,应该不难理解。

  这个滑动区域使用了Repeater元素来生成多个对象,对象来自模型FolderListModel,id为photoFrame的Rectangle为绘制代理,在绘制代理中有两个事件响应组件PinchArea和MouseArea,这两个事件所针对的情况不一样,MouseArea是鼠标事件,PinchArea是涅拉事件主要针对触屏操作。PinchArea细节

时间: 2024-10-24 18:29:22

qml demo分析(photosurface-图片涅拉)的相关文章

qml demo分析(rssnews-常见新闻布局)

一.效果展示 今儿来分析一篇常见的ui布局,完全使用qml编写,ui交互效果友好,如图1所示,是一个常见的客户端新闻展示效果,左侧是一个列表,右侧是新闻详情. 图1 新闻效果图 二.源码分析 首先先来总体分析下该示例代码的工程目录,如图2所示,总共有6个qml文件.其中BusyIndicator和组件是qml已经存在的组件,NewsDelegate组件是新闻详情页中的一项,CategoryDelegate组件是左侧列表中的一项,RssFeeds组件是左侧新闻列表数据源,rssnews文件是主程序

qml demo分析(clocks-时钟)

一.效果展示 效果如图1所示,时钟列表支持鼠标左右拖动,带有黑色背景的是晚上时钟,无黑色背景的是白天时钟 二.源码分析 1.main.cpp文件中只包含了一个宏,该宏的具体解释请看qml 示例中的关键宏文章 2.时钟项 1 Item { 2 id : clock 3 width: { 4 if (ListView.view && ListView.view.width >= 200) 5 return ListView.view.width / Math.floor(ListView

Cocos2d-x3.1TestCpp之MotionStreakTest Demo分析

1.构成代码 VisibleRect.h VisibleRect.cpp AppDelegate.h AppDelegate.cpp HelloWorldScene.h HelloWorldScene.cpp MotionStreakDemo.h MotionStreakDemo.cpp 2.代码分析 (1)VisibleRect.Appdelegate的代码均为TestCpp提供代码: (2)HelloWorldScene.cpp中只需把上一篇Cocos2d-x3.1TestCpp之NewRe

iOS开发-UITableView顶部图片下拉放大

关于顶部图片下拉放大,在用户展示的个人中心显示用户个人头像信息,设置UITableView的headerView实现,UITableView继承自UIScrollView,同样的设置UIScrollView的顶部图片也可以实现同样的效果,简单看一下实现的效果: 控制器中设置需要的属性变量: @property (strong,nonatomic) UITableView *tableView; @property (strong,nonatomic) NSArray *data; @proper

从源代码分析Android-Universal-Image-Loader图片下载技巧

在手机上尤其需要考虑网络对图片下载的影响,常见的情况是在2G网络.在3G网络需要不同的下载策略,也就是说在慢速网络与快速网络中下载需要考虑不同的策略.一种常见的策略就是Android客户端和服务端相配合的方式,针对慢速网络对图片进行优化(让图片的质量低一点,保证能下载),但是这种情况不在本文讨论的范围中.在本文中主要讨论针对不能改变的服务器图片质量(图片的大小 xx KB),Android-Universal-Image-Loader所采取的下载策略. 需要具体考虑网络情况有:快速.慢速.无网络

经验之谈—实现图片下拉放大的效果

这里我们主要是用一下,如何能保持原来的图片的宽高比来轻松的实现放大的效果,主要的是UIViewContentModeScaleAspectFill这个起的效果: 我们用tableView来展示这个效果吧 我们这里并没有计算图片的宽高比,直接用UIViewContentModeScaleAspectFill来实现 #import "ViewController.h" const CGFloat ZYTopViewH = 350; @interface ViewController ()

Cocos2d-x3.1TestCpp之NewRenderTest Demo分析

1.代码构成 VisibleRect.h VisibleRect.cpp AppDelegate.h AppDelegate.cpp HelloWorldScene.h HelloWorldScene.cpp NewRenderTest.h NewRenderTest.cpp 2.HelloWorld代码 #include "cocos2d.h" #include "ui/CocosGUI.h" USING_NS_CC; using namespace ui; cl

XMPP协议实现即时通讯底层书写 (二)-- IOS XMPPFramework Demo+分析

我希望,This is a new day! 在看代码之前,我认为你还是应该先整理一下心情,来听我说几句: 首先,我希望你是在早上边看这篇blog,然后一边開始动手操作,假设你仅仅是看blog而不去自己对照项目,作用不是非常大.一日之计在于晨,所以怀着一颗对技术渴望,激动的.亢奋的心情去学习.你才干有所得. 嗯,就拿鄙人当时做项目来说,每天早上起来的第一件事情.就是研究XMPPFramework作者的代码,依照模块来分析和模仿书写.睡觉的时候还在思考,分析.总结... 当然我并非说每一个Dev

Android图片的拉取与缓存

Anroid应用中经常会有从网上拉取图片的需求,拉图片说简单也很好做,说难也是很费力的,虽然网上的方案很多,开源框架也不少,但具体的实现还是得看需求.下面分享一下我在项目中用到的两种拉图片方案. 1. 少量图片 如果图片少量,使用框架就显得冗余,直接下载就更简洁一些. public static boolean downloadImage(String url, String savePath) { LogUtil.v(TAG, "url=" + url + "; savep