주요 내용으로 건너뛰기

자습서 3: 화면이 여러 개인 애플리케이션

이 자습서에서는 애플리케이션에서 여러 개의 화면을 생성하여 두 화면 간에 데이터를 공유하는 방법에 대해 알아보겠습니다. 한 화면에서 시와 분을 설정하고 시계가 작동하는 다른 화면으로 시간을 전달하는 시계를 시뮬레이션하는 애플리케이션을 생성하겠습니다.

또한 TouchGFX Designer를 사용하여 화면 변경과 같은 인터랙션을 기반으로 애니메이션을 생성하는 방법에 대해서도 알아보겠습니다.

이 자습서에서 사용할 이미지는 이 링크에서 다운로드할 수 있습니다. 다운로드한 파일을 디스크 디렉터리에 압축 해제합니다.

1단계: 2개의 화면 설정하기

자습서 2에서 한 것처럼 새 프로젝트를 생성합니다. 다시 말하지만, 그래픽은 480x272의 해상도에 맞춰 설계되었습니다. "STM32F746G Discovery Kit"의 애플리케이션 템플릿을 기반으로 프로젝트를 생성하는 경우 프로젝트는 이 해상도를 갖게 됩니다. 데모 보드의 해상도가 다를 경우 그래픽을 직접 수정하여 해상도에 맞출 수 있어야 합니다. 데모 보드가 없다면 "시뮬레이터" 애플리케이션 템플릿을 기반으로 하고 해상도를 480x272로 입력해야 합니다.

Screen1 설정하기

가전 먼저 생성할 화면은 Screen1입니다. Screen1은 시와 분 값을 설정하고 시계가 작동 중인 화면으로 전송할 수 있는 화면입니다. Screen1은 아래와 같으며 시계에 원하는 시간을 입력할 수 있는 두 개의 텍스트 상자를 포함하고 있습니다. 상자의 값을 조정할 때는 위쪽 화살표와 아래쪽 화살표를 사용합니다. 선택한 값을 저장하여 시계로 전달할 때는 각 상자 아래에 있는 저장 버튼을 사용합니다. 마지막으로, 시계가 있는 Screen2로 전환할 때는 Clock 버튼을 누르면 됩니다.

애플리케이션의 배경과 텍스트 영역을 삽입하는 것으로 시작하겠습니다. 그런 다음 위젯을 삽입하고 아래 그림 및 표에 표시된 대로 속성을 업데이트하겠습니다.

배경 및 텍스트 영역의 위치가 강조 표시된 Screen1의 스크린샷

위치위젯속성
1Image
  • 이름: background
  • 위치:
    • X: 0, Y: 0
  • 이미지: background_Screen1.png
2TextArea
  • 이름: textAreaHourCaption
  • 위치:
    • X: 86, Y: 46
    • W: 85, H: 24
  • 텍스트:
    • Text: Hour
    • Typography: 20px
    • Alignment: Center
  • 모양:
    • Color: #FFABABAB
3TextArea
  • 이름: textAreaHour
  • 위치:
    • X: 87, Y: 70
    • W: 83, H: 50
  • 텍스트:
    • Text: <value>
    • Wildcard 1:
      • Use wildcard buffer: Yes
      • Initial Value: 00
      • Buffer size: 3
    • Typography: 40px
    • Alignment: Center
  • 모양:
    • Color: #FFABABAB
4TextArea
  • 이름: textAreaMinuteCaption
  • 위치:
    • X: 309, Y: 46
    • W: 85, H: 24
  • 텍스트:
    • Text: Minute
    • Typography: 20px
    • Alignment: Center
  • 모양:
    • Color: #FFABABAB
5TextArea
  • 이름: textAreaMinute
  • 위치:
    • X: 311, Y: 70
    • W: 83, H: 50
  • 텍스트:
    • Text: <value>
    • Wildcard 1:
      • Use wildcard buffer: Yes
      • Initial Value: 00
      • Buffer size: 3
    • Typography: 40px
    • Alignment: Center
  • 모양:
    • Color: #FFABABAB

그런 다음 아래 그림 및 표에 표시된 대로 버튼을 애플리케이션에 삽입합니다.

버튼의 위치가 강조 표시된 Screen1의 스크린샷

위치위젯속성
1Button
  • 이름: buttonHourUp
  • 위치:
    • X: 184, Y: 51
  • 이미지:
    • Released Image: Up_arrow.png
    • Pressed Image: Up_arrow_pressed.png
2Button
  • 이름: buttonHourDown
  • 위치:
    • X: 184, Y: 93
  • 이미지:
    • Released Image: Down_arrow.png
    • Pressed Image: Down_arrow_pressed.png
3Button
  • 이름: buttonMinuteUp
  • 위치:
    • X: 266, Y: 51
  • 이미지:
    • Released Image: Up_arrow.png
    • Pressed Image: Up_arrow_pressed.png
4Button
  • 이름: buttonMinuteDown
  • 위치:
    • X: 266, Y: 93
  • 이미지:
    • Released Image: Down_arrow.png
    • Pressed Image: Down_arrow_pressed.png
5Button With Label
  • 이름: buttonSaveHour
  • 위치:
    • X: 80, Y: 137
  • 텍스트:
    • Text: Save
    • Typography: 25px
    • Alignment: Center
  • Text Appearance:
    • Released Color: #FF424242
    • Pressed Color: #FFA6A6A6
  • 이미지:
    • Released Image: btn_round.png
    • Pressed Image: btn_round_pressed.png
6Button With Label
  • 이름: buttonSaveMinute
  • 위치:
    • X: 303, Y: 137
  • 텍스트:
    • Text: Save
    • Typography: 25px
    • Alignment: Center
  • Text Appearance:
    • Released Color: #FF424242
    • Pressed Color: #FFA6A6A6
  • 이미지:
    • Released Image: btn_round.png
    • Pressed Image: btn_round_pressed.png
7Button With Label
  • 이름: buttonClock
  • 위치:
    • X: 192, Y: 204
  • 텍스트:
    • Text: Clock
    • Typography: 25px
    • Alignment: Center
  • Text Appearance:
    • Released Color: #FF424242
    • Pressed Color: #FFA6A6A6
  • 이미지:
    • 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"를 누르면 지정된 가상 함수가 TouchGFX 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 설정하기

두 번째 화면인 Screen2는 Screen1에서 저장한 값에서 시작하여 작동하는 시계가 있는 화면입니다. Screen2는 시계 외에 시계를 중심으로 움직이는 원으로 구성되어 있으며, 이 원을 통해 시계가 작동 중임을 알 수 있습니다. 마지막으로 Screen1로 돌아갈 수 있도록 화면을 Screen1으로 변경하는 버튼이 구현되어 있습니다.

시뮬레이터로 애플리케이션을 실행할 때 나타나는 Screen2의 스크린샷

요소를 Screen2에 추가하려면 먼저 새 화면을 생성해야 합니다. 아래 그림과 같이 TouchGFX Designer에서 “Add Screen” 버튼(1)을 사용하면 새 화면을 생성할 수 있습니다.

1

TouchGFX Designer에서 Add Screen 버튼이 있는 위치

"screen2"로 전환할 때, 애니메이션 효과를 통해 시계는 화면 밖 왼쪽에서 화면 안으로 들어오고 원은 화면 밖 오른쪽에서 화면 안으로 들어와서 제 위치로 이동하도록 하려고 합니다. 따라서 두 위젯은 처음에 TouchGFX Designer에서 캔버스 바깥쪽에 배치됩니다.

위젯은 아래의 그림과 표와 같이 배치됩니다.

Screen2의 위젯 위치

위치위젯속성
1Image
  • 이름: background
  • 위치:
    • X: 0, Y: 0
  • 이미지: background_Screen2.png
1Button
  • 이름: buttonSettings
  • 위치:
    • X: 422, Y: 10
  • 이미지:
    • Released Image: configuration.png
    • Pressed Image: configuration.png
3TextArea
  • 이름: textClock
  • 위치:
    • X: -124, Y: 109
    • W: 124, H: 54
  • 텍스트:
    • 텍스트: <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
  • 모양:
    • Color: #FFABABAB
4Circle
  • 이름: circle
  • 위치:
    • X: 479, Y: 36
    • W: 200, H: 200
  • Image & Color:
    • Color: #FFBABABA
  • 모양:
    • Center Position:
      • X: 100, Y: 100
    • Start & End Angle:
      • Start: 0, End: 180
    • Radius: 72
    • Line Width: 6
    • Cap Style: Triangle

모든 위젯을 추가한 화면의 모습은 아래와 같을 것입니다. TextArea와 원은 화면 밖에 있기 때문에 보이지 않습니다.

모든 위젯이 Screen2에 추가된 모습

스크린 전환

이제 두 화면 간에 전환하는 기능을 추가해야 합니다. 이를 위해 Screen1의 Clock 버튼과 Screen2의 Configuration 버튼에 인터랙션을 할당합니다. 또한 Screen2의 화면 영역 밖에 배치된 요소 2개가 Screen2 전환 시 제자리로 이동하게 하려고 합니다.

이를 위해 Screen1에 다음과 같은 인터랙션을 추가합니다.

인터랙션속성
"Screen2"로 변경
  • Trigger: Button is clicked
  • Clicked Source: buttonClock
  • Action: Change screen
  • Screen: Screen2
  • Transition: Cover
  • Transition Direction: North

Screen2에 다음과 같은 인터랙션도 추가합니다.

인터랙션속성
"Screen1"로 변경
  • Trigger: Button is clicked
  • Clicked Source: buttonSettings
  • Action: Change screen
  • Screen: Screen1
  • Transition: Slide
  • Transition Direction: South
원을 제자리로 이동
  • Trigger: Screen transition ends
  • Action: Move widget
  • Widget to move: circle
  • Position: 140, 36
  • Easing: Cubic, Out
  • Duration: 750ms
텍스트 시계를 제자리로 이동
  • 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.cpp에서 handleTickEvent를 구현하여 시계와 원을 업데이트하는 코드는 아래와 같습니다.

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;
}
if (circle.getArcStart() > 360 && circle.getArcEnd() > 360)
{
circle.updateArc(circle.getArcStart() - 360, circle.getArcEnd() - 360);
}
circle.updateArc(circle.getArcStart() + addStart, circle.getArcEnd() + addEnd);
}
}
...

자습서 2에서 살펴본 것처럼 와일드카드에 사용되는 문자를 추가해야 합니다. 여기에서는 TextArea에 사용되는 타이포그래피에서 Wildcard Ranges 열에 “0~9”를 추가해야 합니다.

Further reading
이 단계에서는 Circle 위젯을 사용했습니다. Circle 위젯에 관한 자세한 내용은 Circle 페이지를 참조하십시오.

2단계: 데이터 저장하기

이 단계에서는 화면 간에 전환할 때 데이터를 저장하는 방법과 저장된 데이터를 가져오는 방법에 대해 알아보겠습니다.

TouchGFX 애플리케이션은 모델-뷰-프레젠터 설계 패턴을 따릅니다. 따라서 뷰(예: 화면)에서 조작한 데이터를 유지하려면 프레젠터를 통해 모델 클래스로 데이터를 전송해야 합니다. 모델-뷰-프레젠터 설계 패턴에 대한 자세한 내용은 모델-뷰-프레젠터 설계 패턴 페이지를 참조하십시오.

모델에 시와 분 추가하기

모델은 애플리케이션의 데이터를 저장하는 역할을 합니다. 버튼 상태, 현재 표시되는 위젯 등과 같은 임시 데이터는 모델에 저장되지 않습니다.

모델을 통해 데이터를 저장하거나 가져오려면, 다음과 같이 보호된 시와 분 값과 이러한 값에 접근할 수 있는 공개 함수를 모델에 추가합니다.

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
또한 model.hpp 파일에 #include <touchgfx/hal/types.hpp>이 함께 추가되어야만 int16_t 유형을 int16_t 유형을 사용할 수 있도록 해야 합니다.

생성자에서 시와 분을 초기화해야 합니다.

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의 시와 분을 초기화하고 TextArea의 버퍼를 초기화할 수 있습니다.

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);
}
...

시와 분 값을 저장하기 위해, 두 개의 저장 버튼에 대한 인터랙션에서 생성된 가상 함수가 다음과 같이 Screen1View.hpp에 구현되어 프레젠터를 통해 모델에 값을 저장합니다.

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

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

이제 Screen1이 모델에서 시와 분의 초기 값을 가져옵니다.

Screen2 업데이트하기

Screen2도 해당 값을 모델과 동기화해야 합니다.

Screen1과 마찬가지로 텍스트 시계에 표시되는 초기 값은 Model에 저장된 데이터와 일치해야 합니다.

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을 마치겠습니다.