02 exeasyui扩展库应用
作者:沈园园 | 最后修改:2026-04-20
一、概述
AirLCD_1000 是合宙推出的一款SPI接口,3.5寸屏,480*320 的LCD配件板,其中:
1、显示屏的驱动IC是:ST7796;
2、8个插针管脚:LCD_CLK LCD_SDA LCD_RST LCD_RS LCD_CS BKL VCC GND;
- BKL:背光控制管脚,默认高电平,背光常亮;低电平时背光熄灭;
- VCC:2.8V-3.3V;
3、适用于Air780系列/Air8000系列;
airui 是基于 LVGL 9.4 版本进行图形层封装的 LuatOS 核心库,把常用组件、事件管理、输入和基础视觉主题封装为更易上手的 Lua 接口,便于在支持 LuatOS 的设备和 PC 上统一开发。
建议使用airui来开发显示界面,airui demo参考:https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EHM_Air780EHV_Air780EGH/demo/ui/airui/single
二、演示功能概述
1、核心主程序模块
- main.lua - 主程序入口,负责系统初始化和任务调度
- ui_main.lua - exeasyui 主程序,负责页面管理和按键事件分发和执行exeasyui的任务调度
2、显示页面模块
- home_page.lua - 主页模块,提供应用入口和导航功能
- component_page.lua - UI 组件演示模块
- default_font_page.lua - 默认字体演示模块
- hzfont_page.lua - HZFont 矢量字体演示模块
3、驱动模块
- hw_default_font_drv.lua - LCD显示驱动配置和默认字体驱动模块,使用内置 12 号点阵字体
- hw_hzfont_drv.lua - LCD显示驱动配置和HZFont 矢量字体驱动模块
- hw_customer_font_drv.lua - LCD显示驱动配置和自定义外部字体驱动模块(开发中)
- key_drv.lua - 按键硬件驱动模块,负责GPIO初始化和按键事件发布
当前演示的exeasyui V1.7.0版本还不支持同时启用多种字体,仅支持选择一种字体初始化,同时启用多种字体功能正在开发中
使用 HZfont 需要使用 V2020 版本及以上的 14 号或者114号固件,且 14 号或114号固件仅支持 HZfont,不支持内置12号中文字体
4 、按键处理模块
key_handler.lua - 按键逻辑处理模块,负责光标管理和界面导航
- BOOT键 在可点击区域间循环切换
- 开机键 模拟点击当前中心区域
三、准备硬件环境

1、Air780EGH核心板 × 1
2、AirLCD_1000 配件板 × 1
3、母对母杜邦线 × 14,杜邦线太长的话,会出现 spi 通信不稳定的现象
4、TYPE-C 数据线 × 1
6、Air780EGH核心板和 AirLCD_1000配件板的硬件接线方式为:
- Air780EGH核心板通过 TYPE-C USB 口供电(核心板正面开关拨到 ON 一端),此种供电方式下,VDD_EXT 引脚为 3.3V,可以直接给 AirLCD_1000配件板供电;
- 为了演示方便,所以 Air780EGH核心板上电后直接通过 VBAT 引脚给 AirLCD_1000配件板供电;
- 客户在设计实际项目时,一般来说,需要通过一个 GPIO 来控制 LDO 给配件板供电,这样可以灵活地控制配件板的供电,可以使项目的整体功耗降到最低;
LCD 显示屏接线
| Air780EHM/Air780EHV/Air780EGH 核心板 | AirLCD_1000配件板 |
|---|---|
| 53/LCD_CLK | SCLK/CLK |
| 52/LCD_CS | CS |
| 49/LCD_RST | RES/RST |
| 50/LCD_SDA | SDA/MOS |
| 51/LCD_RS | DC/RS |
| 22/GPIO1 | BLK |
| VBAT | VCC |
| GND | GND |
四、准备软件环境
4.1 软件环境
在开始实践本示例之前,先筹备一下软件环境:
1、烧录工具:Luatools 下载调试工具
2、本demo开发测试时使用的固件为LuatOS-SoC_V2018_Air780EGH 1号固件,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;
注意:使用 HZfont 需要使用 V2020 版本及以上的 14 号固件或114号固件,且 14 号固件或114号固件仅支持 HZfont 使用其他字体
4、lib脚本文件:使用Luatools烧录时,勾选 添加默认lib 选项,使用默认lib脚本文件
准备好软件环境之后,接下来查看如何烧录项目文件到Air780EGH核心板,将本篇文章中演示使用的项目文件烧录到Air780EGH核心板中。
4.2 API 介绍
exeasyui UI扩展库:https://docs.openluat.com/osapi/ext/exeasyui/
五、程序结构
AirLCD_1000/exeasyui/
│── main.lua
│── hw_drv
│── hw_customer_font_drv.lua
│── hw_default_font_drv.lua
│── hw_hzfont_drv.lua
│── images
│── xxx.jpg
│── tp_key_drv
│── key_drv.lua
│── ui
│── component_page.lua
│── default_font_page.lua
│── home_page.lua
│── hzfont_page.lua
│── key_handler.lua
│── ui_main.lua
│── readme.md
5.1 文件说明
1、hw_drv:
- hw_customer_font_drv.lua:LCD显示驱动配置和自定义外部字体驱动模块(开发中)
- hw_default_font_drv.lua:LCD显示驱动配置和默认字体驱动模块,使用内置 12 号点阵字体
- hw_hzfont_drv.lua:LCD显示驱动配置和HZFont 矢量字体驱动模块
2、images:
xxx.jpg:演示图片文件(和lua脚本文件一起烧录,会自动放置在/luadb/目录下)
3、tp_key_drv:
- key_drv.lua:按键硬件驱动模块,负责GPIO初始化和按键事件发布
5、ui
- ui_main.lua:exeasyui 主程序,负责页面管理和按键事件分发和执行exeasyui的任务调度
- home_page.lua :主页模块,提供应用入口和导航功能
- key_handler.lua :按键处理功能模块
- component_page.lua:UI 组件演示模块
- default_font_page.lua:默认字体演示模块
- hzfont_page.lua:HZFont 矢量字体演示模块
六、代码详解
6.1 main.lua
主程序文件 main.lua 是整个项目的入口点。它负责初始化系统环境。
6.2 hw_customer_font_drv.lua
用户外部自定义外部点阵字体、lcd显示驱动配置和tp触摸驱动配置的驱动配置文件
-- hw_customer_font_drv是用户外部自定义外部点阵字体、lcd显示驱动配置和tp触摸驱动配置的驱动配置文件,目前exeasyui自定义字体功能正在开发中。
6.3 hw_default_font_drv.lua
本文件为默认字体、lcd和tp驱动模块,核心业务逻辑为:
1、使用lcd内核固件中自带的12号中文字体;
2、根据配置的字体、lcd和tp参数,初始化exEasyUI默认使用的字体、硬件显示和触摸;
3、提供无需外部硬件的字体显示能力;
-- 使用默认12号中文字体初始化exEasyUI硬件
ui.hw_init({
-- lcd_config参数填写可以参考合宙exlcd显示扩展库exlcd.init(param)接口说明:https://docs.openluat.com/osapi/ext/exlcd/#31-exlcdinitparam
lcd_config = {
lcd_model = "AirLCD_1000", -- LCD型号
-- pin_vcc = 24, -- 供电引脚,使用GPIO控制屏幕供电可配置
pin_rst = 36, -- 复位引脚
pin_pwr = 1, -- 背光控制引脚GPIO ID号
pin_pwm = 0, -- 背光控制引脚PWM ID号
port = lcd.HWID_0, -- 驱动端口
-- pin_dc = nil, -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
direction = 0, -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致
w = 320, -- lcd 水平分辨率
h = 480, -- lcd 竖直分辨率
xoffset = 0, -- x偏移(不同屏幕ic 不同屏幕方向会有差异)
yoffset = 0, -- y偏移(不同屏幕ic 不同屏幕方向会有差异)
sleepcmd = 0X10, -- 睡眠命令,默认0X10
wakecmd = 0X11, -- 唤醒命令,默认0X11
-- bus_speed = 50*1000*1000, -- SPI总线速度,不填默认50M,若速率要求更高需要进行设置
-- interface_mode = lcd.WIRE_4_BIT_8_INTERFACE_I, -- lcd模式,默认lcd.WIRE_4_BIT_8_INTERFACE_I
-- direction0 = {0x36,0x00}, -- 0°方向的命令,(不同屏幕ic会有差异)
-- direction90 = {0x36,0x60}, -- 90°方向的命令,(不同屏幕ic会有差异)
-- direction180 ={0x36,0xc0} , -- 180°方向的命令,(不同屏幕ic会有差异)
-- direction270 = {0x36,0xA0}, -- 270°方向的命令,(不同屏幕ic会有差异)
-- hbp = nil, -- 水平后廊
-- hspw = nil, -- 水平同步脉冲宽度
-- hfp = 0, -- 水平前廊
-- vbp = 0, -- 垂直后廊
-- vspw = 0, -- 垂直同步脉冲宽度
-- vfp = 0, -- 垂直前廊
-- initcmd = nil, -- 自定义屏幕初始化命令表
-- flush_rate = nil, -- 刷新率
-- spi_dev = nil, -- spi设备,当port = "device"时有效,当port ≠ "device"时可不填或者填nil
-- init_in_service = false, -- 允许初始化在lcd service里运行,在后台初始化LCD,默认是false,Air8000/G/W/T/A、Air780EHM/EGH/EHV 支持填true,可加快初始化速度,默认SPI总线速度80M
}
})
6.4 hw_hzfont_drv.lua
本文件为HZFont矢量字体、lcd和tp驱动模块,核心业务逻辑为:
1、使用V2020版本及以上14号固件或114号固件内置的hzfont合宙矢量字库;
2、根据配置的字体、lcd和tp参数,初始化exEasyUI默认使用的字体、硬件显示和触摸;
3、提供动态字体大小调整和高质量字体显示能力;
-- 使用Air780EHM/EHV/EGH V2020版本及以上14号固件内置的hzfont合宙矢量字库初始化exEasyUI硬件
ui.hw_init({
font_config = { type = "hzfont", size = 24, antialias = -1 }, -- 默认-1,表示自动抗锯齿
-- lcd_config参数填写可以参考合宙exlcd显示扩展库exlcd.init(param)接口说明:https://docs.openluat.com/osapi/ext/exlcd/#31-exlcdinitparam
lcd_config = {
lcd_model = "AirLCD_1000", -- LCD型号
-- pin_vcc = 24, -- 供电引脚,使用GPIO控制屏幕供电可配置
pin_rst = 36, -- 复位引脚
pin_pwr = 1, -- 背光控制引脚GPIO ID号
pin_pwm = 0, -- 背光控制引脚PWM ID号
port = lcd.HWID_0, -- 驱动端口
-- pin_dc = 0xFF, -- lcd数据/命令选择引脚GPIO ID号,使用lcd 专用 SPI 接口 lcd.HWID_0不需要填此参数,使用通用SPI接口需要赋值
direction = 0, -- lcd屏幕方向 0:0° 1:90° 2:180° 3:270°,屏幕方向和分辨率保存一致
w = 320, -- lcd 水平分辨率
h = 480, -- lcd 竖直分辨率
xoffset = 0, -- x偏移(不同屏幕ic 不同屏幕方向会有差异)
yoffset = 0, -- y偏移(不同屏幕ic 不同屏幕方向会有差异)
sleepcmd = 0X10, -- 睡眠命令,默认0X10
wakecmd = 0X11, -- 唤醒命令,默认0X11
-- bus_speed = 50*1000*1000, -- SPI总线速度,不填默认50M,若速率要求更高需要进行设置
-- interface_mode = lcd.WIRE_4_BIT_8_INTERFACE_I, -- lcd模式,默认lcd.WIRE_4_BIT_8_INTERFACE_I
-- direction0 = {0x36,0x00}, -- 0°方向的命令,(不同屏幕ic会有差异)
-- direction90 = {0x36,0x60}, -- 90°方向的命令,(不同屏幕ic会有差异)
-- direction180 ={0x36,0xc0} , -- 180°方向的命令,(不同屏幕ic会有差异)
-- direction270 = {0x36,0xA0}, -- 270°方向的命令,(不同屏幕ic会有差异)
-- hbp = nil, -- 水平后廊
-- hspw = nil, -- 水平同步脉冲宽度
-- hfp = 0, -- 水平前廊
-- vbp = 0, -- 垂直后廊
-- vspw = 0, -- 垂直同步脉冲宽度
-- vfp = 0, -- 垂直前廊
-- initcmd = nil, -- 自定义屏幕初始化命令表
-- flush_rate = nil, -- 刷新率
-- spi_dev = nil, -- spi设备,当port = "device"时有效,当port ≠ "device"时可不填或者填nil
-- init_in_service = false, -- 允许初始化在lcd service里运行,在后台初始化LCD,默认是false,Air8000/G/W/T/A、Air780EHM/EGH/EHV 支持填true,可加快初始化速度,默认SPI总线速度80M
}
})
6.5 key_drv.lua
本文件为按键驱动功能模块,核心业务逻辑为:
1、初始化BOOT键和PWR键的GPIO;
2、配置按键事件的中断处理函数;
3、实现按键防抖功能,防止误触发;
4、对外发布按键消息;
local key_drv = {}
-- 按键定义
local key_boot = 0 -- GPIO0按键(BOOT键)
local key_pwr = gpio.PWR_KEY -- 电源按键
-- 按键事件处理函数
local function handle_boot_key(val)
if val == 1 then
sys.publish("KEY_EVENT", "boot_down")
else
sys.publish("KEY_EVENT", "boot_up")
end
end
local function handle_pwr_key(val)
if val == 1 then
sys.publish("KEY_EVENT", "pwr_up")
else
sys.publish("KEY_EVENT", "pwr_down")
end
end
--[[
初始化按键GPIO;
配置BOOT键和PWR键的GPIO中断;
@api key_drv.init()
@summary 配置BOOT键和PWR键的GPIO中断
@return bool 初始化只会返回true
@usage
local result = key_drv.init()
if result then
log.info("按键驱动初始化成功")
end
]]
function key_drv.init()
gpio.setup(key_boot, handle_boot_key, gpio.PULLDOWN, gpio.BOTH)
gpio.debounce(key_boot, 50, 0) -- 防抖,防止频繁触发
gpio.setup(key_pwr, handle_pwr_key, gpio.PULLUP, gpio.BOTH)
gpio.debounce(key_pwr, 50, 0) -- 防抖,防止频繁触发
log.info("key_drv", "按键初始化完成")
return true
end
key_drv.init()
return key_drv
6.6 ui_main.lua
本文件为exEasyUI主程序模块,核心业务逻辑为:
1、初始化UI主题和窗口系统;
2、注册所有页面的可点击区域回调函数;
3、包装窗口方法以捕获页面切换事件;
4、订阅按键事件并分发到按键处理器;
5、启动UI渲染主循环,维持界面刷新;
local home_page = require("home_page")
local key_handler = require("key_handler")
-- 页面名称常量
local PAGE_NAMES = {
HOME = "home",
COMPONENT = "component",
DEFAULT_FONT = "default_font",
HZFONT = "hzfont"
}
-- 当前页面
local current_page = nil
-- 处理按键事件
local function handle_key_event(event_type)
if event_type == "boot_down" then
key_handler.next_area()
elseif event_type == "pwr_down" then
key_handler.simulate_click()
end
end
-- 主页区域回调函数
local function home_page_callback()
key_handler.clear_areas()
key_handler.add_area(20, 100, 280, 80) -- 组件按钮
key_handler.add_area(20, 200, 280, 80) -- 默认字体按钮
key_handler.add_area(20, 300, 280, 80) -- HZFont按钮
log.info("ui_main", "已注册主页区域,共3个区域")
end
-- 组件页面区域回调函数
local function component_page_callback()
key_handler.clear_areas()
key_handler.add_area(20, 20, 60, 30) -- 返回按钮
key_handler.add_area(210, 100, 70, 26) -- +10%按钮
key_handler.add_area(20, 170, 60, 30) -- 复选框A
key_handler.add_area(120, 170, 60, 30) -- 复选框B
key_handler.add_area(20, 430, 80, 30) -- 普通按钮
key_handler.add_area(110, 430, 80, 30) -- 蓝色按钮
key_handler.add_area(200, 430, 64, 30) -- 图片按钮
log.info("ui_main", "已注册组件页面区域,共7个区域")
end
-- 默认字体页面区域回调函数
local function default_font_page_callback()
key_handler.clear_areas()
key_handler.add_area(20, 20, 60, 30) -- 返回
log.info("ui_main", "已注册默认字体页面区域,共1个区域")
end
-- HZFont页面区域回调函数
local function hzfont_page_callback()
key_handler.clear_areas()
key_handler.add_area(20, 20, 60, 30) -- 返回
key_handler.add_area(20, 140, 120, 30) -- 切换字体按钮
log.info("ui_main", "已注册HZFont页面区域,共2个区域")
end
-- 注册所有页面的回调函数
local function register_all_pages()
key_handler.register_page(PAGE_NAMES.HOME, home_page_callback)
key_handler.register_page(PAGE_NAMES.COMPONENT, component_page_callback)
key_handler.register_page(PAGE_NAMES.DEFAULT_FONT, default_font_page_callback)
key_handler.register_page(PAGE_NAMES.HZFONT, hzfont_page_callback)
end
-- 页面切换处理
local function handle_page_change(page_name)
if not page_name then
page_name = PAGE_NAMES.HOME
end
if page_name ~= current_page then
current_page = page_name
key_handler.switch_to_page(page_name)
end
end
-- 页面切换任务函数
local function show_subpage_task(page_name)
handle_page_change(page_name)
end
local function back_task()
handle_page_change(PAGE_NAMES.HOME)
end
-- 包装的show_subpage方法
local function wrapped_show_subpage(self, page_name)
-- 调用原始方法
local original_show_subpage = getmetatable(self).original_show_subpage
original_show_subpage(self, page_name)
if page_name then
sys.taskInit(show_subpage_task, page_name)
end
end
-- 包装的back方法
local function wrapped_back(self)
-- 调用原始方法
local original_back = getmetatable(self).original_back
original_back(self)
sys.taskInit(back_task)
end
-- 包装窗口方法
local function wrap_window_methods()
local test_win = ui.window({})
local window_meta = getmetatable(test_win)
-- 保存原始方法
window_meta.original_show_subpage = window_meta.show_subpage
window_meta.original_back = window_meta.back
-- 替换为包装的方法
window_meta.show_subpage = wrapped_show_subpage
window_meta.back = wrapped_back
end
-- 启动UI主任务
local function ui_main()
-- 初始化UI主题
ui.sw_init({ theme = "light" })
-- 包装窗口方法
wrap_window_methods()
register_all_pages()
-- 创建主页
home_page.create()
-- 确保光标显示
key_handler.switch_to_page(PAGE_NAMES.HOME)
-- 订阅按键事件
sys.subscribe("KEY_EVENT", handle_key_event)
end
sys.taskInit(ui_main)
6.7 home_page.lua
本文件为主页功能模块,核心业务逻辑为:
1、创建应用主窗口并配置背景颜色;
2、配置子页面工厂函数,管理各演示页面的创建;
3、创建标题和功能按钮,提供页面导航功能;
4、处理按钮点击事件,实现页面切换;
function home_page.create()
-- 创建主页
local home = ui.window({ background_color = ui.COLOR_WHITE })
home.visible = true
-- 配置子页面工厂
home:configure_subpages({
component = function() return require("component_page").create(ui) end,
default_font = function() return require("default_font_page").create(ui) end,
hzfont = function() return require("hzfont_page").create(ui) end
})
-- 标题
local title = ui.label({
x = 80,
y = 30,
text = "exEasyUI v1.7.0演示系统",
color = ui.COLOR_BLACK,
size = 16
})
local subtitle = ui.label({
x = 80,
y = 60,
text = "boot键:选择 pwr键:确认",
color = ui.COLOR_GRAY,
size = 12
})
-- 组件演示按钮
local btn_component = ui.button({
x = 20,
y = 100,
w = 280,
h = 80,
text = "组件演示",
bg_color = ui.COLOR_BLUE,
text_color = ui.COLOR_WHITE,
on_click = function()
home:show_subpage("component")
end
})
-- 默认字体演示按钮
local btn_default_font = ui.button({
x = 20,
y = 200,
w = 280,
h = 80,
text = "默认字体演示",
bg_color = ui.COLOR_RED,
text_color = ui.COLOR_WHITE,
on_click = function()
home:show_subpage("default_font")
end
})
-- HZFont演示按钮
local btn_hzfont = ui.button({
x = 20,
y = 300,
w = 280,
h = 80,
text = "HZFont演示",
bg_color = ui.COLOR_ORANGE,
text_color = ui.COLOR_WHITE,
on_click = function()
home:show_subpage("hzfont")
end
})
-- 添加所有组件到窗口
home:add(title)
home:add(subtitle)
home:add(btn_component)
home:add(btn_default_font)
home:add(btn_hzfont)
ui.add(home)
end
return home_page
6.8 key_handler.lua
本文件为按键处理功能模块,核心业务逻辑为:
1、管理页面可点击区域列表,支持动态区域注册;
2、处理按键事件,实现光标在可点击区域间的切换;
3、提供光标绘制和清除功能,视觉上标记当前焦点;
4、支持模拟点击操作,触发界面元素响应;
5、实现页面切换时的区域列表更新;
local key_handler = {}
-- 模块内部状态
local clickable_areas = {}
local current_area_index = 0
local page_callbacks = {}
local config = {
cursor_thickness = 2,
cursor_color = 0xFF0000,
show_cursor = true,
last_switch_time = 0
}
local current_drawn_rect = nil
--[[
注册页面区域回调函数;
@api key_handler.register_page(page_name, callback)
@summary 注册页面的可点击区域回调函数
@string
page_name
页面名称标识符,用于后续页面切换时引用;
@function
callback
区域注册回调函数,函数内应调用key_handler.clear_areas()和key_handler.add_area()来定义页面的可点击区域;
@return nil
@usage
key_handler.register_page("home", function()
key_handler.clear_areas()
key_handler.add_area(20, 100, 280, 50) -- 主页按钮1
key_handler.add_area(20, 170, 280, 50) -- 主页按钮2
end)
]]
function key_handler.register_page(page_name, callback)
page_callbacks[page_name] = callback
end
--[[
切换到指定页面并更新区域列表;
@api key_handler.switch_to_page(page_name)
@summary 切换到指定页面
@string
page_name
目标页面名称,必须已通过key_handler.register_page()注册;
@return boolean
切换成功返回true,失败返回false;
@usage
local result = key_handler.switch_to_page("home")
if result then
log.info("成功切换到主页")
end
]]
function key_handler.switch_to_page(page_name)
key_handler.clear_cursor()
if page_callbacks[page_name] then
page_callbacks[page_name]()
else
clickable_areas = {}
end
if #clickable_areas > 0 then
current_area_index = 1
config.last_switch_time = mcu.ticks()
key_handler.draw_cursor()
return true
else
current_area_index = 0
return false
end
end
-- 清空区域
function key_handler.clear_areas()
key_handler.clear_cursor()
clickable_areas = {}
current_area_index = 0
end
-- 添加区域
function key_handler.add_area(x, y, w, h)
table.insert(clickable_areas, { x = x, y = y, w = w, h = h })
if #clickable_areas == 1 and current_area_index == 0 then
current_area_index = 1
config.last_switch_time = mcu.ticks()
end
end
-- 获取当前区域矩形
local function get_current_area_rect()
if current_area_index < 1 or current_area_index > #clickable_areas then
return nil
end
local area = clickable_areas[current_area_index]
return {
x1 = area.x - 3,
y1 = area.y - 3,
x2 = area.x + area.w + 3,
y2 = area.y + area.h + 3
}
end
-- 获取当前区域中心
local function get_current_area_center()
if current_area_index < 1 or current_area_index > #clickable_areas then
return 160, 240
end
local area = clickable_areas[current_area_index]
local center_x = area.x + math.floor(area.w / 2)
local center_y = area.y + math.floor(area.h / 2)
return center_x, center_y
end
-- 清除光标
function key_handler.clear_cursor()
if current_drawn_rect then
lcd.setColor(0xFFFFFF, 0xFFFFFF)
local r = current_drawn_rect
for i = 1, config.cursor_thickness do
local offset = i - 1
lcd.drawRectangle(
r.x1 + offset,
r.y1 + offset,
r.x2 - offset,
r.y2 - offset
)
end
current_drawn_rect = nil
end
end
-- 绘制光标
function key_handler.draw_cursor()
if not config.show_cursor then
return
end
if current_area_index < 1 or current_area_index > #clickable_areas then
return
end
key_handler.clear_cursor()
local rect = get_current_area_rect()
if not rect then return end
current_drawn_rect = rect
lcd.setColor(0xFFFFFF, config.cursor_color)
local thickness = config.cursor_thickness
for i = 1, thickness do
local offset = i - 1
lcd.drawRectangle(
rect.x1 + offset,
rect.y1 + offset,
rect.x2 - offset,
rect.y2 - offset
)
end
config.last_switch_time = mcu.ticks()
end
--[[
切换到下一个可点击区域;
@api key_handler.next_area()
@summary 在可点击区域间循环切换焦点
@return boolean
切换成功返回true,失败返回false;
@usage
local result = key_handler.next_area()
if result then
log.info("已切换到下一个区域")
end
]]
function key_handler.next_area()
if #clickable_areas == 0 then
return false
end
local old_index = current_area_index
current_area_index = current_area_index + 1
if current_area_index > #clickable_areas then
current_area_index = 1
end
key_handler.draw_cursor()
return true
end
--[[
模拟点击当前选中区域;
@api key_handler.simulate_click()
@summary 模拟点击当前焦点区域
@return number, number
返回点击的坐标X, Y;如果当前没有选中区域,返回0, 0;
@usage
local x, y = key_handler.simulate_click()
log.info("点击坐标:", x, ",", y)
]]
function key_handler.simulate_click()
if #clickable_areas == 0 or current_area_index == 0 then
return 0, 0
end
local center_x, center_y = get_current_area_center()
-- exeasyui必须先按下再单击
sys.publish("BASE_TOUCH_EVENT", "TOUCH_DOWN", center_x, center_y)
sys.publish("BASE_TOUCH_EVENT", "SINGLE_TAP", center_x, center_y)
return center_x, center_y
end
--[[
显示或隐藏光标;
@api key_handler.set_visible(visible)
@summary 设置光标可见性
@boolean
visible
true表示显示光标,false表示隐藏光标;
@return nil
@usage
key_handler.set_visible(true) -- 显示光标
key_handler.set_visible(false) -- 隐藏光标
]]
function key_handler.set_visible(visible)
config.show_cursor = visible
if not visible then
key_handler.clear_cursor()
else
key_handler.draw_cursor()
end
end
--[[
获取当前选中区域信息;
@api key_handler.get_current_area()
@summary 获取当前焦点区域的详细信息
@return table or nil
区域信息表,包含以下字段:
- index: number 当前区域索引
- total: number 总区域数
- x: number 区域X坐标
- y: number 区域Y坐标
- w: number 区域宽度
- h: number 区域高度
- center_x: number 中心点X坐标
- center_y: number 中心点Y坐标
如果没有选中区域,返回nil;
@usage
local area = key_handler.get_current_area()
if area then
log.info("当前区域:", area.index, "/", area.total)
end
]]
function key_handler.get_current_area()
if current_area_index < 1 or current_area_index > #clickable_areas then
return nil
end
local area = clickable_areas[current_area_index]
local center_x, center_y = get_current_area_center()
return {
index = current_area_index,
total = #clickable_areas,
x = area.x,
y = area.y,
w = area.w,
h = area.h,
center_x = center_x,
center_y = center_y
}
end
return key_handler
6.9 component_page.lua
本文件为组件演示页面功能模块,核心业务逻辑为:
1、创建带上下滚动功能的演示窗口;
2、展示进度条、按钮、复选框、图片轮播等UI组件;
3、演示组件的交互功能和事件处理;
4、提供返回主页的导航功能;
local component_page = {}
--[[
创建组件演示页面;
@api component_page.create(ui)
@summary 创建组件演示页面界面
@table ui UI库对象
@return table 组件演示窗口对象
@usage
-- 在子页面工厂中调用创建组件演示页面
local component_page = require("component_page").create(ui)
]]
function component_page.create(ui)
local win = ui.window({
background_color = ui.COLOR_WHITE,
x = 0,
y = 0,
w = 320,
h = 480
})
-- 标题
local title = ui.label({
x = 120,
y = 25,
text = "组件演示",
color = ui.COLOR_BLACK,
size = 16
})
-- 返回按钮
local btn_back = ui.button({
x = 20,
y = 20,
w = 60,
h = 30,
text = "返回",
on_click = function()
win:back()
end
})
-- ==================== 1. 进度条组件演示 ====================
local progress_label = ui.label({
x = 20,
y = 70,
text = "1. 进度条组件:",
color = ui.COLOR_BLACK,
size = 14
})
local progress_value = 0
local progress_bar = ui.progress_bar({
x = 20,
y = 100,
w = 180,
h = 26,
progress = progress_value
})
local btn_progress = ui.button({
x = 210,
y = 100,
w = 70,
h = 26,
text = "+10%",
on_click = function()
progress_value = progress_value + 10
if progress_value > 100 then
progress_value = 0
end
progress_bar:set_progress(progress_value)
progress_bar:set_text("进度: " .. progress_value .. "%")
end
})
-- ==================== 2. 复选框组件演示 ====================
local checkbox_label = ui.label({
x = 20,
y = 140,
text = "2. 复选框组件:",
color = ui.COLOR_BLACK,
size = 14
})
local checkbox1 = ui.check_box({
x = 20,
y = 170,
text = "选项A",
checked = false,
on_change = function(checked)
log.info("component_page", "选项A:", checked)
end
})
local checkbox2 = ui.check_box({
x = 120,
y = 170,
text = "选项B",
checked = true,
on_change = function(checked)
log.info("component_page", "选项B:", checked)
end
})
-- -- ==================== 3. 图片轮播组件演示 ====================
local picture_label = ui.label({
x = 20,
y = 220,
text = "3. 图片轮播组件:",
color = ui.COLOR_BLACK,
size = 14
})
local picture = ui.picture({
x = 20,
y = 250,
w = 128,
h = 128,
sources = { "/luadb/1.jpg", "/luadb/2.jpg", "/luadb/3.jpg" },
autoplay = true,
interval = 2000
})
-- ==================== 4. 按钮组件演示 ====================
local button_label = ui.label({
x = 20,
y = 400,
text = "4. 按钮组件:",
color = ui.COLOR_BLACK,
size = 14
})
-- 普通按钮
local normal_btn = ui.button({
x = 20,
y = 430,
w = 80,
h = 30,
text = "普通按钮",
on_click = function()
log.info("component_page", "普通按钮被点击")
end
})
-- 带颜色的按钮
local colored_btn = ui.button({
x = 110,
y = 430,
w = 80,
h = 30,
text = "蓝色按钮",
bg_color = ui.COLOR_BLUE,
text_color = ui.COLOR_WHITE,
on_click = function()
log.info("component_page", "蓝色按钮被点击")
end
})
-- 图片按钮
local image_btn = ui.button({
x = 200,
y = 413,
w = 64,
h = 64,
src = "/luadb/4.jpg",
src_toggled = "/luadb/5.jpg",
toggle = true,
on_click = function()
log.info("component_page", "图片按钮被点击")
end
})
-- 添加所有组件到窗口
win:add(title)
win:add(btn_back)
win:add(progress_label)
win:add(progress_bar)
win:add(btn_progress)
win:add(checkbox_label)
win:add(checkbox1)
win:add(checkbox2)
win:add(picture_label)
win:add(picture)
win:add(button_label)
win:add(normal_btn)
win:add(colored_btn)
win:add(image_btn)
return win
end
return component_page
6.10 default_font_page.lua
本文件为默认字体演示页面功能模块,核心业务逻辑为:
1、创建演示窗口,展示内置12号点阵字体的固定大小特性;
2、演示数字、符号、中英文混排的显示效果;
3、展示默认字体的特性和限制说明;
4、提供返回主页的导航功能;
local default_font_page = {}
--[[
创建默认字体演示页面;
@api default_font_page.create(ui)
@summary 创建默认字体演示页面界面
@table ui UI库对象
@return table 默认字体演示窗口对象
@usage
-- 在子页面工厂中调用创建默认字体演示页面
local default_font_page = require("default_font_page").create(ui)
]]
function default_font_page.create(ui)
local win = ui.window({
background_color = ui.COLOR_WHITE,
x = 0,
y = 0,
w = 320,
h = 480
})
-- 标题
local title = ui.label({
x = 120,
y = 25,
text = "默认字体演示",
color = ui.COLOR_BLACK,
size = 12
})
-- 返回按钮
local btn_back = ui.button({
x = 20,
y = 20,
w = 60,
h = 30,
text = "返回",
on_click = function()
win:back()
end
})
-- 字体演示标题
local demo_title = ui.label({
x = 20,
y = 70,
text = "字体演示 (固定12号):",
color = ui.COLOR_BLACK,
size = 12
})
-- 数字演示
local number_demo = ui.label({
x = 20,
y = 100,
text = "1、数字: 0123456789",
color = ui.COLOR_BLUE,
size = 12
})
-- 符号演示
local symbol_demo = ui.label({
x = 20,
y = 130,
text = "2、符号: !@#$%^&*()_+-=[]",
color = ui.COLOR_ORANGE,
size = 12
})
-- 中英文演示
local text_demo = ui.label({
x = 20,
y = 160,
text = "3、中英文: Hello 世界 ABC",
color = ui.COLOR_RED,
size = 12
})
-- 默认字体特性说明
local feature_title = ui.label({
x = 20,
y = 200,
text = "默认字体特性:",
color = ui.COLOR_BLACK,
size = 12
})
local feature1 = ui.label({
x = 20,
y = 230,
text = "- 内置12号点阵字体",
color = ui.COLOR_GRAY,
size = 12
})
local feature2 = ui.label({
x = 20,
y = 260,
text = "- 无需外部硬件支持",
color = ui.COLOR_GRAY,
size = 12
})
local feature3 = ui.label({
x = 20,
y = 290,
text = "- 启动快速,资源占用小",
color = ui.COLOR_GRAY,
size = 12
})
local feature4 = ui.label({
x = 20,
y = 320,
text = "- 字体大小固定为12号",
color = ui.COLOR_GRAY,
size = 12
})
-- 添加所有组件到窗口
win:add(title)
win:add(btn_back)
win:add(demo_title)
win:add(number_demo)
win:add(symbol_demo)
win:add(text_demo)
win:add(feature_title)
win:add(feature1)
win:add(feature2)
win:add(feature3)
win:add(feature4)
return win
end
return default_font_page
6.11 hzfont_page.lua
本文件为HZFont矢量字体演示页面模块,核心业务逻辑为:
1、创建HZFont矢量字体演示页面,展示矢量字体的动态调整能力;
2、演示多种字体大小和颜色的文本显示效果;
3、提供字体大小动态切换功能,展示矢量字体的缩放优势;
4、展示HZFont矢量字体的特性和应用场景;
local hzfont_page = {}
--[[
页面演示状态记录表
@table demo_state
@field current_size number 当前演示字体大小,初始值为16号
]]
local demo_state = {
current_size = 16 -- 当前字体大小,从16号开始
}
--[[
创建HZFont矢量字体演示页面窗口;
@api hzfont_page.create(ui)
@summary 创建HZFont矢量字体演示页面
@param ui table exEasyUI库对象,用于创建UI组件
@return table 返回创建的窗口对象
@usage
-- 在页面切换逻辑中调用
local hzfont_win = hzfont_page.create(ui)
return hzfont_win
]]
function hzfont_page.create(ui)
--[[
创建主窗口
@param background_color number 窗口背景颜色,白色
@param x number 窗口左上角X坐标,0
@param y number 窗口左上角Y坐标,0
@param w number 窗口宽度,320像素
@param h number 窗口高度,480像素
]]
local win = ui.window({
background_color = ui.COLOR_WHITE,
x = 0,
y = 0,
w = 320,
h = 480
})
--[[
页面标题标签
@param x number 标签左上角X坐标,85像素
@param y number 标签左上角Y坐标,25像素
@param text string 标签文本内容
@param color number 文本颜色,黑色
@param size number 字体大小,16号
]]
local title = ui.label({
x = 85,
y = 25,
text = "HZFont矢量字体演示",
color = ui.COLOR_BLACK,
size = 16
})
--[[
返回按钮
@param x number 按钮左上角X坐标,20像素
@param y number 按钮左上角Y坐标,20像素
@param w number 按钮宽度,60像素
@param h number 按钮高度,30像素
@param text string 按钮文本
@param on_click function 按钮点击回调函数,调用窗口的back方法返回上一页
]]
local btn_back = ui.button({
x = 20,
y = 20,
w = 60,
h = 30,
text = "返回",
on_click = function()
win:back()
end
})
--[[
动态字体大小调整标题
@param x number 标签左上角X坐标,20像素
@param y number 标签左上角Y坐标,70像素
@param text string 标签文本内容
@param color number 文本颜色,黑色
@param size number 字体大小,16号
]]
local dynamic_title = ui.label({
x = 20,
y = 70,
text = "动态字体大小调整:",
color = ui.COLOR_BLACK,
size = 16
})
--[[
字体大小信息显示标签
@param x number 标签左上角X坐标,20像素
@param y number 标签左上角Y坐标,100像素
@param text string 标签文本内容,显示当前字体大小
@param color number 文本颜色,蓝色
@param size number 字体大小,16号
]]
local size_info = ui.label({
x = 20,
y = 100,
text = "当前大小: 16号 (可调整)",
color = ui.COLOR_BLUE,
size = 16
})
--[[
字体大小切换按钮
@param x number 按钮左上角X坐标,20像素
@param y number 按钮左上角Y坐标,140像素
@param w number 按钮宽度,120像素
@param h number 按钮高度,30像素
@param text string 按钮文本
@param on_click function 按钮点击回调函数,切换字体大小
]]
local btn_change_size = ui.button({
x = 20,
y = 140,
w = 120,
h = 30,
text = "切换字体大小",
on_click = function()
-- 每次点击增加4号字体大小
demo_state.current_size = demo_state.current_size + 4
-- 当字体大小超过32号时,重置为12号
if demo_state.current_size > 32 then
demo_state.current_size = 12
end
-- 更新标签文本和字体大小
size_info:set_text("当前字体大小: " .. demo_state.current_size .. "号")
size_info:set_size(demo_state.current_size)
end
})
--[[
字体演示区域标题
@param x number 标签左上角X坐标,20像素
@param y number 标签左上角Y坐标,180像素
@param text string 标签文本内容
@param color number 文本颜色,黑色
@param size number 字体大小,16号
]]
local demo_title = ui.label({
x = 20,
y = 180,
text = "字体演示:",
color = ui.COLOR_BLACK,
size = 16
})
--[[
数字演示标签
@param x number 标签左上角X坐标,20像素
@param y number 标签左上角Y坐标,210像素
@param text string 标签文本内容,显示数字
@param color number 文本颜色,蓝色
@param size number 字体大小,14号
]]
local number_demo = ui.label({
x = 20,
y = 210,
text = "数字: 0123456789",
color = ui.COLOR_BLUE,
size = 14
})
--[[
符号演示标签
@param x number 标签左上角X坐标,20像素
@param y number 标签左上角Y坐标,250像素
@param text string 标签文本内容,显示特殊符号
@param color number 文本颜色,橙色
@param size number 字体大小,20号
]]
local symbol_demo = ui.label({
x = 20,
y = 250,
text = "符号: !@#$%^&*()_+-=[]",
color = ui.COLOR_ORANGE,
size = 20
})
--[[
中英文演示标签
@param x number 标签左上角X坐标,20像素
@param y number 标签左上角Y坐标,300像素
@param text string 标签文本内容,显示中英文混合文本
@param color number 文本颜色,红色
@param size number 字体大小,28号
]]
local text_demo = ui.label({
x = 20,
y = 300,
text = "中英文: LuatOS",
color = ui.COLOR_RED,
size = 28
})
--[[
HZFont特性说明标题
@param x number 标签左上角X坐标,20像素
@param y number 标签左上角Y坐标,350像素
@param text string 标签文本内容
@param color number 文本颜色,黑色
@param size number 字体大小,14号
]]
local feature_title = ui.label({
x = 20,
y = 350,
text = "HZFont特性:",
color = ui.COLOR_BLACK,
size = 14
})
--[[
HZFont特性说明第一条
@param x number 标签左上角X坐标,20像素
@param y number 标签左上角Y坐标,380像素
@param text string 标签文本内容,说明HZFont的内置特性
@param color number 文本颜色,灰色
@param size number 字体大小,12号
]]
local feature1 = ui.label({
x = 20,
y = 380,
text = "- 内置矢量字体,无需外部硬件",
color = ui.COLOR_GRAY,
size = 12
})
--[[
HZFont特性说明第二条
@param x number 标签左上角X坐标,20像素
@param y number 标签左上角Y坐标,410像素
@param text string 标签文本内容,说明HZFont的动态调整能力
@param color number 文本颜色,灰色
@param size number 字体大小,12号
]]
local feature2 = ui.label({
x = 20,
y = 410,
text = "- 支持10-100号字体动态调整",
color = ui.COLOR_GRAY,
size = 12
})
--[[
将创建的UI组件添加到窗口
按照从背景到前景的顺序添加,确保显示层次正确
]]
win:add(title)
win:add(btn_back)
win:add(dynamic_title)
win:add(size_info)
win:add(btn_change_size)
win:add(demo_title)
win:add(number_demo)
win:add(symbol_demo)
win:add(text_demo)
win:add(feature_title)
win:add(feature1)
win:add(feature2)
return win
end
return hzfont_page
七、显示效果展示
7.1 硬件准备
- 按照硬件接线表连接所有设备
- 通过 TYPE-C USB 口供电
- 检查所有接线无误
7.2 软件配置
在 main.lua 中配置系统参数:
-- 必须加载才能启用exeasyui的功能
ui = require("exeasyui")
-- 加载lcd、tp和字库驱动管理功能模块,有以下三种:
-- 1、使用lcd内核固件中自带的12号中文字体的hw_default_font_drv,并按lcd显示驱动配置初始化
-- 2、使用hzfont核心库驱动内核固件中支持的软件矢量字库的hw_hzfont_drv.lua,并按lcd显示驱动配置初始化
-- 3、使用自定义字体的hw_customer_font_drv(目前开发中)
-- 最新情况可查看模组选型手册中对应型号的固件列表内,支持的核心库是否包含lcd、tp、12号中文、hzfont,链接https://docs.openluat.com/air780epm/common/product/
-- 目前exeasyui V1.7.0版本支持使用已经实现的三种功能中的一种进行初始化,同时支持多种字体初始化功能正在开发中
require("hw_default_font_drv")
-- require("hw_hzfont_drv")
-- require("hw_customer_font_drv")开发中
-- 加载按键驱动模块
require("key_drv")
-- 加载exeassyui扩展库实现的用户界面功能模块
-- 实现多页面切换、触摸事件分发和界面渲染功能
-- 包含主页、组件演示页、默认字体演示页、HZfont演示页和自定义字体演示页
require("ui_main")
7.3 软件烧录
- 使用Luatools烧录最新内核固件
- 下载并烧录本项目所有脚本文件
- 将字体文件和图片文件随脚本文件一起烧录到脚本分区
- 烧录成功后设备自动重启后开始运行
7.4 功能测试
7.4.1 主页面操作
- 设备启动后显示主页面,光标自动定位在第一个按钮
- 按BOOT键在不同按钮间切换焦点
- 按PWR键进入对应演示页面
- 使用返回按钮返回主页面

7.4.2 组件演示页面
- 测试进度条组件的动态更新
- 体验复选框的状态变化
- 查看图片轮播效果(如有图片文件)
- 使用按键在不同组件间切换焦点

7.4.3 字体演示页面
- 默认字体页:查看固定 12 号字体的颜色和中英文显示
- HZFont 页:测试动态字体大小调整功能
- 在各页面使用按键操作界面按钮


7.5 预期效果
- 系统启动:正常初始化,显示主页面
- 字体显示:各字体页面正常显示,动态调整功能正常
- 按键操作:在各页面使用按键操作界面按钮
八、常见问题
- 显示异常:检查 LCD 接线,确认对应驱动文件中的硬件参数正确
- 触摸无响应:检查 I2C 接线,确认触摸芯片型号配置正确
- 字体显示异常:确认选择的字体驱动
- 图片无法显示:确认图片文件已正确烧录到指定路径
- 系统卡顿:调整
ui_main.lua中的刷新率参数