# 2. AWTK 应用构建

本章导读:

在学习计算机语言的时候,通常接触的第一个程序也就是最经典的 Hello World 程序。与之类似,要掌握 AWTK 的开发流程,也先创建一个 Hello World 应用程序,通过该程序了解 AWTK 的相关知识。

# 2.1 简介

本章以 HelloWorld.Xml-Demo 为例,介绍 AWTK 应用的构建流程。示例程序的主要功能如下:

  • 一个文本框用于显示"Hello World";
  • 一个按钮用于改变自身文本。

本章节所使用到的代码可以在 HelloWorld.Xml-Demo 目录下找到。

一般 AWTK 应用的实现流程详见下图,图中背景比较深的部分(如设计 UI 界面文件,实现业务逻辑)是编写应用程序的主要工作,其他部分可以复用,应用实现后就可以打包资源并编译程序代码了。

图2.1 AWTK应用的实现流程
图2.1 AWTK应用的实现流程

# 2.2 应用目录

# 2.2.1 应用的目录结构

此处以 HelloWorld.Xml-Demo 为例,简单介绍 AWTK 应用的目录结构:

目录/文件 说明
bin 可执行文件目录
design 原始资源目录(存放 AWTK Designer 设计时使用的资源)
res 经打包后的资源目录(打包时自动生成,应用程序运行时会使用该目录下的资源)
scripts 工具脚本目录(具体使用说明请查阅该目录下的README.md)
src 源代码目录
tests 单元测试代码目录
project.json 项目描述文件
SConstruct SCons 编译脚本

建议使用 AWStudio (opens new window) 中内置的 AWTK Designer 工具新建项目,以上文件以及目录都会自动生成。

# 2.2.2 应用的资源目录

在 AWTK 中,规定应用设计时的资源目录为 design,并且约定 design/default 的目录结构(此处的 default 为主题名称),各目录说明如下表所示(注意目录位置和名称不可随意更改):

目录 说明
fonts 字体文件目录,AWTK 目前支持矢量字库和点阵字
images 图片文件目录(包含 x1、x2、x3、xx 目录),AWTK目前支持的格式有 png、jpg、bmp、gif、svg
strings 多国语言文件目录,包含使用 xml 描述的翻译文本信息,所有语言环境下的文本信息都保存在 strings.xml 中,类似字典文件
styles 样式文件目录,包含使用 xml 描述的界面外观信息,即样式文件,比如下文所介绍的 default.xml
ui UI 文件目录,包含使用 xml 描述的界面结构信息,即UI文件,比如下文所介绍的 home_page.xml

应用运行时将使用 res 目录下的资源,该目录通过打包资源自动生成,如果修改了 design 目录下的资源,则需要重新打包资源让其生效。

# 2.2.3 images 目录说明

design/default/images 目录下可以建立的文件夹详见下表。

目录 说明
x1 存放普通密度屏幕上使用的图片
x2 存放2倍密度屏幕上使用的图片
x3 存放3倍密度屏幕上使用的图片
xx 存放与屏幕密度无关的图片
svg 存放 svg 图片

AWTK 运行时会优先在 x1、x2、x3 目录中的其中一个查找图片(比如LCD的DPR=1,则在x1目录),如果找不到,则在xx目录中查找。对于嵌入式系统,一般只需要 x1 或 xx 的图片。如果开发环境使用高清的 PC 显示器,为了方便 PC 上看效果,建议也准备一套 x2 或 x3 的图片。

AWTK 中使用资源时,资源的名称一般为文件名,且不包含文件后缀。

# 2.3 界面设计

# 2.3.1 手动编写UI设计界面

可以用文本编辑器(如:VSCode、Notepad++等)编写 UI 界面的 XML 文件,代码如下,

<!-- HelloWorld.Xml-Demo/design/default/ui/home_page.xml -->
<window name="home_page">
  <label name="label" x="c" y="m" w="30%" h="20%" style:normal:font_size="30" text="Hello World"/>
  <button name="button" x="c" y="m:100" w="100" h="36" text="True"/>
</window>

以上代码中使用的控件所代表的具体含义请看第 4 章。

# 2.3.2 使用 AWTK Designer 设计 UI 界面

AWTK Designer 是 AWStudio 中内置的工具,专门用来制作 AWTK 应用程序的 UI 界面的实用工具,只要通过拖曳和点击就可以完成复杂的界面设计,而且可以随时预览效果图。

前往 AWStudio云平台 (opens new window) 下载并安装 AWTK Designer 后,可以阅读 AWTK在线帮助文档 (opens new window) 中的《AWTK Designer用户手册》,了解如何使用 AWTK Designer 完成界面设计。

图2.2 AWTK Designer主界面
图2.2 AWTK Designer主界面

# 2.4 打包资源

在设计完 UI 界面后,需要将文件打包为 AWTK 可以使用的资源,在 HelloWorld.Xml-Demo 目录下打开终端,执行以下命令,生成样式、翻译文件、字体、图片、UI等资源文件。

python ./scripts/update_res.py all

执行完上面的命令后会在res文件夹中生成对应的资源文件,并实现资源读取的函数文件。

打包资源的注意事项如下:

  1. 在生成资源之前,请先参考上一章节编译 AWTK。
  2. 如果 design/default 目录下的资源文件发生变化(增加、删除,修改等),都需要重新执行该命令对资源文件进行打包。
  3. 该命令的参数可以查看 scripts 目录下的 README.md。

另外,也可以在 AWTK Designer 中打开项目,点击"项目"页面下的"打包"进行资源的打包。

# 2.5 代码编辑

# 2.5.1 代码结构

HelloWorld.Xml-Demo 的源代码文件在项目的 src 目录下,具体说明见下表:

源文件 说明
common 存放自动生成的导航器(navigator)源代码,用于页面跳转,可直接使用
pages 存放页面源代码,文件名与页面名相同
application.c 存放当前项目的初始化与反初始化源代码,可直接使用
main.c 存放当前项目的应用程序入口,可直接使用
SConscript Scons 编译脚本

# 2.5.2 主函数入口

main.c 中包含着 main 函数的入口(在 awtk_main.inc 中),这个文件用户可以直接套用到自己项目中,通常不需要修改,主要完成设置屏幕大小、资源的初始化以及 AWTK 框架的加载等,代码如下:

/* HelloWorld.Xml-Demo/src/main.c */
#include "awtk.h"

BEGIN_C_DECLS
#ifdef AWTK_WEB
#include "assets.inc"
#else /*AWTK_WEB*/
#include "../res/assets.inc"
#endif /*AWTK_WEB*/
END_C_DECLS

extern ret_t application_init(void);

extern ret_t application_exit(void);

#include "awtk_main.inc"

# 2.5.3 应用程序初始化

在 main.c 文件中额外声明了两个函数,分别是 application_init 和 application_exit,这两个函数分别在进入 awtk 事件循环的前后执行,它们在 application.c 中实现,用户可以在这两个函数中添加项目的初始化和反初始化代码:

/* HelloWorld.Xml-Demo/src/application.c */
......
/* 当程序初始化完成时调用,全局只触发一次。有初始化需求可以自己在此处添加 */
static ret_t application_on_launch(void) {
  return RET_OK;
}

/* 当程序退出时调用,全局只触发一次。如有初始化的数据需要释放,在此函数内释放 */
static ret_t application_on_exit(void) {
  return RET_OK;
}

/* 初始化程序 */
ret_t application_init(void) {
  custom_widgets_register();
  application_on_launch();
  
  if (strlen(APP_SYSTEM_BAR) > 0) {
    /* 如果存在指定的系统栏,则打开指定的顶部系统栏 */
    navigator_to(APP_SYSTEM_BAR);
  }

  if (strlen(APP_BOTTOM_SYSTEM_BAR) > 0) {
    /* 如果存在指定的系统栏,则打开指定的底部系统栏 */
    navigator_to(APP_BOTTOM_SYSTEM_BAR);
  }
  /* 打开设定的主界面窗口 */
  return navigator_to(APP_START_PAGE);
}

/* 退出程序 */
ret_t application_exit(void) {
  application_on_exit();
  log_debug("application_exit\n");

  return RET_OK;
}

# 2.5.4 窗口初始化

在 AWTK 中,通常会为每个窗口配置一个 c 文件,例如 home_page.c,并在该文件中进行窗口初始化,用户可以按需在初始化函数中添加内容,对该窗口的操作也在这个文件中实现,代码如下所示:

/* HelloWorld.Xml-Demo/src/pages/home_page.c */
ret_t home_page_init(widget_t* win, void* ctx) {
  (void)ctx;
  return_value_if_fail(win != NULL, RET_BAD_PARAMS);
  /* 遍历 win 窗口的子控件,visit_init_child 是遍历时的回调函数 */
  widget_foreach(win, visit_init_child, win);

  return RET_OK;
}

# 2.5.5 打开并初始化窗口

在项目代码中可以使用 AWTK 原生的 API 接口打开并初始化窗口,此处以 home_page 为例,代码如下:

ret_t application_init(void) {
  /* 调用 AWTK 原生 API 打开 home_page 窗口 */
  widget_t* win = window_open("home_page");
  /* 初始化 home_page 窗口 */
  home_page_init(win, NULL);
  return RET_OK;
}

另外,也可以使用项目 src/common/navigator.h 中的导航器接口打开窗口,代码如下:

/* HelloWorld.Xml-Demo/src/application.c */
/* 初始化程序 */
ret_t application_init(void) {
  ......
  /* 打开并初始化 界面窗口,APP_START_PAGE 为窗口名称"home_page" */
  return navigator_to(APP_START_PAGE);
}

navigator 的相关代码是 AWTK Designer 新建项目时自动生成的,navigator_to 函数本质上内部也是调用 AWTK 原生的 API 来打开窗口,并在 navigator_window_init 函数中根据窗口名称来调用对应的窗口初始化函数,代码如下,更具体的代码详见 src/common/navigator.c。

/* HelloWorld.Xml-Demo/src/common/navigator.c */
extern ret_t home_page_init(widget_t* win, void* ctx);

static ret_t navigator_window_init(const char* name, widget_t* win, void* ctx) {
  if (tk_str_eq(name, "home_page")) {
    return home_page_init(win, ctx);
  }

  return RET_OK;
}

# 2.5.6 响应按钮事件

在本 demo 中,会简单实现一个按下按钮改变按钮文字的功能来展示如何为一个控件注册事件并实现响应函数,实现步骤如下:

  • 根据按钮名称调用 widget_on 函数注册事件,一般在窗口初始化代码中提到的遍历子控件的函数进行注册;
  • 实现注册的回调函数,代码如下所示。
/* 按钮注册的回调函数 */
static ret_t on_button_click(void* ctx, event_t* e) {
  /* e->target为当前触发事件的控件*/
  widget_t* btn = e->target;
  char btn_text[10] = "";
  /* 获取按钮的文本 */
  widget_get_text_utf8(btn, btn_text,sizeof(btn_text));
  /* 根据文本内容改变文本 */
  if(strcmp("True", btn_text) == 0) {
    widget_set_text_utf8(btn, "False");
  } else {
    widget_set_text_utf8(btn, "True");
  }
  return RET_OK;
}

/* 初始化窗口的子控件 */
static ret_t visit_init_child(void* ctx, const void* iter) {
  widget_t* win = WIDGET(ctx);
  widget_t* widget = WIDGET(iter);
  const char* name = widget->name;

  /* 初始化指定名称的控件(设置属性或注册事件),请保证控件名称在窗口上唯一 */
  if (name != NULL && *name != '\0') {
    /* 在此处对指定名称的控件进行注册事件,此处的事件为指针按下 */
    if (tk_str_eq(name, "button")) {
      widget_on(widget, EVT_CLICK, on_button_click, win);
    }
  }
  return RET_OK;
}

这里的 visit_init_child 函数是在 widget_foreach 遍历接口中注册的回调函数,每遍历一个控件就会调用一次这个回调函数,因此需要对控件名称进行判断。

# 2.6 编译构建

Windows 和 Linux 下编译源代码的命令都是一样的,下面以 Windows 平台为例:

在 HelloWorld.Xml-Demo 目录下打开终端,执行以下命令:

scons

编译后生成的可执行文件在 HelloWorld.Xml-Demo/bin 目录下,名称为 demo,运行效果如下:

图2.3 hello world运行效果
图2.3 hello world运行效果

# 2.7 应用调试

有两种方式调试 AWTK 应用程序:使用 Visual Studio 或者 Visual Studio Code,下面分别介绍如何使用这两种方式。

# 2.7.1 Visual Studio

使用 scons 命令编译时并没有生成 Visual Studio 的工程,如果需要在 Visual Studio 中调试 AWTK 应用程序,可按下列步骤进行:

  1. 打开 Visual Studio;
  2. 在"文件"菜单中点击"打开"并选中"项目/解决方案";
  3. 选择 bin/demo.exe(或者其它要调试的可执行文件);
  4. 在项目设置中设置调试参数(可选);
  5. 将要调试的文件拖到 Visual Studio 中,在要调试的地方打断点进行调试。

# 2.7.2 Visual Studio Code

使用 Visual Studio Code 调试时,可按下列步骤进行:

  1. 如果还没有安装 C/C++ 插件的话,请安装该插件,如下图所示:
图2.4 安装C/C++插件
图2.4 安装C/C++插件
  1. 选择文件→文件夹,选择要调试的应用程序(如:HelloWorld.Xml-Demo)。

  2. 选择运行与调试,如下图所示:

图2.5 添加配置
图2.5 添加配置
  1. 选择C++(Windows)后,弹出界面,如下图所示:
图2.6 弹出界面
图2.6 弹出界面
  1. 如果没有弹出界面,可以手动生成该文件,如下图所示:
图2.7 创建launch.json
图2.7 创建launch.json
图2.8 添加配置
图2.8 添加配置
  1. 将 launch.json 文件中的 program 修改为要调试的应用程序的路径,即将"program":"输入程序名称, 例如 example ${workspaceFolder}/a.exe",修改成如下:
"program": "${workspaceFolder}/bin/demo.exe",
  1. 按F5启动调试,然后就可以在源文件里面下断点了。

建议安装 C/C++ Intellisense 插件,该插件主要用于跳转到定义、自动提示等。

# 2.8 应用发布

在开发完成后,通常需要将可执行文件和运行时需要的资源拷贝出来,发给客户或者放到嵌入式开发板上运行。AWTK 提供了一个脚本:scripts/release.py,将可执行文件和运行时需要的资源拷贝出来,放到当前目录的 release 子目录下。

例如,当前的项目是 HelloWorld.Xml-Demo,此处以 Windows 平台为例,在 HelloWorld.Xml-Demo 目录下打开终端,执行 release.py 脚本,命令如下:

python scripts/release.py

运行成功后,可以看见项目目录下生成了 release 文件夹,其中包含 assets 目录和 bin 目录,存放的内容如下:

  • assets 目录:存放了项目运行时需要的资源;
  • bin 目录:存放了项目的可执行文件。