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

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

はじめに

これから数回に渡って、KineticプロジェクトからQt 4.6に新しく取込まれた機能について説明します。

Kineticプロジェクトは、ダイナミックに滑らかな動きをするGUIをQtで実現しようとするプロジェクトです。公開リポジトリでは、Qt 4.7で予定されている機能を含めて、開発が継続しています。

アニメーションフレームワークとは

アニメーションフレームワークでは、Qtのオブジェクト、つまりQObject[1]のプロパティを動的に変更することによって、ウィジェットやグラフィックスビューのアイテム、そしてQObjectの任意のサブクラスで、アニメーション効果を利用できるようにしています。たとえば、ウィジェットの位置と大きさを決めているgeometryプロパティを時間の経過に合わせて変更して、ボタンやスライダーがウィンドウの端から動いて現れるようにするような効果を簡潔に実装できます。

アニメーションフレームワークの関連クラス

図1 Qt 4.6のアニメーションフレームワーク関連クラス
図1 Qt 4.6のアニメーションフレームワーク関連クラス

図1は、アニメーションフレームワークに用意されているクラスの概要です。

QPropertyAnimationクラスは、Qtのプロパティを動的に変更して、アニメーションをできるようにします。複数のアニメーションをつなぎ合わせたり、並列に動作させるには、QSequentialAnimationGroupとQParallelAnimationGroupを使います。これらの3つのクラスがアニメーションでよく使われ、QPauseAnimationがQSequentialAnimationGroup内のアニメーション間に一時停止時間を入れるために使われます。

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

ウィジェットのアニメーション

次の動画のように、ラベルを左から右に移動させるプログラムについて説明します。このプログラムでは、図2のように一定の速度でラベルを移動させています。

ラベル移動のアニメーション
図2動画の概要
図2 動画の概要
リスト1 ラベル移動アニメーション(その1)
 1: #include <QApplication>
 2: #include <QWidget>
 3: #include <QLabel>
 4: #include <QPushButton>
 5: #include <QPalette>
 6: #include <QLayout>
 7: #include <QPropertyAnimation>
 8: 
 9: class HarnessPrivate
10: {
11: public:
12:     HarnessPrivate() 
13:         : movingAreaSize( QSize( 360, 64 ) ), horizontalOffset( 8 ) {
14:     }
15: 
16:     QSize movingAreaSize;
17:     int horizontalOffset;
18:     QPropertyAnimation* labelAnimation;
19: };
20: 
21: class Harness : public QWidget
22: {
23:     Q_OBJECT
24:     Q_DECLARE_PRIVATE( Harness )
25: 
26: public:
27:     Harness();
28:     ~Harness();
29: 
30: private slots:
31:     void startAnimation();
32: 
33: private:
34:     HarnessPrivate* d_ptr;
35: };
36: 

動くラベルを置くためのクラスです。Animate ボタンがクリックされたならば、startAnimation()スロットを呼出して、アニメーションを開始するようにします。

37: Harness::Harness()
38:     : QWidget( 0 ), d_ptr( new HarnessPrivate )
39: {
40:     Q_D( Harness );
41: 
42:     QLabel* movableLabel;
43:     movableLabel = new QLabel( "Moving", this );
44:     movableLabel->setMargin( 4 );
45:     QPalette movableLabelPalette = movableLabel->palette();
46:     movableLabelPalette.setColor( QPalette::Window, Qt::red );
47:     movableLabel->setPalette( movableLabelPalette );
48:     movableLabel->setAutoFillBackground( true );
49:     int movableLabelY = ( d->movingAreaSize.height() - movableLabel->sizeHint().height() ) / 2;
50:     movableLabel->resize( movableLabel->sizeHint() );
51:     movableLabel->move( d->horizontalOffset, movableLabelY );
52:     

動かすラベルはレイアウト機能を使って並べずに、直に位置と大きさを指定することに注意しましょう。

53:     QPushButton* animateButton = new QPushButton( "Animate" );
54: 
55:     QHBoxLayout* buttonLayout = new QHBoxLayout;
56:     buttonLayout->addStretch();
57:     buttonLayout->addWidget( animateButton );
58: 
59:     QVBoxLayout* topLayout = new QVBoxLayout;
60:     topLayout->addSpacing( d->movingAreaSize.height() );
61:     topLayout->addLayout( buttonLayout );
62: 
63:     setFixedWidth( d->movingAreaSize.width() );
64:     setLayout( topLayout );
65: 
66:     setFixedHeight( sizeHint().height() );
67: 

動かさないボタンは、レイアウト機能を使ってきれいに並べます。ラベルが動く領域を確保するために、addSpacing()で高さが固定された空白領域が取られるように調整し、ウィンドウサイズ変更時の処理を省いているので、ウィンドウサイズを固定しています。

キャプション
68:     d->labelAnimation = new QPropertyAnimation( movableLabel, "pos", this );
69:     d->labelAnimation->setDuration( 5 * 1000 );
70:     int movableLabelStartX = d->horizontalOffset;
71:     int movableLabelEndX = width() - movableLabel->sizeHint().width() - d->horizontalOffset;
72:     d->labelAnimation->setStartValue( QPoint( movableLabelStartX, movableLabelY ) );
73:     d->labelAnimation->setEndValue( QPoint( movableLabelEndX, movableLabelY ) );
74: 

QPropertyAnimationのインスタンス生成で、第一引数にアニメーション対象のオブジェクトを指定し、第二引数にアニメーションで変化させるプロパティ名を指定します。QWidgetクラスでは、posプロパティが以下のように定義されています。

Q_PROPERTY(QPoint pos READ pos WRITE move DESIGNABLE false STORED false)

QPropertyAnimation によって、posプロパティが変更される際には、アニメーション処理の内部では、Qtのメタオブジェクトシステムの機能を用いて、文字列 "pos"からmove()メソッドが求められて呼出され、ウィジェットの位置が変更されます。

setDuration()でアニメーションの動作時間を5秒に設定し、setStartValue()とsetEndValue()でアニメーションの開始値と終了値を設定しています。

75:     connect( animateButton, SIGNAL( clicked() ), SLOT( startAnimation() ) );
76: }
77: 
78: Harness::~Harness()
79: {
80:     delete d_ptr;
81: }
82: 
83: void Harness::startAnimation()
84: {
85:     Q_D( Harness );
86:     
87:     d->labelAnimation->start();
88: }
89: 

Animateボタンをクリックすると、このstartAnimation()スロットが呼び出され、labelAnimationのstart()を呼んでアニメーションを開始し、ラベルを左から右へ移動させます。このように開始値と終了値のみを設定した場合には、これらの2値を線形補間して移動が行われます。

 90: int main( int argc, char** argv )
 91: {
 92:     QApplication app( argc, argv );
 93: 
 94:     Harness harness;
 95:     harness.show();
 96: 
 97:     return app.exec();
 98: }
 99: 
100: #include "movablelabel.moc"
101: 

アニメーション可能なプロパティ

先の例では、ラベルのposプロパティを変更して、その位置を動かしました。アニメーションの時間とその開始値と終了値を指定しただけで、自動的に滑らかに動くように位置が変更されました。開始値は、QVariantAnimationクラスのstartValueプロパティで、終了値はendValueプロパティ。共に型はQVariantです。つまり、QVariant型の2つの値間を補間して、アニメーションが行われていることになります。

QVariantには、QStringやQPixmapなども格納できますが、このような型のプロパティには、自明な補間方法がないので、プロパティのアニメーションの対象ではありません。アニメーション可能なプロパティの型は、表1のようになっています。

表1 アニメーション対応のプロパティ一覧
メタタイプ実際の型
QMetaType::Intint
QMetaType::Doubledouble
QMetaType::Floatfloat
QMetaType::QLineQLine
QMetaType::QLineFQLineF
QMetaType::QPointQPoint
QMetaType::QSizeQSize
QMetaType::QSizeFQSizeF
QMetaType::QRectQRect
QMetaType::QRectFQRectF

したがってQWidgetでは、表1に挙げた型を持つプロパティ、たとえばpos(QPoint⁠⁠、geometry(QRect⁠⁠、windowOpacity(double)などのプロパティがアニメーションの対象になります。最初のサンプルプログラムでは、posプロパティを変更してラベルを横方向に移動しているので、xプロパティをアニメーションしても同様な効果が得られます。

キーバリューによるアニメーション速度の変更

一定の速度でラベルを移動させるのではなく、キーバリュー指定によって、アニメーションの速度を変更してみましょう。リスト1の68~73行目を次のように変更します。

リスト2 ラベル移動速度を変化させる場合の変更点
68:     d->labelAnimation = new QPropertyAnimation( movableLabel, "pos", this );
69:     d->labelAnimation->setDuration( 5 * 1000 );
70:     int movableLabelStartX = d->horizontalOffset;
71:     int movableLabelEndX = width() - movableLabel->sizeHint().width() - d->horizontalOffset;
72:     int movableLabelInterimX = movableLabelStartX + ( movableLabelEndX - movableLabelStartX ) * 9 / 10;
73:     d->labelAnimation->setKeyValueAt( 0.0, QPoint( movableLabelStartX, movableLabelY ) );
74:     d->labelAnimation->setKeyValueAt( 0.5, QPoint( movableLabelInterimX, movableLabelY ) );
75:     d->labelAnimation->setKeyValueAt( 1.0, QPoint( movableLabelEndX, movableLabelY ) );

setStartValue()とsetEndValue()の代わりにsetKeyValueAt()を用いて、第一引数で時間経過の割合を0.0から1.0の間の値で指定し、第二引数で対応する経過値を指定します。この場合には図3に示すように、アニメーション時間が5秒で、0.5の割合の時間経過での経過値を9割の位置にしているので、アニメーションの開始から 2.5秒経過した時刻で、9割の移動が行われるようになります。

図3 キーバリューによる速度変更の概要
図3 キーバリューによる速度変更の概要

この修正をしたサンプルプログラムを動作させると、次のようなアニメーションになります。

移動速度が変化するアニメーション

イージングによるアニメーション速度の変更

イージングとは、ボールが跳ねたりする動きのように、加速度を考慮したアニメーションをを実現する方法です。イージングによって、加速、減速、加減速、跳ねるなどのアニメーション効果を得られます。この場合は、リスト1の68行目~を次のように変更します。

リスト3 イージングによる速度コントロールを可能にさせる
68:     d->labelAnimation = new QPropertyAnimation( movableLabel, "pos", this );
69:     d->labelAnimation->setDuration( 5 * 1000 );
70:     int movableLabelStartX = d->horizontalOffset;
71:     int movableLabelEndX = width() - movableLabel->sizeHint().width() - d->horizontalOffset;
72:     d->labelAnimation->setStartValue( QPoint( movableLabelStartX, movableLabelY ) );
73:     d->labelAnimation->setEndValue( QPoint( movableLabelEndX, movableLabelY ) );
74:     d->labelAnimation->setEasingCurve( QEasingCurve::OutBounce );

最初のサンプルプログラム(リスト1)に74 行目の行を追加すると、横向きの移動ですが、次の動画のように、跳ねるようなアニメーション効果が得られます。

イージングを使うには、setEasingCurve() で、イージングカーブをアニメーションに指定します。ここで指定している QEasingCurve::OutBounce は、アニメーション動作が終了する前(Out)に、跳ねる(Bounce)ように位置が変わります。

Qtでは、Robert PennerのイージングカーブをQEasingCurveで実装しています。イージングは、表2の4つの加速/減速方法と10種類の補間曲線を組み合わせて、40種類あります。

表2 イージングの種類
加速と減速方法
In加速
Out減速
InOut加速後に減速
OutIn減速後に加速
補間曲線
Sine正弦曲線に沿う
Quad2 次曲線に沿う
Cubic3 次曲線に沿う
Quart4 次曲線に沿う
Quint5 次曲線に沿う
Expo指数曲線に沿う
Circ円弧に沿う
Elastic弾む動き
Back行き過ぎて戻る動き
Bounce跳ねる動き

Qtで用意されているイージングには、これらに加えLinear(加減速のない線形補間)や、ユーザ定義のCustomがあります。Qtのパッケージ中のexamples/animation/easingディレクトリにあるEasing Curves Exampleで、イージングカーブが適用されたアニメーションの動作を確かめられます。

おすすめ記事

記事・ニュース一覧