平铺应用

1. 简介

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

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

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

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

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

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

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

fishy

2. 注册平铺应用

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

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

参数说明:

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

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

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

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

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

3. 平铺应用实现

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

3.1 生命周期函数

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

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

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

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

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

fishy

3.2 事件处理

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

fishy

3.3 核心功能模块

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

  1. TLV 应用管理

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

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

  1. 表盘管理

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

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

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

  1. 辅助窗口

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

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

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

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

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

  1. 切换动画

  • TLV 滑动切换动画配置

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

3.4 开发注意事项

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

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

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

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

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

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

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

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

  • start->resume->pause->stop

  • start->pause->stop

  • start->stop

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

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

4. 平铺应用管理

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,系统在读取显示时会跳过该节点。 注意: 删除平铺应用时,该应用不能处于运行状态。

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;
    }
    // ...
}

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

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