4.6がやってきた-Qt最新事情2010

第3回Qt 4.6のアニメーションフレームワーク[後編]

グラフィックスビューのアイテムのアニメーション

グラフィックスビューのQGraphicsItemアイテムにアニメーションフレームワークを適用するには、QGraphicsItemアイテムのプロパティでアニメーションすることなります。しかし、ほとんどのQGraphicsItemアイテムはQObjectを継承してはいないので、そのようなアイテムでアニメーションするには、以下の2つの方法を用います。

  • 1.QGraphicsObjectをサブクラス化して、アイテムを作成する。
  • 2.QObjectとQGraphicsRectItemやQGraphicsPixmapItemを多重継承したアイテムを作成する。

QGraphicsSvgItem、QGraphicsTextItem、QGraphicsWidgetなどのようにQObjectを継承しているアイテムは、そのままでアニメーションを適用できます。

ここでは、2番目の方法でQGraphicsItemアイテムにアニメーションを適用してみましょう。

今回紹介するアニメーションプログラムのソースリストはこちらからダウンロードしてください。
examples.zip

リスト1 QGraphicsItemアイテムのアニメーション例(ボール移動アニメーション)
 1: #include <QApplication>
 2: #include <QGraphicsView>
 3: #include <QGraphicsScene>
 4: #include <QGraphicsPixmapItem>
 5: #include <QPropertyAnimation>
 6: #include <QLayout>
 7: #include <<QPushButton>
 8: 
 9: class MovableGraphicsPixmapItem : public QObject, public QGraphicsPixmapItem
10: {
11:     Q_OBJECT
12:     Q_PROPERTY( QPointF pos READ pos WRITE setPos )
13: 
14: public:
15:     MovableGraphicsPixmapItem( const QPixmap& pixmap, QGraphicsItem* parent = 0 )
16:         : QObject(), QGraphicsPixmapItem( pixmap, parent ) {
17:     }
18: };
19: 

2番目の方法を用いた、QObjectとQGraphicsPixmapItemを多重継承したクラスです。posプロパティを既存のゲッターpos()とセッターsetPos()を用いて定義し、アイテムをプロパティを介して操作できるようすれば、アニメーションフレームワークが適用できるようになります。親のオブジェクトを指定せずに、QObjectを初期化することに注意します。

20: class HarnessPrivate
21: {
22: public:
23:     HarnessPrivate() 
24:         : movingAreaSize( QSize( 360, 120 ) ), horizontalOffset( 8 ) {
25:     }
26: 
27:     QSize movingAreaSize;
28:     int horizontalOffset;
29:     QPropertyAnimation* yellowBallAnimation;
30: };
31: 
32: class Harness : public QWidget
33: {
34:     Q_OBJECT
35:     Q_DECLARE_PRIVATE( Harness )
36: 
37: public:
38:     Harness();
39:     ~Harness();
40: 
41: private slots:
42:     void startAnimation();
43: 
44: private:
45:     MovableGraphicsPixmapItem* createBallItem( const QString& pixmapFileName );
46:     HarnessPrivate* d_ptr;
47: };
48: 

動くアイテムがあるグラフィックスビューを置くためのクラスです。今迄のサンプルプログラムと同じように、Animate ボタンがクリックされたならば、startAnimation() スロットを呼出して、アニメーションを開始するようにします。

49: Harness::Harness()
50:     : QWidget( 0 ), d_ptr( new HarnessPrivate )
51: {
52:     Q_D( Harness );
53: 
54:     QGraphicsScene* graphicsScene = new QGraphicsScene( 0, 0, d->>movingAreaSize.width(), d->movingAreaSize.height() );
55:     MovableGraphicsPixmapItem* yellowBallItem = createBallItem( ":/images/YellowGlassBall.png" );
56:     QSize ballSize = yellowBallItem->pixmap().size();
57:     int ballSpacingY = ( d->movingAreaSize.height() - ballSize.height())  / 2;
58:     int yellowBallY = ballSpacingY;
59:     yellowBallItem->setPos( d->horizontalOffset, yellowBallY );
60:     graphicsScene->addItem( yellowBallItem );
61: 

グラフィックスシーンの左端に、アニメーションが可能なようにしたアイテムを配置しています。

62:     QGraphicsView* graphicsView = new QGraphicsView();
63:     graphicsView->setScene( graphicsScene );
64: 
65:     QPushButton* animateButton = new QPushButton( "Animate" );
66: 
67:     QHBoxLayout* buttonLayout = new QHBoxLayout;
68:     buttonLayout->addStretch();
69:     buttonLayout->addWidget( animateButton );
70: 
71:     QVBoxLayout* topLayout = new QVBoxLayout;
72:     topLayout->addWidget( graphicsView );
73:     topLayout->addLayout( buttonLayout );
74: 
75:     setLayout( topLayout );
76:     setFixedSize( sizeHint() );
77: 
78:     d->yellowBallAnimation = new QPropertyAnimation( yellowBallItem, "pos", this );
79:     d->yellowBallAnimation->setDuration( 5 * 1000 );
80:     int movableBallStartX = d->horizontalOffset;
81:     int movableBallEndX = d->movingAreaSize.width() - ballSize.width() - d->horizontalOffset;
82:     d->yellowBallAnimation->setStartValue( QPoint( movableBallStartX, yellowBallY ) );
83:     d->yellowBallAnimation->setEndValue( QPoint( movableBallEndX, yellowBallY ) );
84:     d->yellowBallAnimation->setEasingCurve( QEasingCurve::OutBounce );
85: 

posプロパティを使ってアニメーションするのは、今までのサンプルプログラムとまったく同じようになります。つまり、QObjectを継承するQtのオブジェクトは、グラフィカルなものも非グラフィカルなものも、アニメーションフレームワークを用いてアニメーションできるのです。

 86:     connect( animateButton, SIGNAL( clicked() ), SLOT( startAnimation() ) );
 87: }
 88: 
 89: Harness::~Harness()
 90: {
 91:     delete d_ptr;
 92: }
 93: 
 94: void Harness::startAnimation()
 95: {
 96:     Q_D( Harness );
 97:     
 98:     d->yellowBallAnimation->start();
 99: }
100: 
101: MovableGraphicsPixmapItem* Harness::createBallItem( const QString& pixmapFileName )
102: {
103:     MovableGraphicsPixmapItem* item = new MovableGraphicsPixmapItem( QPixmap( pixmapFileName ) );
104: 
105:     return item;
106: }
107: 
108: int main( int argc, char** argv )
109: {
110:     QApplication app( argc, argv );
111: 
112:     Harness harness;
113:     harness.show();
114: 
115:     return app.exec();
116: }
117: 
118: #include "movablegraphicsitem.moc"

リスト1のサンプルプログラムを実行すると、以下の動画のように黄色いボールが左から右に移動します。

ボール移動アニメーション

複数のアニメーションの扱い方

アニメーションフレームワークには、複数のウィジェットやアイテムがアニメーションの対象となっている場合に、各アニメーションを順番に実行したり、並行して実行させるために、アニメーショングループという機能が用意されています。

シーケンシャルアニメーションによるアニメーションの順次実行

図1のように、QSequentialAnimationGroupクラスを用いて3つのアニメーションを順番に実行してみましょう。

図1 順次実行の概要。Yellow→Green→Redの順に実行する。
図1 順次実行の概要。Yellow→Green→Redの順に実行する。

コードの変更箇所はリスト2のようになります。

リスト2 アニメーションの順次実行の例(リスト1からの変更部分のみ)
 1: #include <QApplication>
 2: #include <QGraphicsView>
 3: #include <QGraphicsScene>
 4: #include <QGraphicsPixmapItem>
 5: #include <QPropertyAnimation>
 6: #include <QSequentialAnimationGroup>
 7: #include <QLayout>
 8: #include <QPushButton>
 9: 

QSequentialAnimationGroup クラスを使うため、そのヘッダファイルのインクルードを追加します。

21: class HarnessPrivate
22: {
23: public:
24:     HarnessPrivate() 
25:         : movingAreaSize( QSize( 360, 320 ) ), horizontalOffset( 8 ) {
26:     }
27: 
28:     QSize movingAreaSize;
29:     int horizontalOffset;
30:     QSequentialAnimationGroup* ballAnimationGroup;
31: };
32: 

30行目で、QPropertyAnimationの代わりにQSequentialAnimationGroupを使うようにします。

33: class Harness : public QWidget
34: {
35:     Q_OBJECT
36:     Q_DECLARE_PRIVATE( Harness )
37: 
38: public:
39:     Harness();
40:     ~Harness();
41: 
42: private slots:
43:     void startAnimation();
44: 
45: private:
46:     MovableGraphicsPixmapItem* createBallItem( const QString& pixmapFileName );
47:     QPropertyAnimation* createBallAnimation( MovableGraphicsPixmapItem* item, int duration, int startX, int endX, int y );
48:     HarnessPrivate* d_ptr;
49: };
50: 

黄、緑、赤の3つのボールを縦に並べ、上から順番に1つずつ実行するので、ボールのアイテムを生成するメソッドを47行目で宣言します。

51: Harness::Harness()
52:     : QWidget( 0 ), d_ptr( new HarnessPrivate )
53: {
54:     Q_D( Harness );
55: 
56:     QGraphicsScene* graphicsScene = new QGraphicsScene( 0, 0, d->movingAreaSize.width(), d->movingAreaSize.height() );
57:     MovableGraphicsPixmapItem* yellowBallItem = createBallItem( ":/images/YellowGlassBall.png" );
58:     QSize ballSize = yellowBallItem->pixmap().size();
59:     int ballSpacingY = ( d->movingAreaSize.height() - ballSize.height() * 3 ) / 4;

3つのボールがあるので、59行目のボールの上下の空白の求め方を変えています。

60:     int yellowBallY = ballSpacingY;
61:     yellowBallItem->setPos( d->horizontalOffset, yellowBallY );
62:     graphicsScene->addItem( yellowBallItem );
63: 
64:     MovableGraphicsPixmapItem* greenBallItem = createBallItem( ":/images/GreenGlassBall.png" );
65:     int greenBallY = yellowBallY + ballSize.height() + ballSpacingY;
66:     greenBallItem->setPos( d->horizontalOffset, greenBallY );
67:     graphicsScene->addItem( greenBallItem );
68: 
69:     MovableGraphicsPixmapItem* redBallItem = createBallItem( ":/images/RedGlassBall.png" );
70:     int redBallY = greenBallY + ballSize.height() + ballSpacingY;
71:     redBallItem->setPos( d->horizontalOffset, redBallY );
72:     graphicsScene->addItem( redBallItem );

64~72行目で緑と赤のボールを追加しています。

 90:     int movableBallStartX = d->horizontalOffset;
 91:     int movableBallEndX = d->movingAreaSize.width() - ballSize.width() - d->horizontalOffset;
 92: 
 93:     QPropertyAnimation* yellowBallAnimation = createBallAnimation( yellowBallItem, 5 * 1000, movableBallStartX, movableBallEndX, yellowBallY );
 94:     QPropertyAnimation* greenBallAnimation = createBallAnimation( greenBallItem, 5 * 1000, movableBallStartX, movableBallEndX, greenBallY );
 95:     QPropertyAnimation* redBallAnimation = createBallAnimation( redBallItem, 5 * 1000, movableBallStartX, movableBallEndX, redBallY );
 96: 
 97:     d->ballAnimationGroup = new QSequentialAnimationGroup( this );
 98:     d->ballAnimationGroup->addAnimation( yellowBallAnimation );
 99:     d->ballAnimationGroup->addAnimation( greenBallAnimation );
100:     d->ballAnimationGroup->addAnimation( redBallAnimation );
101: 
102:     connect( animateButton, SIGNAL( clicked() ), SLOT( startAnimation() ) );
103: }

93~100行目では各ボールを5秒ずつ動かすアニメーションを作り、97行目でQSequentialAnimationGroupでグループ化しています。このようにすると、まず黄のボールが5秒かけて左から右に移動し終わると、緑のボールが同じように5 秒かけて移動し、最後に赤のボールが移動するという動きになります。

110: void Harness::startAnimation()
111: {
112:     Q_D( Harness );
113: 
114:     foreach ( QPropertyAnimation* propertyAnimation, findChildren<QPropertyAnimation*>() ) {
115:         MovableGraphicsPixmapItem* item = ( MovableGraphicsPixmapItem* )propertyAnimation->targetObject();
116:         item->setProperty( "pos", propertyAnimation->startValue() );
117:     }
118:     
119:     d->ballAnimationGroup->start();
120: }

114~116行目で、3つのボールを左端に戻しています。次回に説明するステートマシンフレームワークを用いれば、初期状態に戻すのをわかりやすく記述できますが、今はこのようにして初期状態に戻します。

119行目でアニメーショングループのstart()を呼んで、アニメーションの開始をさせています。この場合には、グループ内のQPropertyAnimationが順番に実行されます。

129: QPropertyAnimation* Harness::createBallAnimation( MovableGraphicsPixmapItem* item, int duration, int startX, int endX, int y )
130: {
131:     QPropertyAnimation* ballAnimation = new QPropertyAnimation( item, "pos", this );
132:     ballAnimation->setDuration( duration );
133:     ballAnimation->setStartValue( QPoint( startX, y ) );
134:     ballAnimation->setEndValue( QPoint( endX, y ) );
135:     ballAnimation->setEasingCurve( QEasingCurve::OutBounce );
136:     
137:     return ballAnimation;
138: }

各ボールのアニメーションを生成するための補助メソッドです。このプログラムを実行すると、次の動画のようになります。

3つのボールをシーケンシャルに動かすアニメーション

パラレルアニメーションによるアニメーションの並行実行

今度は、図2のように、アニメーションとシーケンシャルアニメーションを並行に実行してみます。シーケンシャルアニメーションのサンプルプログラムへの変更箇所はリスト3のようになります。

図2 並行実行の概要。黄色のボールの動きと並行して、緑→赤の動きを順次実行する。
図2 並行実行の概要。黄色のボールの動きと並行して、緑→赤の動きを順次実行する。
リスト3 アニメーションの並行実行の例(リスト1からの変更部分のみ)
1: #include <QApplication>
2: #include <QGraphicsView>
3: #include <QGraphicsScene>
4: #include <QGraphicsPixmapItem>
5: #include <QPropertyAnimation>
6: #include <QSequentialAnimationGroup>
7: #include <QParallelAnimationGroup>
8: #include <QLayout>
9: #include <QPushButton>

QParallelAnimationGroupクラスを使うために、そのヘッダファイルのインクルードを追加します。

22: class HarnessPrivate
23: {
24: public:
25:     HarnessPrivate() 
26:         : movingAreaSize( QSize( 360, 320 ) ), horizontalOffset( 8 ) {
27:     }
28: 
29:     QSize movingAreaSize;
30:     int horizontalOffset;
31:     QParallelAnimationGroup* ballAnimationGroup;
32: };
33: 

ballAnimationGroupをQParallelAnimationGroupに変更します。

 91:     int movableBallStartX = d->horizontalOffset;
 92:     int movableBallEndX = d->movingAreaSize.width() - ballSize.width() - d->horizontalOffset;
 93: 
 94:     QPropertyAnimation* yellowBallAnimation = createBallAnimation( yellowBallItem, 20 * 1000, movableBallStartX, movableBallEndX, yellowBallY );
 95:     QPropertyAnimation* greenBallAnimation = createBallAnimation( greenBallItem, 10 * 1000, movableBallStartX, movableBallEndX, greenBallY );
 96:     QPropertyAnimation* redBallAnimation = createBallAnimation( redBallItem, 10 * 1000, movableBallStartX, movableBallEndX, redBallY );
 97: 
 98:     d->ballAnimationGroup = new QParallelAnimationGroup( this );
 99:     d->ballAnimationGroup->addAnimation( yellowBallAnimation );
100:     QSequentialAnimationGroup* sequentialAnimation = new QSequentialAnimationGroup( this );
101:     sequentialAnimation->addAnimation( greenBallAnimation );
102:     sequentialAnimation->addAnimation( redBallAnimation );
103:     d->ballAnimationGroup->addAnimation( sequentialAnimation );
104: 
105:     connect( animateButton, SIGNAL( clicked() ), SLOT( startAnimation() ) );
106: }

94~96行目では黄色のボールのアニメーション時間を20秒にし、緑と赤のボールは10秒ずつにしています。このようにすると、黄色のボールが20秒かけて移動する間に、緑ボールが移動してから赤のボールが移動するようになります。

98行目でQParallelAnimationGroupのインスタンスを生成し、100~103行でその中にアニメーションとシーケンシャルアニメーションを入れています。

このプログラムを実行すると、次の動画のようになります。

パラレル動作のアニメーション

おわりに

Qtのオブジェクトのプロパティのアニメーションが、メタオブジェクトシステムによってうまく実装され、簡単に使えるというのを把握していただけたと思います。今回は、アニメーションフレームワークの基本的な機能のみを説明したので、どのようにして、ダイナミックに滑らかな動きをする効果的なGUIが作れるのかについては、わかりにくいかもしれません。これは、次回以降の説明と連載後半の宣言的UIの説明で、具体的に掴めるように解説する予定です。

おすすめ記事

記事・ニュース一覧