Qt動畫與Qt座標小記

Qt動畫


轉載自: <http://jingyan.baidu.com/article/154b46315757b628ca8f4116.html>  <http://blog.csdn.net/syzobelix/article/details/9377863>
  • Qt動畫架構中的主要類如下圖所示:


    動畫框架由基類QAbstractAnimation和它的兩個子類QVariantAnimation和QAnimationGroup組成。 QAbstractAnimation是所有動畫類的祖宗。它包含了所有動畫的基本屬性。比如開始,停止和暫停一個動畫的能力。它也可以接收時間改變通 知。

    動畫框架又進一步提供了QProertyAnimation類。它繼承自QVariantAnimation並對某個Qt屬性(它須是Qt的”元數據對象 系統”的一部分,見http://blog.csdn.net/nkmnkm/article/details/8225089)執行動畫。此類對屬性執 行一個寬鬆曲線插值。所以當你想去動畫一個值時,你可以把它聲明爲一個屬性,並且讓你的類成爲一個QObject。這給予我們極大的自由度來動畫那些已存 在的widget和其它QObject。

    複雜的動畫可以通過建立一個QAbstractAnimation的樹來構建。這個樹通過使用QAnimationGroups來創 建,QAnimationGroups作爲其它動畫的容器。注意動畫組也是從QAbstractAnimation派生的,所以動畫組可以再包含其它動畫 組。

    動畫框架可以單獨使用,同時也被設計爲狀態機框架的一部分。狀態機提供了一個特定的狀態可以用來播放動畫。在進入或退出某個狀態時QState也可以設置 屬性們,並且這個特定的動畫狀態將在指定QPropertyAnimation時給予的值之間做插值運算。後面我們要進一步介紹此問題。

    在場景的背後,動畫被一個全局定時器收集,這個定時器發送update到所有的正在播放的動畫中。

  • 動畫框架中的類們

QAbstractAnimation 所有動畫類的基類   

QAnimationGroup 動畫組的基類   

QEasingCurve 控制動畫的寬鬆曲線類   

QParallelAnimationGroup 並行動畫組類  

QPauseAnimation 串行動畫組類的暫停類  

QPropertyAnimation 動畫Qt屬性的類 

QSequentialAnimationGroup 串行動畫組類   

QTimeLine 控制動畫的時間線類   

QVariantAnimation 各動畫類的虛基類

   

  • 動畫Qt屬性們

    如前面所講,QPropertyAnimation類可以修改Qt屬性們。要動畫一個值,就需要使用此類。實際上,它的父類,QVariantAnimation,是一個虛擬類,不能被直接使用。

    1、我們選擇動畫Qt屬性的一個主要理由是Qt屬性爲我們提供了自己動畫已存在的類的自由度。尤其是QWidget類(我們也可以把它嵌入到一個QGraphicsView中)具有很多屬性表示其bounds,colors等等。讓我們看一個小例子:

QPushButton button("Animated Button");  

button.show();  

QPropertyAnimation animation(&button, "geometry");  

animation.setDuration(10000);  

animation.setStartValue(QRect(0, 0, 0, 0));  

animation.setEndValue(QRect(250, 250, 100, 30));  

animation.start();  

    這段代碼將把按鈕在10秒種內從屏幕的左上角移動到(250,250)處,而且是逐漸變大。見下圖效果:


    2、上面的例子舉在開始值和結束值之間做線性插值。還可以在開始和結束值之間設置值,插值運算就會經過這些點。

animation1 = new QPropertyAnimation(ui.pushButton, "geometry");   

animation1->setDuration(10000);  

animation1->setKeyValueAt(0, QRect(0, 0, 00, 00));  

animation1->setKeyValueAt(0.4, QRect(20, 250, 20, 30));  

animation1->setKeyValueAt(0.8, QRect(100, 250, 20, 30));  

animation1->setKeyValueAt(1, QRect(250, 250, 100, 30));  

animation1->setEndValue(QRect(250, 250, 100, 30));  

    在此例中,動畫將按鈕在8秒中內弄到(250,250)處,然後在2秒種內又弄回原位。移位是在這些點中間以線性插值進行的。


    3、你也有可能動畫一個QObject的值,雖然這些值並沒有被聲明爲Qt屬性。唯一的要求就是這個值具有一個setter。之後你可以從這個類派生 子類從而包含這些值並且聲明一個使用這個setter的屬性。注意每個Qt屬性都需要有一個getter,所以你需要提供一個getter,如果它不存在 的話。

class MyGraphicsRectItem : public QObject, public QGraphicsRectItem  

{  

    Q_OBJECT  

    Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry)  

};  

    在上例中,我們派生了QGraphicsRectItem並定義了一個geometry屬性。我們現在可以動畫這個widget的geometry了,即使QGraphicsRectItem沒有提供geometry屬性。

  • 動畫和圖形視圖框架

    當你想動畫QGraphicsItems,你也要用QPropertyAnimation。然而,QGraphicsItem不是從QObject派生 的。一個好的解決方案是派生要動畫的圖形item。派生類也要從QObject派生。這樣,QPropertyAnimation就可以被用於 QGraphicsItems了。

class Pixmap : public QObject, public QGraphicsPixmapItem  

{  

    Q_OBJECT  

    Q_PROPERTY(QPointF pos READ pos WRITE setPos)  

    ...  

    就如上一節中所講的,我們需要定義希望去動畫的屬性。

    注意:QObject必須是繼承中的第一個,因爲元數據對象系統需要這樣做。

  • 寬鬆曲線

    QPropertyAnimation在屬性的開始值和結束值之間執行一個插值運算。除了向動畫添加更多的關鍵值外,你還可以使用一個寬鬆曲線。寬鬆曲線描述了一個在0和1之間插值的速度變化的函數,如果你想控制一個動畫的速度而不改變插值的路徑時,就非常有用。

animation1 = new QPropertyAnimation(ui.pushButton, "geometry");   

animation1->setDuration(10000);  

animation1->setStartValue(QRect(0, 0, 0, 0));  

animation1->setEndValue(QRect(250, 250, 100, 30));  

animation1->setEasingCurve(QEasingCurve::OutBounce);  

     這裏,動畫將按照一個曲線進行,這個曲線使得動畫像一個跳動的皮球從開始位置跳到結束位置。QEasingCurve具有一個大麴線集合,你可以從裏面 選擇一個。它們被定義爲QEasingCurve::Type枚舉。如果你需要不一樣的曲線,你也可以自己實現一個,然後註冊到 QEasingCurve。

  •  錯誤1:QPropertyAnimation: you're trying to animate a non-existing property 屬性窗口

解釋:

animation1 = new QPropertyAnimation(ui.pushButton, "geometry");

    估計是你給的屬性名字(propertyName)是錯誤的,必須是qt自帶的屬性名字,或者你通過自定義實現的名字。

    我要是把代碼寫成:

animation1 = new QPropertyAnimation(ui.pushButton, "geometry23");

    會提示如下錯誤,找不到屬性geometry23

QPropertyAnimation: you're trying to animate a non-existing property setGeometry23 of your QObject


來源: <http://jingyan.baidu.com/article/154b46315757b628ca8f4116.html>

Qt座標

QPainter的各種draw方法是基於窗口座標系的。

窗口座標爲邏輯座標,是基於視口座標系的;視口座標爲物理座標,是基於繪圖設備座標系的。沒有做過改動的情況下,他們是一樣的,都是以繪圖設備(paint device,qwidget,qpixmap等都爲繪圖設備)大小爲大小,左上角爲原點(0,0)。

窗口:

窗口代表視口的區域,他始終以視口座標爲最終目標進行映射(這句話的意思到下面講視口的時候會再講),他的大小和邏輯位置可以通過QPainter::setWindow()設置,但是無論大小和邏輯位置設置爲什麼數值,他始終代表着整個視口。

例如你有一個實際大小爲200×200像素的窗口,那麼原始狀態之下窗口大小也是200×200,視口大小也是200×200,,在0,0位置畫一個大小爲100×100的矩形的時候,他會佔視口左上角的4分之一。
painter.drawRect(0,0,100,100);
100x100

如果這時候我們通過QPainter::setWindow修改了窗口位置和大小,例如setWindow(-50,-50,100,100)

函數原型:
void QPainter::setWindow(int x, int y, int width, int height)

參數:
x:窗口左上角x座標
y:窗口左上角y座標
width:窗口長度
height:窗口高度

窗口代表的還是整個視口,但是映射的數值有所不同,這時候窗口的邏輯座標(-50,-50)成爲了視口座標的(0,0),而窗口的邏輯大小成爲了 100×100的單位長度(這裏用單位長度是因爲窗口大小的長度並不固定,受視口大小影響),因爲用100個單位長度代表原本物理大小的200像素,所 以,每一個單位長度就是實際的2像素。因爲QPainter是以窗口座標爲基礎的,所以這時候畫一個位置爲(-50,-50),大小爲 50,50的矩形。
painter.drawRect(-50,-50,50,50);
矩形還是佔窗口的左上角的4分之一(下圖左),而
painter.drawRect(0,0,50,50);
矩形佔窗口右下角的4分之一。
50x50

而視口對應的則是物理座標,沒有改動的情況下,視口大小與繪圖區大小一樣,上面的例子中,視口的屬性一直沒有改變過,所以視口的左上角還是在繪圖區的物理位置(0,0),在窗口座標的(-50,-50)。大小爲物理大小的

200×200像素,而爲窗口座標系下的100×100單位長度。

視口:

現在我們看看設置視口對繪圖的影響,爲了簡單起見,先把上面的setWindow()一句註釋掉,即現在窗口,視口是一樣的。

現在來改變視口的屬性,先用painter.setViewPort(0,0,100,100);
函數原型:
void QPainter::setViewport ( int x, int y, int width, int height )

參數:
x:設置視口左上角x座標
y::設置視口左上角y座標
width:設置視口長度
height:設置視口寬度

所以上面語句的作業就是把視口的的原點位置設置爲繪圖設備(這裏是QDialog)的原點,大小改變爲100,100。

那麼現在是個什麼情況呢?
現在我們把視口的座標設置爲繪圖區的左上角(0,0)位置,大小設置爲繪圖區的一半,因爲繪圖區是(200×200),而我們把視口設置爲(100×100)。即現在實際的繪圖區爲繪圖設備的左上角的4分之一。

那麼這時候我們再用
painter.drawRect(0,0,100,100);畫一個矩形,實際顯示是怎麼樣的呢?看下圖:
100x100

繪製出來的是dialog的16分之一了,爲什麼會這樣呢?

前面我們講過窗口座標始終以視口座標爲最終目標進行映射, 而原來沒有經過修改的窗口的屬性爲以左上角爲原點,大小爲200×200單位長度,我們修改視口大小爲100×100像素後,窗口的200單位長度就映射 到100像素的視口長度上,即每一單位長度爲0.5像素,所以繪製出來的結果就是100×0.5=50像素,所以長和高都是dialog的4分之一,面積 就是16分之一了。

這時候如果我們拖動dialog邊界改變dialog的大小會怎麼樣呢?小狼原本的想法是畫出來的矩形應該還是佔總大小的16分之一,實際上這是錯的。
void Lang::paintEvent(QPaintEvent *)
{
QPainter painter(this);

<code style="margin: 0px; padding: 0px; border: 0px; font-size: 0.857142857rem; vertical-align: baseline; font-family: Consolas, Monaco, 'Lucida Console', monospace; line-height: 2; display: block;">    qDebug()<<"after drag:"<<endl;
    qDebug()<<painter.viewport().width();
    qDebug()<<painter.viewport().height();
    painter.setViewport(0, 0, 100 ,100 );
    //painter.setWindow(-50,-50,100,100);
    painter.drawRect(0,0, 100, 100);
}
</code>

先通過painter.viewport().width()和heigth()獲取當前實際視口大小(paintEvent之前,視口會被重置爲繪圖設備實際大小)。如下圖,當我們把dialog拖動爲400×400大小時,矩形框變得更小了。
400x400

其實這很簡單。因爲在paintEvent之前窗口值也會重置爲dialog(繪圖設備)大小,所以這時候窗口大小爲400×400單位長度,而視口我們 還是再設定爲100×100像素,所以這時候窗口大小的一單位長度爲實際的100/400=0.25像素,所以畫一個100×100單位長度的矩形時,實 際大小時25×25像素,所以變得更小。
100x100
上圖中我是用qq的截圖功能進行測量,qq截圖會給出當前截取圖形的大小,正是25×25(圈的時候有所偏差).

最後再來看一個窗口和視口一起設置的例子.
void Lang::paintEvent(QPaintEvent *)
{
QPainter painter(this);

<code style="margin: 0px; padding: 0px; border: 0px; font-size: 0.857142857rem; vertical-align: baseline; font-family: Consolas, Monaco, 'Lucida Console', monospace; line-height: 2; display: block;">    qDebug()<<"after drag:"<<endl;
    qDebug()<<painter.viewport().width();
    qDebug()<<painter.viewport().height();
    painter.setViewport(0, 0, 100 ,100 );
    //painter.setWindow(-50,-50,100,100);
    painter.drawRect(0,0, 100, 100);
}
</code>

這裏設置窗口座標(-50,-50)映射爲視口的原點,把窗口100×100單位長度映射爲視口的100×100像素大小
這時候窗口邏輯座標-50,-50就是視口的座標(0,0),也就是繪圖設備的50,50座標,所以窗口座標(0,0)位置即繪圖設備座標的(100,100)
因爲窗口座標100單位長度映射到視口座標的100像素,所以上圖的painter.drawRect(0,0, 50, 50);一句繪製出了的結果就是在繪圖設備的(100,100)位置繪製一個50×50像素的矩形。
6 7

總結:

要得到QPainter繪圖的真正位置,要經過兩步

第一步:
窗口座標轉換爲視口座標,轉換公式爲:
vp_width / win_width * (draw_x - win_x)
其中vp_width爲視口長度,win_width爲窗口長度,draw_x爲實際要繪製的x左上座標,win_x爲窗口的x左上座標,y座標同理

窗口大小轉換爲視口大小,轉換公式爲:
draw_width * vp_width / win_width
draw_width爲要繪製的窗口長度,vp_width爲視口長度,win_width爲窗口長度

height同理

第二步:
視口座標轉換爲繪圖設備座標,這一步就簡單的進行相加就好了
實際座標x=上一步轉換來的視口座標+vp_x
vp_x爲視口的左上x座標,實際座標y同理


 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章