应用

FAQ1 如何新增一个应用(内置、外置)

应用由代码文件资源文件(包含图片、动画、多语言配置文件等)两部分组成。 新增内置/外置应用的完整流程,可参考文档:创建一个新应用


FAQ2 如何基于已有应用修改为自定义应用

1. 修改内置应用

修改内置应用需分别处理代码文件资源文件,具体步骤如下:

1.1 代码修改

  1. 完整复制已有应用的整个目录,将目录名、所有相关文件名统一修改为自定义名称(建议遵循项目命名规范,如小写字母+下划线的命名格式)。 应用代码目录修改示例

  2. 清理冗余文件:删除与自定义应用功能无关的代码文件、配置文件。

  3. 处理函数关联依赖:逐一检查并调整代码中对原有目录、文件名、函数名的引用,确保所有关联逻辑指向修改后的内容,避免出现调用异常。

1.2 资源文件替换与适配

  1. 复制已有应用的资源目录,删除其中不需要的冗余资源(如多余的图片、动画文件)。 资源目录修改示例

  2. 将自定义的新资源按原有目录结构放置到对应位置。

  3. 分辨率适配处理:参照新增应用分辨率支持文档进行适配,同时同步更新软件代码中对该资源名称的引用,确保资源能正常加载。

2. 修改外置应用

外置应用的代码与资源文件存放在同一目录下,修改流程更简化:

  1. 选择一个现有外置应用,复制其整个目录并修改为自定义名称。 外置应用目录修改示例

  2. 其余修改步骤,参照外置应用创建流程执行即可。


FAQ3 如何删除一个已有的应用

1. 删除内置应用

删除内置应用需分代码删除资源删除两个独立步骤,具体操作如下:

1.1. 删除代码

  • 代码存放路径solution/examples/xxx/application/(其中xxx为具体产品型号,如watchgridview等)。

  • 操作方法:直接删除对应应用的整个目录即可。

  • 示例:删除guide应用,可直接删除路径:solution\examples\watch\application\guide删除内置应用代码示例

1.2. 删除资源

  • 内置应用资源存放路径solution/examples/xxx/resource/

  • 操作方法:仅需删除images目录下对应应用的子目录即可;公共资源以及多语言表无需处理(原因:未使用的资源在编译时不会被链接,不影响项目体积;多语言表是从代码中检索生成的,会自动更新)。 删除内置应用资源示例

2. 删除外置应用

2.1. 静态删除(项目编译前)

  • 外置应用存放路径solution/examples/_dynamic_app/application/

  • 应用类型与删除规则:该目录下包含C应用、Python应用、QJS应用、Tool应用等类型的外置应用,动态应用的代码与资源文件均存放在同一目录中。

  • 操作方法:根据要删除的应用类型,进入对应子目录,直接删除整个目录。

  • 示例:删除C应用下的alarm应用,删除路径:solution/examples/_dynamic_app/application/c/app/alarm删除动态应用示例

2.2. 动态删除(程序运行时)

外置应用支持程序运行后动态删除,具体操作参照:删除外置应用


FAQ4 如何添加全局自定义动画

1 新增动画类型

动画类型由主ID和次ID组成,主ID需唯一标识该动画,且不能与现有的重复。主ID范围以 sdk/middleware/lvgl/lvsf/lvsf_switchanim.h 中的内置枚举为准: LV_SWITCHANIM_NONE 为无动画; LV_SWITCHANIM_PUSHLV_SWITCHANIM_SHUTTER 为系统内置动画; LV_SWITCHANIM_DEFAULT 固定为 0xFF。 当前源码未定义客户自定义动画起始枚举,客户自定义动画请避开已有内置主ID,并使用小于 LV_SWITCHANIM_DEFAULT 的空闲值(例如从 0xEF 附近规划)。

2 注册动画回调

动画注册主要通过宏BUILTIN_ANIMATION注册,该宏定义如下:

#define BUILTIN_ANIMATION(anim_name,anim_major, anim_progress_cb) anim_name : 动画名称 anim_major: 动画主ID anim_progress_cb :动画回调函数

下面以缩放动画为案例说明该流程

static void lv_zoomanim_progress(lv_baseanim_t *baseanim, lv_obj_t *anim_obj, int32_t progress)
{
	//缩放动画参数获取
    lv_baseanim_para_t *para = lv_baseanim_get_para(baseanim);
    uint16_t minor = para->minor;
    lv_coord_t zoom_start = APP_TRANS_ANIM_ZOOM_NONE;
    lv_coord_t zoom_end = 0;
    lv_coord_t opa_start = LV_OPA_COVER;
    lv_coord_t opa_end = LV_OPA_30;

    //根据设备号区分不同的子动画
    if (LV_ZOOMANIM_LARGER == para->minor)
    {
        zoom_start = APP_TRANS_ANIM_ZOOM_NONE;
        zoom_end = APP_TRANS_ANIM_ZOOM_NONE << 2;
        opa_start = LV_OPA_COVER;
        opa_end = LV_OPA_30;
    }
	//动画主要分为进场动画和出场动画,每种方式单独处理
    if (LV_BASEANIM_ENTER_TYPE == lv_baseanim_get_type(baseanim))
    {
        LV_COORD_SWAP(zoom_start, zoom_end);
        LV_COORD_SWAP(opa_start, opa_end);
    }

	//缩放具体执行函数
    lv_coord_t zoom = lv_map(progress, 0, LV_SWITCHANIM_PROGRESS_MAX, zoom_start, zoom_end);
    lv_img_set_zoom(anim_obj, zoom);
    lv_coord_t opa = lv_map(progress, 0, LV_SWITCHANIM_PROGRESS_MAX, opa_start, opa_end);
    lv_obj_set_style_img_opa(anim_obj, opa, 0);
    lv_obj_invalidate(lv_scr_act());
}

//具体动画注册,包含动画主ID号以及回调处理函数
BUILTIN_ANIMATION(zoomanim, LV_SWITCHANIM_ZOOM, lv_zoomanim_progress);

FAQ5 如何添加指定页面动画

1 调用框架接口指定动画类型

指定页面的切换动画主要设置进场动画类型和出场动画类型,分别调用以下接口

void gui_app_set_enter_anim_type(uint16_t major, uint16_t minor, int16_t minor_aux)
	//major:动画主ID号
	//minor:动画次ID号
	//minor_aux:与之匹配另一个出场页面的动画次ID

void gui_app_set_exit_anim_type(uint16_t major, uint16_t minor, int16_t minor_aux)
	//major:动画主ID号
	//minor:动画次ID号
	//minor_aux:与之匹配另一个进场页面的动画次ID

2 配置合适的动画优先级

页面切换过程中,如A页面切到B页面。动画框架会先比较两个页面所配置动画的优先级,**最终会使用优先级高的动画类型**,如果两个优先级相等,则使用上一级页面的动画类型
void gui_app_set_anim_prior(int16_t enter_prior, int16_t exit_prior)
enter_prior:	进场动画优先级
exit_prior:		出场动画优先级

FAQ6 如何关闭进场动画

1 调用框架接口关闭动画

关闭切换动画直接在框架接口函数on_start中调用void gui_app_close_anim()函数,该函数主要将进出动画类型设置为LV_SWITCHANIM_NONE,且优先级降为最高。

FAQ7 如何设置开机默认 APP 或 Home 键切换的 APP?

如果只是产品默认入口变化,不需要重新设计应用框架,可以按下面方式处理:

  1. 开机默认启动 APP:调用 app_set_reg_power_on_app 设置目标 APP 名称;如果未设置,默认进入 Main

  2. Home 键主 APP:调用 app_set_reg_main_app 设置主菜单类 APP,通常保持为 Main

  3. Home 键平铺/次 APP:调用 app_set_reg_tlv_app 设置 Home 键循环切换的另一个 APP;

  4. 也可以在设备界面通过 主菜单 -> 设置 -> 默认APP 修改开机 APP 或平铺 APP。

接口说明和 UI 设置入口请参考 设置默认APP

FAQ8 系统是如何判断是否息屏的?

系统通过 app_screen_lock_time_is_end() 进行判定,只有返回 true 时才会执行息屏。具体逻辑如下:

  1. 处于monkey测试(monkey_mode()),则不息屏。

  2. 锁屏开关(screen_lock.enable,可通过 app_screen_lock_enable 控制)未打开,则不息屏。

  3. 当设定阈值(screen_lock.idle_time_limit,可通过 app_screen_lock_time_setapp_screen_lock_time_temp_set 修改)小于或等于当前实际空闲时间(lv_disp_get_inactive_time(NULL))时触发灭屏。

FAQ9 不息屏可能是什么原因?

参考 FAQ8, 常见原因如下:

  1. 通过app_screen_lock_enable关闭了息屏。

  2. app_screen_lock_time_setapp_screen_lock_time_temp_set更改了灭屏阈值。

  3. lv_disp_trig_activity(NULL)刷新了当前空闲时间,常见有:

    • UI 唤醒操作(app_gui_wakeup/send_msg_to_gui_thread_e(msg, NEED_WAKEUP_UI))。

    • 按键/触摸操作。

可配合log具体确认。

FAQ10 lvgl的接口可以跨线程使用吗?

  • 不行,由于lvgl是单线程,没有线程保护,建议所有ui处理都用send_msg_to_gui_thread接口发到gui线程统一处理,不然可能出现显示异常或者死机

FAQ11 快速连续创建page导致后续ui跳转异常

  • 检查app_schedule_get_this是否返回null,如果返回的是null,则可能是gui_app_create_page_for_app_ext函数的变量page4app_msg_t初始化为0导致,可以修改为1解决

FAQ12: 如何修改root app id,即最终返回的app id

  • 默认代码设置的是主菜单Main为root app,可以通过该函数app_schedule_change_main_app_id修改

FAQ13: 保活app的个数怎么设置

  • 默认代码设置的是2个,可以通过app_schedule_max_running_apps函数修改

FAQ14: APP注册后不想在菜单里面显示,如何设置

  • 可以使用APPLICATION_REGISTER_HIDDEN宏注册,用该图片注册的app没有带图标资源,所以不会在菜单里面显示

FAQ15: APP想注册不同的图标风格,如何设置

  • 默认app注册接口APPLICATION_REGISTER会注册两个风格的图片,在资源mainmenu和mainmenu2目录添加对应的图标资源即可

FAQ16: APP进入后的第一个界面id是什么

  • APP注册接口有用到APP_PAGE_REGISTER(app_name, “root”, ptr_size);所以第一个界面id是"root";

FAQ17: 调用gui_app_xx_now后出现Recursive断言

  • 该断言是gui_app_xx_now等接口调用的地方,本身就在app_schedule() 里面,所以出现递归了,
    常见得情况是在msg_handle函数里面调用此类接口,要避免在msg_handle调用该接口

FAQ18: 调用gui_app_create_page后出现Create page %s[%x] error, invalid app handler断言

  • 该断言是检测到调用该接口的时候没有active的app出现的断言,所以需要保证当前有active的app

FAQ19: 界面销毁后需要在on_stop手动删除obj吗?

  • 如果是创建在lc_scr_act上的,on_stop阶段框架app schedule会自动删除obj,无需手动删除,其他例如top和sys层的obj需要手动删除