メイン・コンテンツまでスキップ

チュートリアル3: 複数スクリーンのアプリケーション

このチュートリアルでは、1つのアプリケーション内に複数のスクリーンを作成し、2つのスクリーン間でデータを共有する方法について学びます。 ここでは、時計をシミュレートするアプリケーションを作成します。1つのスクリーンを使用して時と分を設定し、動作中の時計がある別のスクリーンにその時刻を渡します。

さらに、TouchGFX Designerを使用して、スクリーン変更などのインタラクションに基づくアニメーションを作成する方法についても学習します。

このチュートリアルで使用する画像は、このリンクからダウンロードできます。 ご使用のディスク上のディレクトリにファイルを解凍してください。

ステップ1: 2つのスクリーンの設定

チュートリアル2と同じ手順で、新しいプロジェクトを作成します。 ここでも、グラフィックスは480 x 272の解像度で設計されています。 "STM32F746G Discovery Kit"のアプリケーション・テンプレートをベースにしたプロジェクトにするなら、この解像度になります。 解像度が異なるデモ・ボードを使用する場合は、それに一致するようにユーザ自身でグラフィックスを変更する必要があります。 独自のデモ・ボードを使用しない場合は、単純に"Simulator"アプリケーション・テンプレートをベースにして、解像度には必ず480 x 272と入力します。

Screen1の設定

最初に作成するスクリーンはScreen1です。このスクリーンでは時と分の値を設定し、クロックを実行しているスクリーンにそれを渡します。 以下に示すScreen1には、クロックに必要な時刻が格納された2つのテキスト・ボックスが含まれています。 ボックス内の値を調整するには、上下方向の矢印を使用します。 選択した値を保存してクロックに渡すには、それぞれのボックスの下にあるSaveボタンを使用します。 最後に、クロックの表示されるScreen2に切り替えるために、Clockボタンを押します。

まずは、アプリケーションにバックグラウンドとテキスト・エリアを挿入します。 下の画像や表に示すように、ウィジェットを挿入し、プロパティを更新します。

バックグラウンドとテキスト・エリアの位置が、ハイライトされたScreen1のスクリーンショット

場所ウィジェットプロパティ
1Image
  • Name: background
  • Location:
    • X: 0、Y: 0
  • Image: background_Screen1.png
2TextArea
  • Name: textAreaHourCaption
  • Location:
    • X: 86、Y: 46
    • W: 85、H: 24
  • Text:
    • Text: Hour
    • Typography: 20px
    • Alignment: Center
  • Appearance:
    • Color: #FFABABAB
3TextArea
  • Name: textAreaHour
  • Location:
    • X: 87、Y: 70
    • W: 83、H: 50
  • Text:
    • Text: <value>
    • Wildcard 1:
      • Use wildcard buffer: Yes
      • Initial Value: 00
      • Buffer size: 3
    • Typography: 40px
    • Alignment: Center
  • Appearance:
    • Color: #FFABABAB
4TextArea
  • Name: textAreaMinuteCaption
  • Location:
    • X: 309、Y: 46
    • W: 85、H: 24
  • Text:
    • Text: Minute
    • Typography: 20px
    • Alignment: Center
  • Appearance:
    • Color: #FFABABAB
5TextArea
  • Name: textAreaMinute
  • Location:
    • X: 311、Y: 70
    • W: 83、H: 50
  • Text:
    • Text: <value>
    • Wildcard 1:
      • Use wildcard buffer: Yes
      • Initial Value: 00
      • Buffer size: 3
    • Typography: 40px
    • Alignment: Center
  • Appearance:
    • Color: #FFABABAB

次に、下の画像と表に示すように、アプリケーションにボタンを挿入します。

ボタンの位置がハイライトされたScreen1のスクリーンショット

場所ウィジェットプロパティ
1Button
  • Name: buttonHourUp
  • Location:
    • X: 184、Y: 51
  • Image:
    • Released Image: Up_arrow.png
    • Pressed Image: Up_arrow_pressed.png
2Button
  • Name: buttonHourDown
  • Location:
    • X: 184、Y: 93
  • Image:
    • Released Image: Down_arrow.png
    • Pressed Image: Down_arrow_pressed.png
3Button
  • Name: buttonMinuteUp
  • Location:
    • X: 266、Y: 51
  • Image:
    • Released Image: Up_arrow.png
    • Pressed Image: Up_arrow_pressed.png
4Button
  • Name: buttonMinuteDown
  • Location:
    • X: 266、Y: 93
  • Image:
    • Released Image: Down_arrow.png
    • Pressed Image: Down_arrow_pressed.png
5Button With Label
  • Name: buttonSaveHour
  • Location:
    • X: 80、Y: 137
  • Text:
    • Text: Save
    • Typography: 25px
    • Alignment: Center
  • Text Appearance:
    • Released Color: #FF424242
    • Pressed Color: #FFA6A6A6
  • Image:
    • Released Image: btn_round.png
    • Pressed Image: btn_round_pressed.png
6Button With Label
  • Name: buttonSaveMinute
  • Location:
    • X: 303、Y: 137
  • Text:
    • Text: Save
    • Typography: 25px
    • Alignment: Center
  • Text Appearance:
    • Released Color: #FF424242
    • Pressed Color: #FFA6A6A6
  • Image:
    • Released Image: btn_round.png
    • Pressed Image: btn_round_pressed.png
7Button With Label
  • Name: buttonClock
  • Location:
    • X: 192、Y: 204
  • Text:
    • Text: Clock
    • Typography: 25px
    • Alignment: Center
  • Text Appearance:
    • Released Color: #FF424242
    • Pressed Color: #FFA6A6A6
  • Image:
    • Released Image: btn_round.png
    • Pressed Image: btn_round_pressed.png

設定したグラフィック要素を使用して、次のステップではボタンに対するトリガを追加します。これは、選択された値を調整して保存するものです。

インタラクションプロパティ
Hour up button is clicked(時のアップボタンをクリック)
  • Trigger: Button is clicked
  • Clicked Source: buttonHourUp
  • Action: Call new virtual function
  • Function Name: buttonHourUpClicked
Hour down button is clicked(時のダウンボタンをクリック)
  • Trigger: Button is clicked
  • Clicked Source: buttonHourDown
  • Action: Call new virtual function
  • Function Name: buttonHourDownClicked
Minute up button is clicked(分のアップボタンをクリック)
  • Trigger: Button is clicked
  • Clicked Source: buttonMinuteUp
  • Action: Call new virtual function
  • Function Name: buttonMinuteUpClicked
Minute down button is clicked(分のダウンボタンをクリック)
  • Trigger: Button is clicked
  • Clicked Source: buttonMinuteDown
  • Action: Call new virtual function
  • Function Name: buttonMinuteDownClicked
Save Hour button is clicked(時の保存ボタンをクリック)
  • Trigger: Button is clicked
  • Clicked Source: buttonSaveHour
  • Action: Call new virtual function
  • Function Name: buttonSaveHourClicked
Save Minute button is clicked(分の保存ボタンをクリック)
  • Trigger: Button is clicked
  • Clicked Source: buttonSaveMinute
  • Action: Call new virtual function
  • Function Name: buttonSaveMinuteClicked

"Generate Code"または"Run Simulator"を押すと、指定した仮想関数がDesignerによって生成されます。 まずは、矢印ボタン用の4つの関数を統合しましょう。 時と分の値をトラッキングするために、2つのカウンタも追加されます。

次に、以下のコードを追加します。

TouchGFX/gui/include/gui/screen1_screen/Screen1View.hpp
...
public:
...
virtual void buttonHourUpClicked();
virtual void buttonHourDownClicked();
virtual void buttonMinuteUpClicked();
virtual void buttonMinuteDownClicked();

protected:
int16_t hour;
int16_t minute;
...

矢印を押すと、クロックの値に合うように、対応する値が変更されます。

4つの関数のロジックは、次の方法で追加する必要があります。

TouchGFX/gui/src/screen1_screen/Screen1View.cpp
Screen1View::Screen1View()
: hour(0), minute(0) //clear the new counters
{
}
...
void Screen1View::buttonHourUpClicked()
{
hour = (hour + 1) % 24; // Keep new value in range 0..23
Unicode::snprintf(textAreaHourBuffer, TEXTAREAHOUR_SIZE, "%02d", hour);
textAreaHour.invalidate();
}

void Screen1View::buttonHourDownClicked()
{
hour = (hour + 24 - 1) % 24; // Keep new value in range 0..23
Unicode::snprintf(textAreaHourBuffer, TEXTAREAHOUR_SIZE, "%02d", hour);
textAreaHour.invalidate();
}

void Screen1View::buttonMinuteUpClicked()
{
minute = (minute + 1) % 60; // Keep new value in range 0..59
Unicode::snprintf(textAreaMinuteBuffer, TEXTAREAMINUTE_SIZE, "%02d", minute);
textAreaMinute.invalidate();
}

void Screen1View::buttonMinuteDownClicked()
{
minute = (minute + 60 - 1) % 60; // Keep new value in range 0..59
Unicode::snprintf(textAreaMinuteBuffer, TEXTAREAMINUTE_SIZE, "%02d", minute);
textAreaMinute.invalidate();
}

Screen2の設定

2つ目のスクリーンのScreen2には動作中のクロックが配置され、Screen1に保存された値から開始されます。 このクロック以外に、Screen2の構成要素には、クロックの周囲でアニメーション化されたサークルも含まれています。これはクロックが動作中であることを示します。 最後に、Screen1に戻るためのボタンがあり、これによってスクリーンがScreen1に変更されます。

シミュレータでアプリケーションを実行中のScreen2のスクリーンショット

Screen2に要素を追加する前に、新しいスクリーンを作成する必要があります。 このためには、下の画像に示すように、TouchGFX Designerの"Add Screen"ボタン(1)を使用します。

1

TouchGFX Designerの"Add Screen"ボタンの位置

ここでは、"Screen2"に移るときに、クロックとサークルがスクリーンの外からビュー内に移動する形で、それぞれの位置にアニメーション化したいと思います。クロックは左側から、サークルは右側から入ってくるようにします。 このため、この2つのウィジェットは、TouchGFX Designerのキャンバスの外側に、最初は配置されます。

ウィジェットの配置は、次の画像とその下の表のようにする必要があります。

Screen2のウィジェットの位置

場所ウィジェットプロパティ
1Image
  • Name: background
  • Location:
    • X: 0、Y: 0
  • Image:background_Screen2.png
1Button
  • Name: buttonSettings
  • Location:
    • X: 422、Y: 10
  • Image:
    • Released Image: configuration.png
    • Pressed Image: configuration.png
3TextArea
  • Name: textClock
  • Location:
    • X: 124、Y: 109
    • W: 124、H: 54
  • Text:
    • Text: <hour>:<min>
    • Wildcard 1:
      • Initial Value: 00
      • Use wildcard buffer: Yes
      • Buffer size: 3
    • Wildcard 2:
      • Initial Value: 00
      • Use wildcard buffer: Yes
      • Buffer size: 3
    • Typography: 40px
    • Alignment: Center
  • Appearance:
    • Color: #FFABABAB
4Circle
  • Name: circle
  • Location:
    • X: 479、Y: 36
    • W: 200、H: 200
  • Image & Color:
    • Color: #FFBABABA
  • Appearance:
    • Center Position:
      • X: 100、Y: 100
    • Start & End Angle:
      • Start: 0、End: 180
    • Radius: 72
    • Line Width: 6
    • Cap Style: Triangle

すべてのウィジェットを追加した後のスクリーンは、次のようになります。 TextAreaとCircleはスクリーンの外にあるので、表示されていません。

Screen2にすべてのウィジェットを追加

スクリーン間の切り替え

次に、2つのスクリーン間を切り替える機能を追加する必要があります。 このためには、Screen1のClockボタンとScreen2のConfigurationボタンに、インタラクションを割り当てます。 また、Screen2のスクリーン領域外に配置されている2つの要素が、Screen2に移ったときに、それぞれの位置に移動するようにする必要もあります。

このために、以下のインタラクションをScreen1に追加します。

インタラクションプロパティ
Change to "Screen2"("Screen2"に変更)
  • Trigger: Button is clicked
  • Clicked Source: buttonClock
  • Action: Change screen
  • Screen: Screen2
  • Transition: Cover
  • Transition Direction: North

さらに、以下のインタラクションをScreen2に追加します。

インタラクションプロパティ
Change to "Screen1"("Screen1"に変更)
  • Trigger: Button is clicked
  • Clicked Source: buttonSettings
  • Action: Change screen
  • Screen: Screen1
  • Transition: Slide
  • Transition Direction: South
Move circle into place(サークルを位置に移動)
  • Trigger: Screen transition ends
  • Action: Move widget
  • Widget to move: circle
  • Position: 140、36
  • Easing: Cubic、Out
  • Duration: 750ms
Move text clock into place(テキスト・クロックを位置に移動)
  • Trigger: Screen transition ends
  • Action: Move widget
  • Widget to move: textClock
  • Position: 178、109
  • Easing: Cubic、Out
  • Duration: 750ms

インタラクションの追加後、スクリーンは次のようになります。

Screen2にすべてのウィジェットを追加

クロックを更新して、実行時にサークルのアニメーションを動かすには、仮想関数handleTickEventを使用します。

handleTickEventはTouchGFXフレームワークによって(フレームごとに)周期的に呼び出されることで、アクティブなスクリーンで要素を動的に更新できるようになります。この場合、それはクロックとサークルです。

Screen1のときと同様に、時と分のカウンタを使用してクロックをトラッキングします。 handleTickEventはクロックの更新よりも高い頻度で呼び出されるので、クロック更新間のティック数を確認するためにtickCounterが追加されます。 サークル内の円弧の角度を更新するには、関数addStartおよびaddEndが使用されます。 handleTickEvent関数と変数が、以下に示すように、Screen2View.hppに追加されます。

TouchGFX/gui/include/gui/screen2_screen/Screen2View.hpp
public:
...
virtual void handleTickEvent();

protected:
int16_t hour;
int16_t minute;
int16_t tickCount;
int16_t addStart;
int16_t addEnd;
...

Screen2View.cpphandleTickEventを実装します。これにより、クロックとサークルを更新するコードは以下のようになります。

TouchGFX/gui/src/screen2_screen/Screen2View.cpp
Screen2View::Screen2View()
: hour(0), minute(0), tickCount(0), addStart(1), addEnd(2)
{ }
...
void Screen2View::handleTickEvent()
{
if (tickCount == 60)
{
minute++;
hour = (hour + (minute / 60)) % 24;
minute %= 60;

Unicode::snprintf(textClockBuffer1, TEXTCLOCKBUFFER1_SIZE, "%02d", hour);
Unicode::snprintf(textClockBuffer2, TEXTCLOCKBUFFER2_SIZE, "%02d", minute);

textClock.invalidate();

tickCount = 0;
}

if (!textClock.isMoveAnimationRunning())
{
tickCount++;
if (circle.getArcStart() + 340 == circle.getArcEnd())
{
addStart = 2;
addEnd = 1;
}
else if (circle.getArcStart() + 20 == circle.getArcEnd())
{
addStart = 1;
addEnd = 2;
}
circle.updateArc(circle.getArcStart() + addStart, circle.getArcEnd() + addEnd);
}
}
...

チュートリアル2で学習したように、ワイルドカード内で使用する文字を追加する必要があります。 この例では、TextAreasで使用するタイポグラフィ用に、__Wildcard Rangesの列に"0-9"を追加する必要があります。

Further reading
このステップでは、Circleウィジェットを使用しました。 Circleウィジェットの詳細については、「Circle(サークル)」ページを参照してください。

ステップ2: データの保存

このステップでは、スクリーン間の切り替え時にデータを保存する方法と、保存されたデータを取り出す方法を示します。

TouchGFXアプリケーションはモデル・ビュー・プレゼンタ(MVP: Model-View-Presenter)の設計パターンに従っているので、ビュー(スクリーンなど)内でデータを操作するためには、(プレゼンタを介して)データをModelクラスに送る必要があります。 モデル・ビュー・プレゼンタの設計パターンの詳細については、「モデル・ビュー・プレゼンタ(MVP: Model-View-Presenter)の設計パターン」ページを参照してください。

モデルへの時と分の追加

モデルには、アプリケーションのデータを保持する役目があります。 一時的なデータ(ボタンの状態や、現在表示されているウィジェットなど)をモデル内に含むことはできません。

モデルを介してデータを保存および取り出すには、保護された時と分の値を、これらの値にアクセスするためのパブリック関数と一緒にモデルに追加します。

TouchGFX/gui/include/gui/model/Model.hpp
...
public:
void saveHour(int16_t saveHour)
{
hour = saveHour;
}

void saveMinute(int16_t saveMinute)
{
minute = saveMinute;
}

int16_t getHour()
{
return hour;
}

int16_t getMinute()
{
return minute;
}

protected:
int16_t hour;
int16_t minute;
...
Note
int16_t タイプを 使用できるようにするには、 model.hppファイルに #include <touchgfx/hal/types.hpp> も含める必要があります。

コンストラクタで時と分を必ず初期化します。

TouchGFX/gui/src/model/Model.cpp
...
Model::Model() : modelListener(0), hour(0), minute(0)
{
}
...

このコードによって、時と分のモデル内での位置が決まり、スクリーンの変更時に保存できるようになります(スクリーン内に保存されたデータは、別のスクリーンへの変更時に失われます)。 モデルはすべてのプレゼンタで使用できるので、これは、プレゼンタ(およびビュー)間で情報を共有するための推奨される方法です。 モデルにより、UIがその他のシステム(ハードウェア周辺機器や他のソフトウェア・モジュールなど)に接続することもできます。

ビューからモデルへのアクセス

次に、モデル内のデータにビューからアクセスするため、プレゼンタは以下に示すように、Screen1Viewがモデルからデータを読み込んで保存できるようにする関数を提供する必要があります。

TouchGFX/gui/include/gui/screen1_screen/Screen1Presenter.hpp
...
public:
void saveHour(int16_t hour)
{
model->saveHour(hour);
}

void saveMinute(int16_t minute)
{
model->saveMinute(minute);
}

int16_t getHour()
{
return model->getHour();
}

int16_t getMinute()
{
return model->getMinute();
}
...

Screen2もモデル内のデータにアクセスできるようにする必要があるので、Screen2Presenter.hppにも同じ行を追加します。

モデルからのデータ

モデル内の時と分にアクセスするコードを配置できたので、ローカル変数のみを使用するのではなく、モデルからこれらの値を取得するように、Screen1Screen2を更新する必要があります。

Screen1の更新

次に、Screen1View内の時と分をモデルから取得した値で初期化し、テキスト・エリアのバッファを初期化します。

Screen1View.cpp
...
void Screen1View::setupScreen()
{
Screen1ViewBase::setupScreen();

hour = presenter->getHour();
minute = presenter->getMinute();

Unicode::snprintf(textAreaHourBuffer, TEXTAREAHOUR_SIZE, "%02d", hour);
Unicode::snprintf(textAreaMinuteBuffer, TEXTAREAMINUTE_SIZE, "%02d", minute);
}
...

時と分の値を保存するために、2つのSaveボタン用の仮想関数(インタラクションの下で作成されたもの)がScreen1View.hppに実装され、値がモデル内に(プレゼンタを介して)保存されます。

Screen1View.hpp
...
public:
virtual void buttonSaveHourClicked()
{
presenter->saveHour(hour);
}

virtual void buttonSaveMinuteClicked()
{
presenter->saveMinute(minute);
}
...

これで、Screen1が時と分の初期値をモデルから取得するようになります。

Screen2の更新

Screen2の値も、モデルと同期する必要があります。

Screen1と同様に、テキスト・クロックに表示される初期値が、モデルからのデータと一致する必要があります。

Screen2View.cpp
...
void Screen2View::setupScreen()
{
Screen2ViewBase::setupScreen();

hour = presenter->getHour();
minute = presenter->getMinute();

Unicode::snprintf(textClockBuffer1, TEXTCLOCKBUFFER1_SIZE, "%02d", hour);
Unicode::snprintf(textClockBuffer2, TEXTCLOCKBUFFER2_SIZE, "%02d", minute);
}
...

これで時と分がモデルからフェッチされます。 更新後の値は、ユーザが(Screen1の設定スクリーンに移動するために)このスクリーンを離れるときに、モデルに送り返される必要があります。

Screen2View.cpp
...
void Screen2View::tearDownScreen()
{
presenter->saveHour(hour);
presenter->saveMinute(minute);

Screen2ViewBase::tearDownScreen();
}
...

これで、設定スクリーンに移る直前に、時と分の更新された値がモデルに送られます。

これでこの小さいアプリケーションは完成し、チュートリアル3が終了しました。