第十一章 图形视图、动画和状态机框架——Qt

一、图形视图框架的结构

图形视图框架提供了一个基于图形项的模型视图编程算法,主要由场景、视图和图形项三部分组成,这三部分分别由QGraphicsScence、QGraphicsView和
QGraphicsItem这3个类来表示。
在这里插入图片描述

1.场景

QGraphicsScene提供了图形视图框架中的场景,场景拥有以下功能:

  • 提供了用于管理大量图形项的快速接口;
  • 传播事件给每一个图形项;
  • 管理图形项的状态,例如选择和焦点处理;
  • 提供无变换的渲染功能,主要用于打印;

场景是图形项QGraphicsItem对象的容器。

  • QGraphicsScene::addItem()函数:将图形项添加到场景中,然后调用众多的图形项发现函数之一来检索添加的图形项。
  • QGraphicsScene::items()函数:和它的其他几个重载函数可以返回符合条件的所有图形项,这些图形项不是与指定的点、矩形、多边形或者矢量路径相交,就是包含在它们之中。
  • QGraphicsScene::itemAt()函数:返回指定点的最上面的图形项。所有的图形项发现函数返回的图形项都是使用递减顺序。
  • QGraphicsScene::RemoveItem()函数:从场景中删除一个图形项。

一个场景分为三层:图形项层(ItemLayer)、前景层(ForegroundLayer)和背景层(BackgroundLayer)。
场景的绘制总是从背景层开始,然后是图形项层,最后是前景层。前景层和背景层都可以使用QBrush进行填充。

2.视图

QGraphicsView提供了视图部件,它用来使场景中的内容可视化。可以连接多个视图到同一个场景来为相同的数据集提供多个视口。
视图部件是一个可滚动的区域,可以使用setDragMode()函数以QGraphicsView::ScrollHandDrag为参数来使光标变为手掌形状,从而可以拖动场景。
如果设置setDragMode()的参数为QGraphicsView::RubberBandDrag,那么可以在视图上使用鼠标拖出橡皮筋框来选择图形项。
默认的QGraphicsView提供了一个QWidget作为视口部件,如果要使用OpenGL进行渲染,可以调用QGraphicsView::setViewport()设置QGLWidget作为视口。
QGraphicsView会获取视口部件的拥有权(ownership)。

3.图形项

QGraphicsItem是场景中图形项的基类。Item)。
QGraphicsItem主要支持如下功能:

  • 鼠标按下、移动、释放、双击、悬停、滚轮和右键菜单事件
  • 键盘输入焦点和键盘事件
  • 拖放事件
  • 分组,使用QGraphicsItemGroup通过parent-child关系来实现
  • 碰撞检测

除此之外,图形项还可以存储自定义的数据,可以使用setData()进行数据存储,然后使用data()获取其中的数据。
要实现自定义的图形项,那么首先要创建一个QGraphicsItem的子类,然后重新实现它的两个纯虚公共函数:boundingRect()和paint(),
前者用来返回要绘制图形项的矩形区域,后者用来执行实际的绘图操作。

  • boundingRect():
    blockBoundingRect(const QTextBlock &block) const
  • paint():
    void QGraphicsItem::paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0 )
    这个函数一般会被QGraphicsView调用,用来在本地坐标中绘制图形项中的内容。
    其中painter参数用来进行一般的绘图操作;
    option参数为图形项提供了一个风格选项;
    widget参数是可选的,如果提供了该参数,那么它会指向那个要在其上进行绘图的部件,否则默认为0,表明使用缓冲绘图。
    painter的画笔的宽度默认为0,它的画笔被初始化为绘图设备调色板的QPalette::Text画刷,而painter的画刷被初始化为QPalette::Window。

图形视图框架提供的标准化图形项,
在这里插入图片描述

二、图形视图框架的坐标系统和事件处理

1.坐标系统

图形视图框架基于笛卡尔坐标系统,一个图形项在场景中的位置和几何形状由x坐标和y坐标来表示。
当使用一个没有变换的视图来观察场景时,场景中的一个单元代表屏幕上的一个像素。
图形视图框架中有3个有效的坐标系统:图形项坐标、场景坐标和视图坐标。

(1)图形项坐标

图形项使用自己的本地坐标系统,坐标通常是以它们的中心为原点(0,0)。
图形项的位置是指图形项的原点在其父图形项或者场景中的位置。如果一个图形项在另一个图形项之中,那么它被称为子图形项,而包含它的图形项称为它的父图形项。
子图形项的位置和坐标是相对于父图形项的,虽然父图形项的坐标变换会隐含的变换子图形项,但是,子图形项的坐标不会受到父图形项的变换的影响。
默认的,父图形项会被最先进行绘制,然后按照顺序对其上的子图形项进行绘制。所有的图形项都包含一个Z值来设置它们的层叠顺序,一个图形项的Z值默认为0。

(2)场景坐标

场景坐标是所有图形项的基础坐标系统。场景坐标系统描述了每一个顶层图形项的位置。
场景坐标的原点在场景的中心,x和y坐标分别向右和向下增大。
每一个在场景中的图形项除了拥有一个图形项的本地坐标和边界矩形外,还都拥有一个场景坐标(QGraphicsItem::scenePos())
和一个场景中的边界矩形(QGraphicsItem::sceneBoundingRect())。
场景坐标用来描述图形项在场景坐标系统中的位置,而图形项的场景边界矩形构成了QGraphicsScene怎样来判断场景中的哪些区域被更改了的基础。

(3)视图坐标

视图的坐标就是部件的坐标。
视图坐标的每一个单位对应一个像素,原点(0, 0)总在QGraphicsView的视口的左上角,而右下角是(宽,高)。
所有的鼠标事件和拖放事件最初都是使用视图坐标被接收的。

(4)坐标映射

图形视图框架的映射函数:

映射函数						描述
QGraphicsView::mapToScene()		从视图坐标系统映射到场景坐标系统
QGraphicsView::mapFromScene()	从场景坐标系统映射到视图坐标系统
QGraphicsItem::mapToScene()		从图形项的坐标系统映射到场景的坐标系统
QGraphicsItem::mapFromScene()	从场景的坐标系统映射到图形项的坐标系统
QGraphicsItem::mapToParent()	从本图形项的坐标系统映射到其父图形项的坐标系统
QGraphicsItem::mapFromParent()	从父图形项的坐标系统映射到本图形项的坐标系统
QGraphicsItem::mapToItem()		从本图形项的坐标系统映射到另一个图形项的坐标系统
QGraphicsItem::mapFromItem()	从另一个图形项的坐标系统映射到本图形项的坐标系统	
2.事件处理与传播

图形视图框架中的事件都是首先由视图进行接收,然后传递给场景,再由场景传递给相应的图形项。
而对于键盘事件,它会传递给获得焦点的图形项,可以使用QGraphicsScene类的setFocusItem()函数或者图形项自身调用setFocus()函数来将其设置为焦点图形项。
默认的,如果场景没有获得焦点,那么所有的键盘事件都会被丢弃。如果调用了场景的setFocus()函数或者场景中的一个图形项获得了焦点,那么场景也会自动获得焦点。
对于鼠标悬停效果,QGraphicsScene会调度悬停事件。如果一个图形项可以接收悬停事件,那么当鼠标进入它的区域之中时,它就会收到一个GraphicsSceneHoverEnter事件。
如果鼠标继续在图形项的区域之中进行移动,那么QGraphicsScene就会向该图形项发送GraphicsSceneHoverMove事件。
当鼠标离开图形项的区域时,它将会收到一个GraphicsSceneHoverLeave事件。
图形项默认是无法接收悬停事件的,可以使用QGraphicsItem类的setAcceptHoverEvents()函数使图形项可以接收悬停事件。

三、图形视图框架的其他特性

1.图形效果

QGraphicsEffect类是所有图形效果的基类。
使用图形效果来改变元素的外观是通过在源对象(如一个图形项)和目标设备(如视图的视口)之间挂接了渲染管道和一些操作来实现的。
图形效果可以实施在任何一个图形项或者非顶层窗口的任何一个窗口部件上,只需先创建一个图形效果对象,然后调用setGraphicsEffect()函数来使用这个图形效果即可。
如果想停止该效果,可以调用setEnable(false)。
Qt标准图形效果:

图形效果类					介绍
QGraphicsBlurEffect 		该类提供了一个模糊效果,该效果一般用来减少源对象细节的显示。可以使用setBlurRadius()函数来修改细节等级,默认的模糊半径是5
									像素;还可以使用setBlurHints()来指定模糊怎样来执行
QGraphicsColorizeEffect		该类提供了一个染色效果,该效果用来为源对象进行染色。可以使用setColor()函数修改颜色,默认是浅蓝色QColor(0,0,192);还可以
									使用setStrength()来修改效果的强度,强度在0.0~1.0之间,默认为1.0
QGraphicsDropShadowEffect	该类提供了一个阴影效果,该效果可以为源对象提供一个阴影。可以使用setColor()来修改效果的颜色,默认是透明的黑灰色
									QColor(63,63,63,180);可以使用setOffset()来改变阴影的偏移值,默认为右下方8像素;还可以使用setBlurRadius()来改变引用的模糊
									半径,其默认值为1
QGraphicsOpacityEffect		该类提供了一个透明效果,该效果可以使源对象透明。可以使用setOpacity()函数来修改透明度,其值在0.0~1.0之间,0.0表示完全透明,
									1.0表示完全不透明
2.动画、碰撞检测和图形项组
(1)动画

以前可以使用QGraphicsItemAnimation类很容易的实现图形项的动画效果,不过该类现在已经过时。
现在主要是通过动画框架来实现动画效果。
另外的方法是创建一个继承自QObject和QGraphicsItem的自定义图形项,然后创建它自己的定时器来实现动画。该方法是使用QGraphicsScene::advance()来推进场景。

(2)碰撞检测

图形视图框架提供了图形项之间的碰撞检测,碰撞检测可以使用两种方法来实现:

  • 重新实现QGraphicsItem::shape()函数来返回图形项的准确的形状,然后使用默认的collidesWithItem()函数通过两个图形项形状之间的交集来判断是否发生碰撞。
    如果图形项的形状很复杂,那么进行这个操作是非常耗时的。如果没有重新实现shape()函数,那么它默认会调用boundingRect()函数返回一个简单的矩形。
  • 重新实现collidesWithItem()函数来提供一个自定义的图形项碰撞算法。
    • 使用QGraphicsItem类的collidesWithItem()函数来判断是否与指定的图形项进行了碰撞;

    • 使用collidesWithPath()来判断是否与指定的路径碰撞;

    • 使用collidingItem()来获取与该图形项碰撞的所有图形项的列表;也可以调用QGraphicsScene类的collidingItem()。
      这几个函数都有一个Qt::ItemSelectionMode参数来指定怎样进行图形项的选取,它一共有4个值:

        常量								描述
        Qt::ContainsItemShape				选取只有形状完全包含在选择区域之中的图形项
        Qt::IntersectsItemShape			选取形状完全包含在选择区域之中或者与区域的边界相交的图形项
        Qt::ContainsItemBoundingRect		选取只有边界矩形完全包含在选择区域之中的图形项
        Qt::IntersectsItemBoundingRect	选取边界矩形完全包含在选择区域之中或者与区域的边界相交的图形项
      
(3)图形项组

QGraphicsItemGroup图形项组为图形项提供了一个容器,它可以将多个图形项组合在一起而将它本身以及它所有的子图形项看做一个独立的图形项。
与父图形项不同,图形项组中的所有图形项都是平等的,例如可以通过拖动其中任意一个来将它们一起进行移动。
而如果只想将一个图形项存储在另一个图形项之中,那么可以使用setParentItem()来为其设置父图形项。

3.打印和使用OpneGL进行渲染
(1)打印

图形视图框架提供渲染函数QGraphicsScene::render()和QGraphicsView::render()来完成打印功能。
两者的不同之处就是一个在场景坐标上进行操作而另一个在视图坐标上。
QGraphicsScene::render()经常用来打印没有变换的场景,比如几何数据和文本文档等;
QGraphicsView::render()函数适合用来实现屏幕快照。

(2)使用OpenGL进行渲染

使用OpenGL进行渲染,可以使用QGraphicsView::setViewport()来将QGLWidget作为QGraphicsView的视口。如果想OpenGL进行抗锯齿,可以使用采样缓冲区(sample buffer)。

首先在项目文件中添加如下一行代码:
	QT += opengl	
然后视图对象调用setViewport():
	view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); 
4.窗口部件、布局和内嵌部件

图形部件QGraphicsWidget与QWidget很相似,但是与QWidget不同,它不是继承自QPaintDevice,而是QGraphicsItem。
通过它可以实现一个拥有事件、信号和槽、大小提示和策略的完整的部件,还可以使用QGraphicsLinearLayout和QGraphicsGridLayout来实现部件的布局。
QGraphicsWidget继承自QGraphicsObject和QGraphicsLayoutItem,而QGraphicsObject继承自QObject和QGraphicsItem,所以QGraphicsWidget既拥有以前窗口部件的一些特性也拥有图形项的一些特性。
图形视图框架提供了对任意的窗口部件嵌入场景的无缝支持,这是通过QGraphicsWidget的子类QGraphicsProxyWidget实现的。
可以使用QGraphicsScene类的addWidget()函数方便的将任何一个窗口部件嵌入到场景中,这也可以通过创建QGraphicsProxyWidget类的实例来实现。

四、动画框架

动画框架是Kinetic项目的一部分,其目的是为了提供一种简单的方法来创建平滑的具有动画效果的GUI界面。 该框架是通过控制Qt的属性来实现动画的,它可以应用在窗口部件和其他QObject对象上,也可以应用在图形视图框架中。
基类QAbstractAnimation和它的两个子类QVariantAnimation以及QAnimationGroup构成了动画框架的基础。
这里的QAbstractAnimation是所有动画类的祖先,它定义了一些所有动画类都共享的功能函数,比如动画的开始、停止和暂停等,
它也可以接收时间变化的通知,通过继承这个类,可以创建自定义的动画类。
类继承关系:
在这里插入图片描述

1.实现属性动画

QPropertyAnimation类可以对Qt属性进行插值,如果要对一个值进行动画,那么就要使用这个类。
使用Qt属性来进行动画的最主要原因就是这样可以为已经存在的Qt API中的类提供灵活的动画。并不是所有的属性都可以设置动画,必须是Qt支持的QVariant类型。
使用setDuration()函数指定了动画的持续时间;
使用函数setStartValue()和setEndValue()分别设置动画开始时和结束时目标属性的值;
可以调用setKeyValueAt(qreal step, const QVariant &value)函数在动画中间为属性设置值,
其中step取值在0.0-1.0之间,0.0表示开始位置,1.0表示结束位置,而value为属性的值。
在动画中可以使用pause()来暂停动画;使用resume()来恢复暂停状态;使用stop()来停止动画;可以使用setDirection()函数设置动画的方向 。

2.使用缓和曲线

默认的运动过程都是线性的,即匀速运动。除了在动画中添加更多的关键点,还可以使用缓和曲线,缓和曲线描述了怎样来控制0和1之间的插值的速度的功能,这样就可以在不改变插值的情况下来控制动画的速度。

3.动画组

使用QAnimationGroup类可以实现复杂的动画,它的两个子类QSequentialAnimationGroup和QParallelAnimationGroup分别提供了串行动画组和并行动画组。

4.在图形视图框架中使用动画

要对QGraphicsItem使用动画,也可以使用QPropertyAnimation类。但是,QGraphicsItem并不是继承自QObject类,所以直接继承自QGraphicsItem的图形项并不能直接使用QPropertyAnimation类来创建动画。
QGraphicsItem的子类QGraphicsObject,它继承自QObject和QGraphicsItem,这个类为所有需要使用信号和槽以及属性的图形项提供了一个基类,通过创建这个类的子类就可以使用属性动画了。
QGraphicsObject还提供了多个常用的属性,比如位置pos、透明度opacity、旋转rotation和缩放scale等,这些都可以直接用来设置动画。

五、状态机框架

状态机框架提供了一些类来创建和执行状态图(state graphs),状态图为一个系统如何对外界激励进行反应提供了一个图形化模型,该模型是通过定义一些系统可能进入的状态以及系统怎样从一个状态切换到另一个状态来实现的。
事件驱动的系统(比如Qt应用程序)的一个关键特性就是它的行为不总是仅仅依赖于前一个或者当前的事件,而且也依赖于将要执行的事件。通过使用状态图,这些信息会非常容易进行表达。
详情请参考:link.

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值