Roger's Clock - 您的第一个 MoSync 应用程序
第 3 篇文章:
Android 教程竞赛
这是我为文章 #3 提交的作品:创建一个简单的“Hello World”Android 项目。它主要的亮点在于它描述了如何使用 MoSync,一个不太为人知的开发环境,但它的优点是支持广泛的移动平台,而不仅仅是 Android。
引言
Roger 是我岳父。像他这个年纪的很多人一样,他有短期记忆问题,这给他的家人带来了很大的困扰,他们不得不应对他反复提出的问题
-
今天星期二吗?
-
是的,今天是星期二。
-
(两分钟后)今天是星期三吗?
-
不,今天是星期二。
经过一番搜索,我找到了解决方案:一个日历时钟(http://www.dayclox.com)。
订购它。
缺货。可用性未知。
但是等等!我有一个旧的 Android 平板电脑闲置着(因为它处理器很慢,只有大约 1GB 内存)。这是我开始使用 MoSync 进行 Android 开发的机会。
下载并安装 MoSync
我在另一篇文章中详细介绍了如何下载和安装 MoSync。安装包含几个值得学习的示例项目。我在这里展示的许多内容都可以在这些示例中找到。
Specification
软件开发第一条规则:在开始编码之前,决定你想做什么。
在这种情况下,设计非常简单。我想要一个持续显示星期几、一天中的时间以及日期的功能。这显然需要定期更新,并且点击 **返回** 按钮应该退出程序。哦,并且最好能够适应屏幕大小和方向。
第一步:创建新项目
启动 MoSync 并选择菜单 **文件 – 新建 – 项目**
项目向导将出现
选择项目类型(此处为 C++ MoSysnc 项目)并单击“下一步”
在提供的项目类型中进行选择。此处我选择了一个 C++ Moblet 项目。Moblet 是 MoSync 的事件处理框架,可以轻松处理屏幕点击和其他事件。然后单击“下一步”
为项目提供一个名称。名称中不得包含空格或标点符号,否则在生成的代码中会出现问题。您还可以指定存储项目的文件夹。此处我仅保留了默认路径。然后单击“完成”,MoSync 将构建项目。
左侧的项目浏览器显示组成项目的文件(此处仅为 main.cpp)。通过展开文件,您可以看到包含的文件、命名空间、类和成员函数。编辑器窗口显示代码;向导已经填写了一些事件处理程序。
尝试默认项目
既然我们已经有了这个免费的代码,不妨尝试一下。按 Ctrl-B 构建项目
项目浏览器中会添加一个新条目(“Release Packages”)。您可以展开它找到 **RogersClock.apk**,然后右键单击在模拟器中运行它。随意单击模拟器窗口中的任何位置或在键盘上输入以测试提供的事件处理程序。
关闭应用程序(在模拟器中使用 Escape 键)后,查看模拟器中的应用程序页面。您会看到带有默认 MoSync 图标的 Roger 时钟。
查看提供的事件处理程序
这是怎么回事?这是 MoSync 提供的 main.cpp 文件,
#include <MAUtil/Moblet.h> #include <conprint.h> using namespace MAUtil; /** * A Moblet is a high-level class that defines the * behaviour of a MoSync program. */ class MyMoblet : public Moblet { public: /** * Initialize the application in the constructor. */ MyMoblet() { printf("Press zero or back to exit\n"); } /** * Called when a key is pressed. */ void keyPressEvent(int keyCode, int nativeCode) { if (MAK_BACK == keyCode || MAK_0 == keyCode) { // Call close to exit the application. close(); } // Print the key character. printf("You typed: %c\n", keyCode); } /** * Called when a key is released. */ void keyReleaseEvent(int keyCode, int nativeCode) { } /** * Called when the screen is touched. */ void pointerPressEvent(MAPoint2d point) { // Print the x and y coordinate. printf("You touched: %i %i\n", point.x, point.y); } }; /** * Entry point of the program. The MAMain function * needs to be declared as extern "C". */ extern "C" int MAMain() { Moblet::run(new MyMoblet()); return 0; }
核心是一个类(MyMoblet),带有一个构造函数和三个事件处理程序。构造函数打印初始欢迎消息“Press zero or back to exit”。请注意,使用的是标准的 printf() 函数,因此开始一个简单的应用程序非常容易。
当按下键时(自然),名为 **keyPressEvent()** 的事件处理程序会被调用,并将按键代码作为参数。如果字符是 0 或 Back,它将通过调用 **close()** 来结束应用程序。这很方便,已经满足了我们规范的一部分。否则,它只会显示按键(再次使用 **printf()**)。
相应的 **KeyReleaseEvent()** 事件处理程序也已定义,但它什么也没做。
最后,当屏幕被点击时,名为 **pointerPressEvent()** 的事件处理程序会被调用,并将点击位置作为参数。它只是显示点击的坐标。
除此之外,还有一个 **main()** 函数,它创建一个 **MyMoblet** 的实例。
重命名 Moblet 类
我不太喜欢“小 moblet 在我的平板电脑里跑来跑去”这个想法,听起来太暴力了。所以让我们从将 MyMoblet 更改为 RogersClock 开始,在源代码的每个地方。
从菜单中选择 **编辑-查找/替换** 或按 **Ctrl-F**。填写详细信息(不要忘记“区分大小写”和“整个单词”),然后单击 **全部替换**。请注意,我们也可以使用正则表达式。
再次编译并运行代码,只是为了检查。
第二步:显示日期和时间
让我们让它看起来更像一个时钟。与其只显示“您按下了……”,不如让它显示日期和时间,并在屏幕被点击时更新。
为此,我们只需要更改 **pointerPressEvent()** 处理程序。
问题。我们如何获取日期和时间?
MoSync 包含一个完整的 C++ API。文档已随 MoSync 一起安装;您可以通过选择菜单 **帮助 – MoSync C++ API 参考** 来阅读它。
文档将在 Internet Explorer 中打开。键入 **Ctrl-F** 进行搜索,然后键入 **Time**。
由此,我们可以看到有三个函数处理时间和日期:maGetMilliSecondCount()、maTime() 和 maLocalTime(),它们在 **matime.h** 中定义。通过点击 **maLocalTime()**,我们看到它返回自 1970 年以来经过的秒数。点击 **matime.h** 显示有各种可能对我们有帮助的转换函数,特别是 **sprint_time()**,它将时间值转换为一个静态分配的字符串。
所以现在看起来很容易了。我们只需要包含 **matime.h** 并更改 **pointerPressEvent()** 处理程序来获取时间并将其转换为字符串,而不是打印固定的消息。
void pointerPressEvent(MAPoint2d point) { // get the current time time_t now = maLocalTime(); // display the time printf("%s\n", sprint_time(now)); }
结果如下
第三步:定期更新屏幕
好的,现在我们每次点击屏幕都能看到日期和时间。但如果它能自动更新就更好了。我们需要一个东西每秒或每隔一段时间向应用程序发送信号,并更新显示。实际上,是一个计时器。幸运的是,MoSync 允许我们设置一个警报,在请求时发送一个事件。
这里感兴趣的函数是
-
addTimer() - 请求一个定时的事件
-
runTimerEvent() - 接收到定时事件时调用的处理程序
我们需要对代码进行三处更改
-
主要的 **RogersClock** 类必须继承自 **TimerListener** 才能接收计时器事件。
-
**pointerPressEvent()** 处理程序必须变为 **runTimerEvent()**(无参数)。
-
构造函数应调用 **addTimer()**。
这是更改
class RogersClock : public Moblet, public TimerListener { public: /** * Initialize the application in the constructor. */ RogersClock() { printf("Press zero or back to exit\n"); // Arrange to call runTimerEvent() every second addTimer(this, 1000, 0); } ... /** * Called when the alarm rings */ void runTimerEvent() { // get the current time time_t now = maLocalTime(); // display the time printf("%s\n", sprint_time(now)); } };
结果如下
我承认它看起来没太大区别,但它每秒会自动更新一次,而不是等待我点击屏幕。
第四步:让它看起来更好
一切正常,但我们确实需要一个更简洁、更清晰的显示。所以,我们不再使用 **printf()**,而是必须开始处理字体和图形显示。另外,缩写(Sat、Jul)帮助不大。
字体
为了处理文本大小,我们可以创建自己的字体。它们应该基于屏幕的大小,幸运的是,MoSync 提供了 **maGetScrSize()** 函数来实现这一点。
MAExtent ex = maGetScrSize(); mScreenWidth = EXTENT_X(ex); mScreenHeight = EXTENT_Y(ex)
假设我们想要三行文本:一行显示日期,一行显示星期几,一行显示时间。我们将使它们的大小不同,并在它们之间留出一些空间。
一种简单的方法是取高度和宽度中较小的一个,除以十,并以此作为字体大小的基础。然后,每个字体的大小都可以是该值的倍数。
mLineHeight = ((mScreenWidth < mScreenHeight) ? mScreenWidth : mScreenHeight) / 10; // Prepare fonts mDateFont = maFontLoadDefault(FONT_TYPE_SANS_SERIF, FONT_STYLE_BOLD, mLineHeight); mDayFont = maFontLoadDefault(FONT_TYPE_SANS_SERIF, FONT_STYLE_BOLD, 3 * mLineHeight); mTimeFont = maFontLoadDefault(FONT_TYPE_SANS_SERIF, 0, 2 * mLineHeight);
显然,我们只想做一次,所以我们会把它放在一个单独的函数(**GetScreenParameters()**)中,并从构造函数中调用它。
颜色
MoSync 附带的 Hello World 示例展示了一个向屏幕输出彩色文本的简单示例。
//The first syscall sets the background colour. maSetColor(0xFFFFFF), //The second syscall writes the text "Hello World" to the backbuffer. maDrawText(0, 32, "Hello World!"); //The third syscall copies the contents of the backbuffer to the physical screen. maUpdateScreen();
我们将使用这些函数分别设置每一行的颜色。
图形显示
与大多数图形显示一样,MoSync 不会直接绘制到屏幕。它会在后台缓冲区构建新图像,然后在请求时将其写入屏幕。所以我们需要在写入新时间之前清除这个缓冲区。这只需要这样做:
maSetColor(0); // black pen maFillRect(0, 0, mScreenWidth, mScreenHeight); // cover the screen with black
保持屏幕常亮
Android 设备会在几秒钟后关闭背光,以节省电池。这对这个应用程序来说不太理想。只需在更新屏幕后添加一行,就可以重置背光计时器并保持显示亮起。
maResetBacklight();
不要使用缩写
是时候告别 **sprint_time()** 了。取而代之的是,我们可以使用 **split_time()**,它返回熟悉的 **tm** 结构,时间被分解成各个部分。我们必须包含我们自己的星期和月份名称表。我们确实应该本地化它们,但这对于一个入门项目来说要求太高了。
整理
更新屏幕开始变得有些复杂,所以我们将所有代码移到一个单独的函数中,并让处理程序调用它。这使得事情更加整洁。将前面的部分放在一起,这是结果代码:
// Days of the week. We should localize this char *Day[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; // Days of the week. Ditto char *Month[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; // Display colours. We should make these into parameters #define DAY_COLOUR 0x0000FF #define TIME_COLOUR 0x00FF00 #define DATE_COLOUR 0xFF0000 /** * Get the size of the screen and create fonts based on it */ void GetScreenParameters() { // Calculate the font size MAExtent ex = maGetScrSize(); mScreenWidth = EXTENT_X(ex); mScreenHeight = EXTENT_Y(ex); mLineHeight = ((mScreenWidth < mScreenHeight) ? mScreenWidth : mScreenHeight) / 10; // Prepare fonts mDateFont = maFontLoadDefault(FONT_TYPE_SANS_SERIF, FONT_STYLE_BOLD, mLineHeight); mDayFont = maFontLoadDefault(FONT_TYPE_SANS_SERIF, FONT_STYLE_BOLD, 3 * mLineHeight); mTimeFont = maFontLoadDefault(FONT_TYPE_SANS_SERIF, 0, 2 * mLineHeight); } /** * Display a line of text, centered, with the specified font, * colour and vertical position */ void CentreText(MAHandle font, int colour, int y, char *text) { maFontSetCurrent(font); maSetColor(colour); maDrawText((mScreenWidth - EXTENT_X(maGetTextSize(text))) / 2, y, text); } /** * Display the current date and time */ void UpdateDisplay() { // get the current time time_t timenow = maLocalTime(); // split the time into its components struct tm now; split_time(timenow, &now); //Print it CentreText(mDayFont, DAY_COLOUR, mLineHeight, Day[now.tm_wday]); sprintf(line, "%02d:%02d:%02d", now.tm_hour, now.tm_min, now.tm_sec); CentreText(mTimeFont, TIME_COLOUR, 5 * mLineHeight, line); sprintf(line, "%d %s %d", now.tm_mday, Month[now.tm_mon], 1900 + now.tm_year); CentreText(mDateFont, DATE_COLOUR, 8 * mLineHeight, line); // Update the screen maUpdateScreen(); maResetBacklight(); // Clear the screen, ready for the next update maSetColor(0); maFillRect(0, 0, mScreenWidth, mScreenHeight); }
这是显示效果
第五步:尊重屏幕方向
嗯,我们快完成了,但显然屏幕方向有问题(您可能已经注意到了,因为我不得不将所有屏幕截图都转过来。同样,这并不难做到。
最简单的方法,而且对于这种应用程序来说很合适,就是强制应用程序进入横屏模式。将这一行添加到构造函数中即可实现:
maScreenSetOrientation(SCREEN_ORIENTATION_LANDSCAPE);
然而,处理方向变化的工作量并没有增加多少。首先,我们更改构造函数:
maScreenSetOrientation(SCREEN_ORIENTATION_DYNAMIC);
然后我们需要一个事件处理程序来处理方向变化。没有专门的方向变化处理程序,只有一个名为 **customEvent()** 的通用处理程序。它需要检测方向变化,然后删除并重新创建字体。
void customEvent(const MAEvent& event) { //If the event type is screen changed... if (event.type == EVENT_TYPE_SCREEN_CHANGED) { // release the fonts maFontDelete(mDayFont); maFontDelete(mDateFont); maFontDelete(mTimeFont); // fix parameters and display the clock GetScreenParameters(); } }
最终应用程序如下:
对于这样一个简单的应用程序来说,这已经很不错了,并且对 Roger 来我们家拜访时非常有帮助。
在您的设备上安装并运行应用程序
如果您的设备已连接且处于调试模式,您可以将应用程序下载到设备上。以下是它在我 Galaxy Tab 上的样子(抱歉照片拍得不太好):
未来改进
显然,还有一些事情要做来改进这个应用程序。然而,这篇文章并不是关于这个特定应用程序的;它只是为了帮助您开始使用 MoSync。所以我只提几件我想更改的事情:
-
星期和月份的名称是内置的。最好从系统或配置文件中获取它们,并允许选择语言和颜色。
-
字体大小仅基于屏幕高度。结果,像 Wednesday 这样的长单词在纵向模式下往往会被截断。
结束语
顺便说一句,Dayclox 数字日历时钟现在又可以购买了。它有点贵,但对我的岳父和他的家人来说非常有用,我强烈推荐它。我与这家公司唯一的联系就是作为一个客户。