教程3:有多个屏幕的应用
在本教程中,您将学习如何在一个应用中创建多个屏幕以及在两个屏幕之间共享数据。 我们将创建一个模拟时钟的应用,它将用一个屏幕来设置小时和分钟并将时间传递到另一个屏幕。
您还将学习如何使用TouchGFX Designer基于交互(如屏幕切换)创建动画。
本教程将使用的图像可从此链接下载。 将文件解压缩到磁盘上的目录。
第1步:设置两个屏幕
像教程2中所做的一样新建一个项目。 同样地,图形是为480x272的分辨率而设计的。 如果项目基于“STM32F746G探索套件”的应用模板,项目将使用此分辨率。 如果您有具有其他分辨率的演示板,应能修改图形以适应您的需求。 如果没有演示板,则直接基于“模拟器”应用模板,并务必输入480x272作为分辨率。
设置Screen1
要创建的第一个屏幕是Screen1,可以在该屏幕上设置小时和分钟值,然后发送到显示运行的时钟的屏幕。 Screen1显示如下,包含两个文本框,其中包含时钟所需的时间。 使用向上和向下箭头调整文本框中的值。 使用对应文本框下方的“保存”按钮保存选择的值并将它们传递到时钟。 最后,按下“时钟”按钮切换到显示时钟的Screen2。
我们首先为应用插入背景和文本区。 插入控件并更新属性,如下列图片和表格所示。
位置 | 控件 | 属性 |
---|---|---|
1 | 图像 |
|
2 | TextArea |
|
3 | TextArea |
|
4 | TextArea |
|
5 | TextArea |
|
然后,按下面的图片和表格所示将按钮插入应用。
位置 | 控件 | 属性 |
---|---|---|
1 | 按钮 |
|
2 | 按钮 |
|
3 | 按钮 |
|
4 | 按钮 |
|
5 | 带标签的按钮 |
|
6 | 带标签的按钮 |
|
7 | 带标签的按钮 |
|
在设置了图形元素后,下一步是为按钮添加触发条件,它会调整选择的值并保存值:
交互 | 属性 |
---|---|
点击“小时值增大”按钮 |
|
点击“小时值减小”按钮 |
|
点击“分钟值增大”按钮 |
|
点击“分钟值减小”按钮 |
|
点击“保存分钟值”按钮 |
|
点击“保存分钟值”按钮 |
|
如果您按下“生成代码”或“运行模拟器”按钮,TouchGFX Designer将生成指定的虚拟函数。 我们首先整合箭头按钮的四个函数。 为了记录小时和分钟值,还需添加两个计数器。
现在添加下列代码:
TouchGFX/gui/include/gui/screen1_screen/Screen1Presenter.hpp
...
public:
...
virtual void buttonHourUpClicked();
virtual void buttonHourDownClicked();
virtual void buttonMinuteUpClicked();
virtual void buttonMinuteDownClicked();
protected:
int16_t hour;
int16_t minute;
...
在按下箭头按钮时,对应的值会发生变化,以便符合时钟值的范围。
应按以下方式添加四个函数的逻辑:
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之前,必须新建一个屏幕。 这是通过TouchGFX Designer中的“添加屏幕”按钮 (1)(如下图所示)来完成的。
在进入“screen2”时,我们要让时钟和圆移动到它们的位置,从屏幕以外移动到视图中,时钟从左侧移入而圆从右侧移入。 因此,两个控件最初位于TouchGFX Designer中的画布之外。
应按照下面的图片和表格所示放置控件。
位置 | 控件 | 属性 |
---|---|---|
1 | 图像 |
|
1 | 按钮 |
|
3 | TextArea |
|
4 | 圆 |
|
在添加所有控件后,屏幕应如下图所示。 TextArea和Circle位于屏幕之外,因此不可见。
在屏幕之间切换
现在,我们需要添加功能,以便在两个屏幕之间切换。 为此,我们为Screen1上的“时钟”按钮和Screen2上的“配置”按钮分配交互。 此外,我们要在进入Screen2时让位于Screen2上屏幕区之外的两个元素移动到位。
为此,为Screen1添加下列交互:
交互 | 属性 |
---|---|
切换到“Screen2” |
|
此外,为Screen2添加下列交互:
交互 | 属性 |
---|---|
切换到“Screen1” |
|
将圆移动到位 |
|
将文本时钟移入到位 |
|
在添加交互后,屏幕如下图所示:
为了在运行时间更新时钟和使圆动画化,我们使用handleTickEvent
虚拟函数。
TouchGFX框架定期(每一帧)调用handleTickEvent
,能够动态地更新活动屏幕中的元素,在本例中为时钟和圆。
与Screen1类似,使用小时和分钟计数器使时钟正常运行。 由于handleTickEvent
的调用频率高于时钟的更新频率,因此增加了tickCounter来确定两次时钟更新之间的tick数。 使用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;
}
circle.updateArc(circle.getArcStart() + addStart, circle.getArcEnd() + addEnd);
}
}
...
根据教程2中所学内容,我们需要在通配符中添加使用的字符。 在本例中,您需要在供TextAreas使用的字体排印的“通配符范围”列中添加“0-9”。
Further reading
第2步:保存数据
在这一步中,我们将展示在屏幕间切换时如何保存数据,以及如何检索保存的数据。
TouchGFX应用遵循Model-View-Presenter设计模式,为了保存视图(即屏幕)中操作的数据,应将数据发送到Model类(通过表现器)。 关于Model-View-Presenter设计模式的更多信息,可以在“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
model.hpp
文件还应包括 #include <touchgfx/hal/types.hpp>
以便启用 int16类型
确保在构造函数中初始化小时和分钟:
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
。
模型中的数据
访问模型中小时和分钟值的代码已到位,应更新Screen1
和Screen2
以便从模型获取这些值,而不是只使用本地变量。
更新Screen1
现在,我们可以用模型中的值初始化Screen1View中的小时和分钟,并初始化TextAreas的缓冲区:
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类似,文本时钟中显示的初始值必须与模型中的数据一致。
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到此结束。