多屏应用
在本教程中,您将学习如何在应用程序中创建多个屏幕以及如何在两个屏幕之间共享数据。我们将创建一个模拟时钟的应用程序,它将使用一个屏幕设置小时和分钟,并将设置的时间传递给另一个带有运行时钟的屏幕。
您还将学习如何使用TouchGFX Designer根据交互(例如屏幕更改)创建动画。
可以从此链接下载本教程中将使用的图像 。将文件解压缩到磁盘上的目录。
设置屏幕1
创建的第一个屏幕是Screen1,它是可以设置小时和分钟并将其与运行时钟一起发送到屏幕的屏幕。屏幕1如下所示,其中包含两个文本框,其中包含所需的时钟时间。要调整框中的值,请使用向上和向下箭头。为了保存所选值并将其传递到时钟上,使用了相应框下方的保存按钮。最后,通过按时钟按钮,随时钟切换到Screen2。
插入背景和文本区域的操作如下图和表格所示。
位置 | 小部件 | 物产 |
1个 | 图片 |
|
2 | 文字区 |
|
3 | 文字区 |
|
4 | 文字区 |
|
5 | 文字区 |
|
这些按钮将插入到应用程序中,如下图和表所示。
位置 | 小部件 | 物产 |
1个 | 纽扣 |
|
2 | 纽扣 |
|
3 | 纽扣 |
|
4 | 纽扣 |
|
5 | 带标签的按钮 |
|
6 | 带标签的按钮 |
|
7 | 带标签的按钮 |
|
通过设置图形元素,下一步是添加按钮的触发器,按钮将调整所选值并保存它们:
相互作用 | 物产 |
单击小时增加按钮 |
|
单击小时减少按钮 |
|
单击分钟向上按钮 |
|
单击分钟向下按钮 |
|
单击“节省时间”按钮 |
|
单击“保存分钟”按钮 |
|
利用设计人员生成的虚拟功能,我们首先集成了箭头按钮的四个功能。为了跟踪小时和分钟的值,还添加了两个计数器。
因此,以下代码应添加到Screen1View.hpp
Screen1View.hpp
...
public:
...
virtual void buttonHourUpClicked();
virtual void buttonHourDownClicked();
virtual void buttonMinuteUpClicked();
virtual void buttonMinuteDownClicked();
protected:
int16_t hour;
int16_t minute;
...
当按下箭头时,相应的值将更改为适合时钟的值。
应当以以下方式添加这四个功能的逻辑。
Screen1View.cpp
...
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();
}
设置屏幕2
第二个屏幕Screen2是运行时钟的放置位置,从Screen1中保存的值开始。除时钟外,Screen2还包含一个围绕时钟设置动画的圆圈,表示时钟正在运行。最后,返回到Screen1,实现了将屏幕更改为Screen1的按钮。
在将元素添加到Screen2之前,必须创建一个新屏幕。这是通过TouchGFX Designer中的添加屏幕按钮完成的,如下图所示。
当进入“ screen2”时,时钟和动画都通过进入视图而动画到其位置,时钟从左侧移入,圆圈从右侧移入。因此,这两个小部件最初放置在TouchGFX Designer中的画布外部。
窗口小部件的放置应按照下面的图片和表格进行。
位置 | 小部件 | 物产 |
1个 | 图片 |
|
2 | 纽扣 |
|
3 | 文字区 |
|
4 | 圈 |
|
在屏幕之间切换
屏幕1上的时钟按钮和屏幕2上的配置按钮需要交互才能切换屏幕。此外,输入Screen2时,需要将放置在Screen2上屏幕区域之外的两个元素移到适当位置。
将以下交互添加到Screen1:
相互作用 | 物产 |
“更改为Screen2” |
|
还将以下交互添加到Screen2:
相互作用 | 物产 |
更改为“ Screen1” |
|
将圆移到位 |
|
将文字时钟移到位 |
|
要在运行时更新时钟并为圆圈设置动画,请handleTickEvent
使用虚拟功能 。
handleTickEvent
由TouchGFX框架定期调用,使它能够动态更新活动屏幕中的元素,在这种情况下将是时钟和循环。
类似于Screen1,一个小时和一个分钟计数器用于跟踪时钟。由于 handleTickEvent
调用频率比应更新的时钟频率高,因此添加了tickCounter来确定时钟更新之间的滴答数。为了更新圆弧的角度,使用了addStart和addEnd函数。handleTickEvent
如下所示,将函数和变量添加到Screen2View.hpp中。
Screen2View.hpp
public:
...
virtual void handleTickEvent();
protected:
int16_t hour;
int16_t minute;
int16_t tickCount;
int16_t addStart;
int16_t addEnd;
...
集成了handleTickEvent
Screen2View.cpp,从而更新了时钟和圆圈的代码如下所示
Screen2View.hpp
...
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.invalidate();
circle.setArc(circle.getArcStart() + addStart, circle.getArcEnd() + addEnd);
circle.invalidate();
}
}
...
- 请记住,将用于小时和分钟的印刷术的通配符范围
0-9
列添加到列中,以使TouchGFX生成所有数字的字形。
在这一步中,使用了小部件Circle来了解有关Circle的更多信息,文章 Circle描述了该小部件及其属性。
在下一步中,将添加Screen1上保存按钮的代码,以允许在Screen1和Screen2之间共享数据。
步骤2:储存资料
在此步骤中,我们将显示在屏幕之间切换时如何保存数据以及如何检索保存的数据。
在模型中保存数据
为了将在视图(即屏幕)中操作的数据持久化,应该将数据发送到模型(通过演示者)。有关Model-View-Presenter模式的更多信息,可以在Internet上的许多地方找到,例如Wikipedia,或在 “屏幕概念和Model-View-Presenter”一文中找到。
将小时和分钟添加到模型
该模型负责保存应用程序的数据。临时数据(例如按钮状态,当前可见的小部件等)不应在模型中。
要通过模型保存和检索数据,请向模型添加受保护的小时和分钟值,以及用于访问这些值的公共函数:
型号
...
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;
...
确保在构造函数中初始化小时和分钟:
模型.cpp
...
Model::Model() : modelListener(0), hour(0), minute(0)
{
}
...
使用此代码,小时和分钟在模型中占有一席之地。由于该模型适用于所有演示者,因此建议在演示者(和视图)之间共享信息。该模型也是UI能够连接到系统其余部分(例如硬件外围设备和其他软件模块)的地方。
从视图访问模型
现在,为了从视图访问模型中的数据,演示者应该提供允许Screen1View从模型中加载和保存数据的函数,如下所示:
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以从Model中获取这些值,而不是仅使用局部变量。
更新画面1
现在,我们可以使用模型中的值在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);
}
...
为了保存小时和分钟值,在Screen1View.hpp中实现了在交互作用下为两个保存按钮创建的虚拟函数,并将这些值存储在模型中(通过演示者):
Screen1View.hpp
...
public:
virtual void buttonSaveHourClicked()
{
presenter->saveHour(hour);
}
virtual void buttonSaveMinuteClicked()
{
presenter->saveMinute(minute);
}
...
现在,Screen1从模型中获取小时和分钟的初始值。
更新Screen2
Screen2还需要将其值与模型同步。
与屏幕1相似,文本时钟中显示的初始值必须与来自模型的数据匹配。
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的内容,但是需要进一步改进,并在下一步中找到想法。
在下一个教程中,将介绍小部件“自定义容器”和“滚轮”,您将学习为小部件创建自定义行为。继续 教程4。
步骤3:进一步改进的想法
即使我们有一个运行中的应用程序,也可能缺少一些东西。这些事情值得考虑,如果不在本应用程序中,那么也许在现实世界中的应用程序中也应考虑。
如果模型支持某种警报功能,则每次时钟更新时,Screen2都必须将更新的时间发送给模型。
- 提示:Screen2View :: handleTickEvent()。
- 提示:如果小时/分钟实际上已更改,则只有在更新时才通知模型,而不是在每个刻度上都将更新通知模型。
同样,可以通过应用程序中的其他一些后台作业或硬件在模型中更改时钟。
- 提示:在更新值之前,先在滴答处理程序中从模型中读取分钟/小时,然后立即将它们写回到模型中。
- 提示:考虑添加一种锁定机制,以确保没有其他过程在更改模型中的时钟值
如果时钟长时间运行,则弧开始和弧结束可能会溢出,因为弧被存储为具有较高值的??整数。
- 提示:如果getArcStart()> 360,则从圆弧起点和圆弧终点都减去360是安全的。
将应用程序更改为在时钟屏幕而不是配置屏幕上启动。
- 提示:选择Screen2时,只需单击“设置为启动屏幕”按钮。
输入Screen2后立即开始计时(而不是在动画结束之后)。
- 提示:移动“ tickCounter ++;” 在“ if(!textClock.isMoveAnimationRunning())”之外。
屏幕1和屏幕2是此小示例可接受的名称。考虑将屏幕名称更改为更好的名称。
- 提示:例如ClockConfigScreen和RunningClockScreen。
- 提示:甚至可以使用ClockConfig和RunningClock。
更新视图中的通配符缓冲区涉及对snprintf()的两次调用。考虑将这些行移至单独的函数,以减少应用程序中的重复代码量。
- 提示:添加函数“ void updateClockText();” 在Screen1View.hpp中,并在Screen1View.cpp中添加实现。对Screen2View.hpp和Screen2View.cpp执行相同的操作。
更改屏幕过渡效果。
- 提示:如果过渡是“北”以转到一个屏幕,则返回的过渡应为相反的方向(“南”),以使两个屏幕彼此相邻放置。
【来源】