跳转到主要内容

教程3:有多个屏幕的应用

在本教程中,您将学习如何在一个应用中创建多个屏幕以及在两个屏幕之间共享数据。 我们将创建一个模拟时钟的应用,它将用一个屏幕来设置小时和分钟并将时间传递到另一个屏幕。

您还将学习如何使用TouchGFX Designer基于交互(如屏幕切换)创建动画。

本教程将使用的图像可从此链接下载。 将文件解压缩到磁盘上的目录。

第1步:设置两个屏幕

像教程2中所做的一样新建一个项目。 同样地,图形是为480x272的分辨率而设计的。 如果项目基于“STM32F746G探索套件”的应用模板,项目将使用此分辨率。 如果您有具有其他分辨率的演示板,应能修改图形以适应您的需求。 如果没有演示板,则直接基于“模拟器”应用模板,并务必输入480x272作为分辨率。

设置Screen1

要创建的第一个屏幕是Screen1,可以在该屏幕上设置小时和分钟值,然后发送到显示运行的时钟的屏幕。 Screen1显示如下,包含两个文本框,其中包含时钟所需的时间。 使用向上和向下箭头调整文本框中的值。 使用对应文本框下方的“保存”按钮保存选择的值并将它们传递到时钟。 最后,按下“时钟”按钮切换到显示时钟的Screen2。

我们首先为应用插入背景和文本区。 插入控件并更新属性,如下列图片和表格所示。

突出了背景和文本区位置的screen1屏幕截图

位置控件属性
1图像
  • 名称:background
  • 位置:
    • X: 0, Y: 0
  • 图像:background_Screen1.png
2TextArea
  • 名称:textAreaHourCaption
  • 位置:
    • X: 86, Y: 46
    • W: 85, H: 24
  • 文本:
    • 文本:小时
    • 字体排印:20px
    • 对齐:中心
  • 外观:
    • 颜色: #FFABABAB
3TextArea
  • 名称:textAreaHour
  • 位置:
    • X: 87, Y: 70
    • W: 83, H: 50
  • 文本:
    • 文本: <value>
    • 通配符: 1
      • 使用通配符缓冲区:有
      • 初始值: 00
      • 缓存大小:3
    • 字体排印:40px
    • 对齐:中心
  • 外观:
    • 颜色: #FFABABAB
4TextArea
  • 名称:textAreaMinuteCaption
  • 位置:
    • X: 309, Y: 46
    • W: 85, H: 24
  • 文本:
    • 文本:分钟
    • 字体排印:20px
    • 对齐:中心
  • 外观:
    • 颜色: #FFABABAB
5TextArea
  • 名称:textAreaMinute
  • 位置:
    • X: 311, Y: 70
    • W: 83, H: 50
  • 文本:
    • 文本: <value>
    • 通配符: 1
      • 使用通配符缓冲区:有
      • 初始值: 00
      • 缓存大小:3
    • 字体排印:40px
    • 对齐:中心
  • 外观:
    • 颜色: #FFABABAB

然后,按下面的图片和表格所示将按钮插入应用。

突出了按钮位置的screen1屏幕截图

位置控件属性
1按钮
  • 名称: buttonHourUp
  • 位置:
    • X: 184, Y: 51
  • 图像:
    • 释放图像:Up_arrow.png
    • 已按下图像:Up_arrow_pressed.png
2按钮
  • 名称: buttonHourDown
  • 位置:
    • X: 184, Y: 93
  • 图像:
    • 释放图像:Down_arrow.png
    • 已按下图像:Down_arrow_pressed.png
3按钮
  • 名称:buttonMinuteUp
  • 位置:
    • X: 266, Y: 51
  • 图像:
    • 释放图像:Up_arrow.png
    • 已按下图像:Up_arrow_pressed.png
4按钮
  • 名称:buttonMinuteDown
  • 位置:
    • X: 266, Y: 93
  • 图像:
    • 释放图像:Down_arrow.png
    • 已按下图像:Down_arrow_pressed.png
5带标签的按钮
  • 名称:buttonSaveHour
  • 位置:
    • X: 80, Y: 137
  • 文本:
    • 文本:保存
    • 字体排印:25px
    • 对齐:中心
  • 文本外观:
    • 释放颜色:#FF424242
    • 按下颜色: #FFA6A6A6
  • 图像:
    • 释放图像:btn_round.png
    • 按下图像:btn_round_pressed.png
6带标签的按钮
  • 名称:buttonSaveMinute
  • 位置:
    • X: 303, Y: 137
  • 文本:
    • 文本:保存
    • 字体排印:25px
    • 对齐:中心
  • 文本外观:
    • 释放颜色:#FF424242
    • 按下颜色: #FFA6A6A6
  • 图像:
    • 释放图像:btn_round.png
    • 按下图像:btn_round_pressed.png
7带标签的按钮
  • 名称:buttonClock
  • 位置:
    • X: 192, Y: 204
  • 文本:
    • 文本:时钟
    • 字体排印:25px
    • 对齐:中心
  • 文本外观:
    • 释放颜色:#FF424242
    • 按下颜色: #FFA6A6A6
  • 图像:
    • 释放图像:btn_round.png
    • 按下图像:btn_round_pressed.png

在设置了图形元素后,下一步是为按钮添加触发条件,它会调整选择的值并保存值:

交互属性
点击“小时值增大”按钮
  • 触发条件:点击按钮
  • 点击源:buttonHourDown
  • 操作:调用新的虚函数
  • 函数名称:buttonHourUpClicked
点击“小时值减小”按钮
  • 触发条件:点击按钮
  • 点击源:buttonHourDown
  • 操作:调用新的虚函数
  • 函数名称:buttonHourUpClicked
点击“分钟值增大”按钮
  • 触发条件:点击按钮
  • 点击源:buttonMinuteUp
  • 操作:调用新的虚函数
  • 函数名称:buttonMinuteUpClicked
点击“分钟值减小”按钮
  • 触发条件:点击按钮
  • 点击源:buttonMinuteDown
  • 操作:调用新的虚函数
  • 函数名称:buttonMinuteDownClicked
点击“保存分钟值”按钮
  • 触发条件:点击按钮
  • 点击源:buttonSaveHour
  • 操作:调用新的虚函数
  • 函数名称:buttonSaveHourClicked
点击“保存分钟值”按钮
  • 触发条件:点击按钮
  • 点击源:buttonSaveMinute
  • 操作:调用新的虚函数
  • 函数名称:buttonSaveMinuteClicked

如果您按下“生成代码”或“运行模拟器”按钮,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的屏幕截图

在将元素添加到Screen2之前,必须新建一个屏幕。 这是通过TouchGFX Designer中的“添加屏幕”按钮 (1)(如下图所示)来完成的。

1

TouchGFX Designer中“添加屏幕”按钮的位置

在进入“screen2”时,我们要让时钟和圆移动到它们的位置,从屏幕以外移动到视图中,时钟从左侧移入而圆从右侧移入。 因此,两个控件最初位于TouchGFX Designer中的画布之外。

应按照下面的图片和表格所示放置控件。

screen2中控件的位置

位置控件属性
1图像
  • 名称:background
  • 位置:
    • X: 0, Y: 0
  • 图像:background_Screen2.png
1按钮
  • 名称:buttonSettings
  • 位置:
    • X: 422, Y: 10
  • 图像:
    • 释放图像:configuration.png
    • 按下图像:configuration.png
3TextArea
  • 名称:textClock
  • 位置:
    • X: -124, Y: 109
    • W: 124, H: 54
  • 文本:
    • 文本:<hour>:<min>
    • 通配符: 1
      • 初始值: 00
      • 使用通配符缓冲区:有
      • 缓存大小:3
    • 通配符: 2
      • 初始值: 00
      • 使用通配符缓冲区:有
      • 缓存大小:3
    • 字体排印:40px
    • 对齐:中心
  • 外观:
    • 颜色: #FFABABAB
4
  • 名称:circle
  • 位置:
    • X: 479, Y: 36
    • W: 200, H: 200
  • 图像/颜色:
    • 颜色:#FFBABABA
  • 外观:
    • 中心位置:
      • X: 100, Y: 100
    • 起始角度和结束角度:
      • 开始:0,结束:180
    • 半径:72
    • 线条宽度: 6
    • 端点样式:三角形

在添加所有控件后,屏幕应如下图所示。 TextArea和Circle位于屏幕之外,因此不可见。

添加到Screen2的所有控件

在屏幕之间切换

现在,我们需要添加功能,以便在两个屏幕之间切换。 为此,我们为Screen1上的“时钟”按钮和Screen2上的“配置”按钮分配交互。 此外,我们要在进入Screen2时让位于Screen2上屏幕区之外的两个元素移动到位。

为此,为Screen1添加下列交互:

交互属性
切换到“Screen2”
  • 触发条件:点击按钮
  • 点击源:buttonHourUp
  • 操作:更改屏幕
  • 屏幕: Screen2
  • 转换:封面
  • 转换方向:北

此外,为Screen2添加下列交互:

交互属性
切换到“Screen1”
  • 触发条件:点击按钮
  • 点击源:buttonSettings
  • 操作:更改屏幕
  • 屏幕:Screen1
  • 转换:滑动
  • 转换方向:南
将圆移动到位
  • 触发条件:
  • 屏幕转换结束
  • 要移动的控件:circle
  • 位置:140, 36
  • 缓动:立方体,缓出
  • 时长:750ms
将文本时钟移入到位
  • 触发条件:
  • 屏幕转换结束
  • 要移动的控件:textClock
  • 位置:178, 109
  • 缓动:立方体,缓出
  • 时长:750ms

在添加交互后,屏幕如下图所示:

添加到Screen2的所有控件

为了在运行时间更新时钟和使圆动画化,我们使用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

模型中的数据

访问模型中小时和分钟值的代码已到位,应更新Screen1Screen2以便从模型获取这些值,而不是只使用本地变量。

更新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到此结束。