平铺(TLV)

1. 创建一个平铺应用

1.1. 简介

平铺应用是智能穿戴设备(如手表)中由主应用框架管理的核心交互界面集合,包含表盘、上下快捷页面及左右子应用,作为用户日常操作的主要入口,支持展示各类功能模块(如活动记录、心率监测等)并提供便捷的切换交互。

  • 平铺功能通过 APP_TLV_USED 使能,配置路径为:menuconfig (Top) Gui Framwork Config Tileview Config

  • 使能平铺相关的宏之后,Solution 框架将提供自动注册以及调度机制

  • 支持最少一个子应用,没有最大数量限制(视内存情况而定)

  • 支持切换动画平移、缩放、弧形旋转,水平 3d 翻转、绕 Y 轴翻转,以及用户自定义动画

  • 他的调度与主应用框架类似,通过 msg_handle 进行,msg 中的参数是该应用的节点信息,其结构是tlv_node_t

注:有的用户也习惯将平铺应用称作一级页面

fishy

1.2. 注册平铺应用

注册平铺应用需使用TLV_REGISTER宏,语法如下:

TLV_REGISTER(priority, id, name, thumb_img, ptr_size)

参数说明:

  • priority:应用优先级,数值越小优先级越高,应用按优先级排序,支持动态调整(如编辑页面拖动排序)

  • id:应用标识字符串,框架调度时使用,需在平铺框架中保持唯一

  • name:应用显示标题,多语言字符串,仅用于显示

  • thumb_img:应用缩略图,用于编辑模式

  • ptr_size:应用全局内存大小,由框架统一申请和释放,页面可直接使用

1.3. 平铺应用实现

平铺应用的实现主要围绕生命周期函数和事件处理,核心包括以下几个部分:

1.3.1 生命周期函数

平铺应用有四个主要的生命周期函数,用于管理应用的创建、显示、隐藏和销毁:

  • on_start():应用初始化时调用,用于创建基本 UI 元素

  • on_resume():应用即将显示时调用,用于初始化动态元素和启动动画

  • on_pause():应用即将隐藏时调用,用于暂停动画和释放临时资源

  • on_stop():应用销毁时调用,用于释放所有资源

fishy

1.3.2 事件处理

应用需要处理各种用户交互事件和系统事件,如触摸、滑动、按键等,确保应用能够正确响应用户操作。

fishy

1.3.3 核心功能模块

平铺页面主要包含四部分核心功能:

  1. TLV 应用管理

  • 显示已设定的 TLV 应用和表盘

  • 长按进入 TLV 编辑页面,支持 TLV 的添加、删除和排序操作

  1. 表盘管理

  • 提供表盘的编辑与选择功能

  • 支持将指定表盘设为 TLV 应用

  • 包含六扇门表盘选择页面

  1. 辅助窗口

  • 下拉消息框:显示通知和系统消息

  • 上拉堆叠菜单:提供快捷功能入口

  • 右滑侧边栏:展示附加信息或功能

  • 快捷页面:快速访问常用功能

  • 所有辅助框均通过lv_multegde控件封装了拖拽交互,简化开发

  1. 切换动画

  • TLV 滑动切换动画配置

  • 上下拉页面切换动画管理

1.3.4 开发注意事项

  1. 帧率优化 平铺页面同时存在左、中、右三个子应用,为保证高刷新率,需注意:

  • 页面设计尽量简洁,避免大长页

  • 复杂页面考虑使用进场动画,延迟复杂元素初始化,既提高帧率又增加动感

  • 禁止在平铺页面中进行 flash 读写和 delay 操作,历史数据应提前读取到内存

  • 避免直接使用大尺寸的圆或弧,建议使用图片替代;若图片无法满足,可考虑使用绘制功能

  1. 状态机差异 平铺应用的状态机切换与普通应用不同,需特别注意:

  • 普通应用:start->resume->pause->stop(顺序切换)

  • 平铺应用可能的状态切换:

  • start->resume->pause->stop

  • start->pause->stop

  • start->stop

这种设计旨在提高状态切换效率,省去非必要过程,因此开发时需兼容以上所有场景。

1.3.5 示例代码

以下是一个活动记录应用的实现示例,使用进场动画来提高帧率:

static void on_start(void *param)
{
    /* 获取该应用的parent obj */
    lv_obj_t *parent = TLV_GET_NODE_PARENT;
    /* 获取框架申请的全局内存 */
    p_activity = TLV_GET_NODE_MEM_PTR;
    LV_ASSERT_NULL(p_activity);
    
    /* 创建背景图片 */
    lv_obj_t *bg_img = lv_img_create(parent);
    lv_img_set_src(bg_img, APP_GET_IMG(img_activity_bg_tlv));
    lv_obj_center(bg_img);
    lv_obj_add_flag(bg_img, LV_OBJ_FLAG_EVENT_BUBBLE);
    
    /* 创建透明背景容器,其他子控件均创建在该容器上 */
    lv_obj_t *bg_cont = lv_obj_create(bg_img);
    lv_obj_remove_style_all(bg_cont);
    lv_obj_set_size(bg_cont, 400, 210);
    lv_obj_set_style_bg_color(bg_cont, LV_COLOR_YELLOW, LV_PART_MAIN);
    lv_obj_set_style_bg_opa(bg_cont, 0, LV_PART_MAIN);
    lv_obj_refr_size(bg_cont);
    lv_obj_align(bg_cont, LV_ALIGN_TOP_MID, 0, 0);
    lv_obj_refr_pos(bg_cont);
    lv_obj_add_flag(bg_cont, LV_OBJ_FLAG_EVENT_BUBBLE);
    lv_obj_clear_flag(bg_cont, LV_OBJ_FLAG_SCROLLABLE);
}

static void on_resume(void *param)
{
    /* 初始化其他子控件 */
    app_activity_init(0);

    /* 启动进场动画,将arc页面初始化放在动画中,提高帧率并增加动感 */
    lv_anim_t n_anim;
    lv_anim_init(&n_anim);
    lv_anim_set_values(&n_anim, 0, 1024);
    lv_anim_set_time(&n_anim, 5000);
    lv_anim_set_exec_cb(&n_anim, arc_anim_cb);
    lv_anim_set_repeat_count(&n_anim, 500);
    lv_anim_set_var(&n_anim, p_activity);
    lv_anim_start(&n_anim);
}

static void on_pause(void *param)
{
    /* 删除页面上的动画,防止页面停止时动画仍在运行引发异常 */
    for (uint16_t i = 0; i < 3; i++)
    {
        lv_anim_del(&p_activity->arc_arr[i], NULL);
    }
}

static void on_stop(void *param)
{
    /* 指针变量置空 */
    p_activity = NULL;
}

/* 注册活动记录的平铺应用 */
TLV_REGISTER(APP_PRIO_ACTIVITY, tlv_activity, app_get_strid(key_tlv_activity, "activity"), 
             img_activity_thum, sizeof(app_activity_t));

具体例程可以参见solution\examples\watch\application\tileview\tlv_sleep

1.4. 平铺应用管理

1.4.1. 删除平铺应用

平铺应用节点结构定义如下:

typedef struct
{
   tlv_desc_t      desc;              // TLV描述信息
   lv_obj_t       *scr;               // 平铺应用父对象
   void           *mem_ptr;           // 平铺应用全局内存指针
   uint32_t        mem_size;          // 平铺应用全局内存大小
   void           *user_data;         // 平铺应用用户数据
   rt_list_t       list;              // 链表节点
   tlv_state_t     state;             // 应用状态
   bool            installed;         // 应用是否已安装,true表示已安装
   bool            updated;           // 是否已更新,true表示已更新并将在关机时写入flash
} tlv_node_t;

删除方法: 将对应节点的installed属性设为false,系统在读取显示时会跳过该节点。 注意: 删除平铺应用时,该应用不能处于运行状态。

1.4.2. 编辑(排序)平铺应用

排序 TLV 应用的主要思路是调整 TLV 链表的节点顺序,以下是实现示例:

void tlv_edit_drage_item_cb(lv_event_t *e)
{
    lv_obj_t *snapshot = lv_event_get_current_target(e);
    lv_obj_t *multlist = lv_obj_get_parent(snapshot);
    lv_event_code_t code = lv_event_get_code(e);
    
    if (LV_EVENT_RELEASED == code)
    {
        rt_list_t *list = tlv_list_get();
        if (NULL == item_ref)
        {
            // 将节点调整到链表最后面:移除节点后添加到最后
            tlv_node_t *node = (tlv_node_t *)item->info;
            rt_list_remove(&node->list);
            rt_list_insert_before(list, &node->list);
        }
        else
        {
            // 将节点调整到参考节点前面:删除节点后插入到参考节点前
            tlv_node_t *node = (tlv_node_t *)item->info;
            tlv_node_t *node_ref = (tlv_node_t *)item_ref->info;
            rt_list_remove(&node->list);
            rt_list_insert_before(&node_ref->list, &node->list);
        }
        
        // 开启multlist调整动画
        lv_multlist_item_move_before(multlist, item, item_ref, true);
        item->major_pos = item->org_x;
        int32_t target_x = lv_multlist_get_focus_pos(multlist, LV_MULTLIST_ALIGN_CENTER, item);
        item->anim_pos_start = pos_x + snapshot->coords.x1 - (item->org_w >> 1);
        lv_multlist_anim(item, 0, 1024, 300, wf_edit_recover_anim_exe, wf_edit_recover_anim_ready, NULL);
        lv_multlist_anim(multlist, pos_x, target_x, 300, (lv_anim_exec_xcb_t)lv_multlist_set_pos, NULL, NULL);
        edit->drage_item = NULL;
        edit->focus_item = item;
    }
    // ...
}

排序功能通常在编辑模式中通过拖拽实现,提供直观的用户体验。

通过以上指南,开发者可以快速理解和实现平铺应用,为智能穿戴设备构建高效、美观的用户界面。

2. 平铺快捷组件

2.1. 快捷组件介绍

快捷组件框架是一套动态可裁剪的组件管理机制,实现对消息模块堆叠功能快捷栏侧边导航栏等核心组件的高效管理, 旨在提升配置管理效率与项目适配灵活性。通过统一的标准化接口,实现对各类核心组件的高效管理,使系统能够根据项目需求灵活增减组件配置,显著增强系统的可扩展性与适配能力,满足不同项目场景的多样化需求。

2.2. 快捷组件框架

快捷组件框架已在 solution\framework\gui_fwk\tlv_fwk.c 实现,主要接口及说明如下:

2.2.1 组件状态管理

  1. 组件状态:用于状态机转换,状态改变需遵循指定的状态机规则(与平铺状态机一致)

enum
{
    TLV_STATE_STARTED,          /**< 启动tileview应用 */
    TLV_STATE_RESUMED,          /**< 激活tileview应用 */
    TLV_STATE_PAUSED,           /**< 暂停tileview应用 */
    TLV_STATE_STOPED,           /**< 销毁tileview应用 */
};
  1. 状态机切换函数

int tlv_comp_change_state(const char *id, lv_obj_t *parent, tlv_state_t state);
  • 功能:切换指定组件的状态

  • 参数:

    • id:组件唯一标识符

    • parent:父级对象

    • state:目标状态

2.2.2 组件注册机制

// 组件启动时调用
static void on_start(void *param)
{
    p_notify = (notification_t *)app_calloc(1, sizeof(notification_t));
    RT_ASSERT(p_notify);

    p_notify->parent = TLV_COMP_GET_PARENT;
}

// 组件恢复时调用
static void on_resume(void *param)
{
    // 恢复组件显示和交互
}

// 组件暂停时调用
static void on_pause(void *param)
{
    // 暂停组件活动
}

// 组件停止时调用
static void on_stop(void *param)
{
    // 释放资源
    p_notify = NULL;
}

// 注册组件到TLV框架
TLV_COMPONENT_REGISTER(notify);

2.3. 内置快捷组件

TLV 组件框架已适配以下核心组件,项目可根据需求进行个性化配置,不需要的组件可直接删除相应目录或移除宏配置:

2.3.1 消息模块

功能:提供系统消息通知功能

fishy

2.3.2 堆叠功能

功能:实现组件的层叠显示与管理

fishy

2.3.3 快捷栏

功能:提供快速访问常用功能的界面

fishy

2.3.4 侧边导航栏

功能:实现侧边式导航菜单,提供主要功能入口

fishy

2.4. 组件使用与配置建议

  • 组件裁剪:不需要的组件可直接删除对应目录或通过宏定义#undef移除

  • 状态管理:组件状态切换应遵循状态机规则,避免非法状态转换

  • 个性化适配:可基于现有组件进行二次开发,通过重写生命周期函数实现定制化需求

  • 资源管理:在on_stop回调中确保释放所有已分配资源,避免内存泄漏

通过以上机制,快捷组件框架能够高效管理各类 UI 组件,为不同项目场景提供灵活的适配能力。