multanim控件

1. 使用场景

场景切换过程中,同一种动画可能反复用到,比如APP切换、TLV切换、表盘切换等。为提到代码重复利用率,方便使用,本控件将常用的动画进行了封装,应用程序只需调用统一的接口,即可实现相应的动画。封装的动画主要有缩放动画、3D翻转动画、轴向压缩动画,mask渐隐动画等

2. 功能介绍

2.1 动画类型

enum
{
    LV_MULTANIM_NONE,         //无效动画
    LV_MULTANIM_ZOOM,         //缩放动画
    LV_MULTANIM_3D,           //3D翻转动画
    LV_MULTANIM_SWITCH,       //反向翻转并带一定缩放
    LV_MULTANIM_TURN,         //半页翻转动画
    LV_MULTANIM_SCALE,        //轴向压缩动画
    LV_MULTANIM_FADE,         //mask渐隐动画
    LV_MULTANIM_OPEN,         //中间向两侧打开的mask动画
	LV_MULTANIM_OPEN,		  //预留,暂不支持
    LV_MULTANIM_ROLL,		  //预留,暂不支持
    LV_MULTANIM_DRAG,		  //预留,暂不支持
	LV_MULTANIM_BOOK,		  //翻书动画,仅58X支持
    LV_MULTANIM_SHUTTLE,	  //飞梭动画,仅58X支持
    LV_MULTANIM_SHUTTER,      //百叶窗动画,仅58X支持
    LV_MULTANIM_MAX,          //无效
};
typedef uint16_t lv_multanim_type;
//目前已封装的动画类型包含以上几种,

2.2 接口说明

接口函数

功能说明

参数说明

返回值说明

lv_multanim_create(lv_obj_t *parent)

创建一个multanim动画实例

parent:父对象指针,新创建的multanim实例将作为该对象的子对象

成功返回创建的multanim对象指针,失败返回NULL

lv_multanim_set_type(lv_obj_t *multanim, lv_multanim_type type)

设置动画类型(需在启动动画前设置)

multanim:multanim实例指针;type:动画类型(如LV_MULTANIM_ZOOM、LV_MULTANIM_FADE等)

返回设置前的动画类型

lv_multanim_set_dir(lv_obj_t *multanim, lv_multanim_dir dir)

设置动画方向

multanim:multanim实例指针;dir:动画方向(如LV_MULTANIM_LEFT、LV_MULTANIM_TOP等,支持组合)

lv_multanim_set_major_img(lv_obj_t *multanim, lv_obj_t *major_img)

设置动画的主图像(必须设置)

multanim:multanim实例指针;major_img:主图像对象指针

lv_multanim_set_minor_img(lv_obj_t *multanim, lv_obj_t *minor_img)

设置动画的次要图像

multanim:multanim实例指针;minor_img:次要图像对象指针

lv_multanim_set_range(lv_obj_t *multanim, int32_t range)

设置动画的范围

multanim:multanim实例指针;range:动画范围值

lv_multanim_set_viewpoint(lv_obj_t *multanim, lv_point_t *start_v, lv_point_t *end_v)

设置动画的视角起止点

multanim:multanim实例指针;start_v:动画起始视角点;end_v:动画结束视角点

lv_multanim_set_zoom(lv_obj_t *multanim, lv_coord_t start_zoom, lv_coord_t zoom_end)

设置动画的缩放起止值

multanim:multanim实例指针;start_zoom:起始缩放值;zoom_end:结束缩放值

lv_multanim_set_process(lv_obj_t *multanim, int32_t process)

设置动画进度(核心动画控制接口)

multanim:multanim实例指针;process:动画进度值[0,1024]

lv_multanim_set_mask(lv_obj_t *multanim, const lv_img_dsc_t *mask_l, const lv_img_dsc_t *mask_r)

为蒙版类动画设置左右蒙版

multanim:multanim实例指针;mask_l:左侧渐隐蒙版;mask_r:右侧渐隐蒙版

lv_multanim_get_proc(lv_obj_t *multanim)

获取当前动画进度

multanim:multanim实例指针

返回当前动画进度值

lv_multanim_get_major_img(lv_obj_t *multanim)

获取动画的主图像对象

multanim:multanim实例指针

返回主图像对象指针

lv_multanim_get_minor_img(lv_obj_t *multanim)

获取动画的次要图像对象

multanim:multanim实例指针

返回次要图像对象指针

lv_multanim_create_mask(const void *src)

根据指定路径读取蒙版的图像描述符

src:蒙版文件/资源路径

返回蒙版的lv_img_dsc_t类型指针

lv_multanim_free_mask(lv_img_dsc_t *dsc)

释放由lv_multanim_create_mask创建的蒙版描述符

dsc:需要释放的蒙版描述符指针

lv_multanim_get_type(lv_obj_t *multanim)

获取当前设置的动画类型

multanim:multanim实例指针

返回当前动画类型

3. 案例详解

3.1 使用流程

动画使用步骤基本流程:

//1.创建multanim实例
lv_obj_t *multanim = lv_multanim_create(parent);
//2.设置动画类型
lv_multanim_set_type(multanim, anim_type);
//3.设置主、次图片
lv_multanim_set_major_img(multanim, major_img);
lv_multanim_set_minor_img(multanim, minor_img);
//4.设置进度值
lv_multanim_set_process(multanim, proc);

3.2 切换动画

3D切换动画的实现使用了该控件,流程如下

static void lv_turn3d_anim_progress(lv_baseanim_t *baseanim, lv_obj_t *anim_obj, int32_t progress)
{
    if (LV_BASEANIM_EXIT_TYPE == lv_baseanim_get_type(baseanim))
    {
        if (NULL == switch_multanim)
        {
            //1.创建multanim实例
            switch_multanim = lv_multanim_create(anim_obj);
            //2.设置动画类型
            lv_multanim_set_type(switch_multanim, LV_MULTANIM_3D);
            //3.设置主图片
            lv_multanim_set_major_img(switch_multanim, anim_obj);
            lv_multanim_set_zoom(switch_multanim, APP_TRANS_ANIM_ZOOM_NONE + 4, APP_TRANS_ANIM_ZOOM_NONE * 0.8f);
            lv_obj_add_event_cb(switch_multanim, lv_switch_multanim_delete, LV_EVENT_DELETE, NULL);
        }
        lv_baseanim_para_t *para = lv_baseanim_get_para(baseanim);
        if (para->flag)
            lv_multanim_set_process(switch_multanim, progress);//4.设置进度值
        else
            lv_multanim_set_process(switch_multanim, -progress);//4.设置进度值
    }
    else if (switch_multanim)
    {
        //3.设置次图片
        lv_multanim_set_minor_img(switch_multanim, anim_obj);
    }
}

BUILTIN_ANIMATION(turn3Danim, LV_SWITCHANIM_TURN_3D, lv_turn3d_anim_progress);

效果展示

fishy

3.3 平铺动画

/**
 * @brief TLV组件切换动画的核心实现函数
 * @param multlist 多列表容器对象(TLV的载体)
 * @param item 当前需要执行动画的列表项
 * @param offset 当前项的偏移量(水平方向,正值向右,负值向左)
 * @note 该函数是LVGL框架下的动画回调,负责根据TLV状态、偏移量和配置的动画类型,
 *       动态管理快照对象、切换动画类型、配置动画参数并更新元素位置
 */
static void tlv_switch_anim(lv_obj_t *multlist, lv_multlist_item_t *item, int32_t offset)
{
    // 1. 基础参数初始化
    int32_t offset_abs = LV_ABS(offset);          // 偏移量的绝对值(消除方向影响)
    lv_coord_t hor_res = lv_disp_get_hor_res(NULL); // 获取屏幕水平分辨率(用于动画范围计算)
    lv_coord_t ver_res = lv_disp_get_ver_res(NULL); // 获取屏幕垂直分辨率(预留参数,当前未使用)

    // 2. 暂停状态(TLV_STATE_PAUSE)的处理逻辑:仅保留屏幕内的快照,超出则销毁
    if (TLV_STATE_PAUSE == p_tlv->state)
    {
        /* 暂停状态下,当元素偏移超出屏幕中心位置时,删除快照释放资源 */
        if (offset_abs > hor_res && item->snapshot)
        {
            lv_obj_del(item->snapshot);  // 销毁快照对象
            item->snapshot = NULL;       // 置空指针,避免野指针
        }

        /* 暂停状态下,元素在屏幕范围内且无快照时,创建快照并显示 */
        if (offset_abs < hor_res && NULL == item->snapshot)
        {
            lv_img_dsc_t *dsc = p_tlv->img_dsc;  // 获取TLV默认的图片数据源
            // 获取当前多列表的中心元素(用于避免快照图片重复)
            lv_multlist_item_t *cent = lv_multlist_get_center_item(multlist);
            
            // 如果中心元素存在且有快照,切换到下一张图片(避免同图重复显示)
            if (cent && cent->snapshot)
                dsc = (dsc == lv_img_get_src(cent->snapshot)) ? (dsc + 1) : dsc;
            
            tlv_item_take_snapshot(item, dsc);  // 为当前项创建快照(基于指定图片数据源)
        }

        // 如果快照存在,取消隐藏标志(确保快照可见)
        if (item->snapshot)
            lv_obj_clear_flag(item->snapshot, LV_OBJ_FLAG_HIDDEN);
    }

    // 3. 恢复状态(TLV_STATE_RESUME)的处理逻辑:销毁所有快照,显示原始元素
    if (TLV_STATE_RESUME == p_tlv->state && item->snapshot)
    {
        lv_obj_del(item->snapshot);  // 销毁快照对象
        item->snapshot = NULL;       // 置空指针
        // 取消原始元素的隐藏标志,恢复正常显示
        lv_obj_clear_flag(item->element, LV_OBJ_FLAG_HIDDEN);
    }

    // 4. 快照存在时,执行动画类型配置和参数更新(核心动画逻辑)
    if (item->snapshot)
    {
        /* 步骤1:根据TLV配置的动画过渡类型,映射到LVGL多动画类型 */
        uint8_t anim_type = TLV_ANIM_TYPE_FIRST;  // 动画类型标记(区分不同动画逻辑分支)
        uint32_t target_anim = LV_MULTANIM_ZOOM;  // 默认动画类型:缩放

        switch (p_tlv->anim_cfg.trans)
        {
        case TLV_ANIM_TRANS_ZOOM:                // 缩放过渡
            target_anim = LV_MULTANIM_ZOOM;
            break;
        case TLV_ANIM_TRANS_FADE:                // 渐变过渡
            target_anim = LV_MULTANIM_FADE;
            anim_type = TLV_ANIM_TYPE_SECOND;    // 渐变使用第二种动画逻辑
            break;
        case TLV_ANIM_TRANS_TURN_3D_HOR:         // 3D水平旋转过渡
            target_anim = LV_MULTANIM_3D;
            break;
        case TLV_ANIM_TRANS_TURN_Y:              // Y轴旋转过渡(映射为缩放动画)
            target_anim = LV_MULTANIM_SCALE;
            break;
        case TLV_ANIM_TRANS_DRAG:                // 拖拽旋转过渡
            target_anim = LV_MULTANIM_TURN;
            break;
        default:                                 // 未知类型,使用默认缩放
            break;
        }

        /* 步骤2:初始化多动画对象(multanim),仅首次创建 */
        if (NULL == p_tlv->multanim)
        {
            // 创建多动画对象,绑定到TLV的多列表容器
            p_tlv->multanim = lv_multanim_create(p_tlv->multlist);
            // 添加标志位:
            // - LV_OBJ_FLAG_PRESS_LOCK:按压锁定(避免重复触发)
            // - LV_OBJ_FLAG_EVENT_BUBBLE:事件冒泡(向上传递事件)
            // - LV_OBJ_FLAG_CLICKABLE:可点击(响应交互)
            lv_obj_add_flag(p_tlv->multanim, LV_OBJ_FLAG_PRESS_LOCK | LV_OBJ_FLAG_EVENT_BUBBLE | LV_OBJ_FLAG_CLICKABLE);
        }

        /* 步骤3:动画类型切换时,重置动画参数(遮罩、缩放等) */
        // 先设置动画类型,若返回值与目标类型不一致,说明需要切换参数
        if (target_anim != lv_multanim_set_type(p_tlv->multanim, target_anim))
        {
            /* 子步骤3.1:遮罩资源管理(渐变/展开动画需要遮罩) */
            if (LV_MULTANIM_FADE == target_anim || LV_MULTANIM_OPEN == target_anim)
            {
                // 懒加载左遮罩:为空时从资源池加载并创建
                if (NULL == p_tlv->mask_l)
                    p_tlv->mask_l = lv_multanim_create_mask(APP_GET_IMG_FROM_APP(trans_anim, img_mask_left));
                // 懒加载右遮罩:为空时从资源池加载并创建
                if (NULL == p_tlv->mask_r)
                    p_tlv->mask_r = lv_multanim_create_mask(APP_GET_IMG_FROM_APP(trans_anim, img_mask_right));
                // 将左右遮罩绑定到多动画对象
                lv_multanim_set_mask(p_tlv->multanim, p_tlv->mask_l, p_tlv->mask_r);
            }
            else
            {
                // 非渐变/展开动画:释放无用的遮罩资源
                // 获取默认遮罩资源(用于对比是否需要释放)
                lv_img_dsc_t *mask_l = (lv_img_dsc_t *)APP_GET_IMG_FROM_APP(trans_anim, img_mask_left);
                lv_img_dsc_t *mask_r = (lv_img_dsc_t *)APP_GET_IMG_FROM_APP(trans_anim, img_mask_right);

                // 左遮罩存在且与默认资源不一致 → 释放
                if (p_tlv->mask_l && mask_l != p_tlv->mask_l)
                    lv_multanim_free_mask(p_tlv->mask_l);
                // 右遮罩存在且与默认资源不一致 → 释放
                if (p_tlv->mask_r && mask_r != p_tlv->mask_r)
                    lv_multanim_free_mask(p_tlv->mask_r);
                // 置空遮罩指针
                p_tlv->mask_l = NULL;
                p_tlv->mask_r = NULL;
            }

            /* 子步骤3.2:缩放参数配置(不同动画类型对应不同缩放值) */
            if (LV_MULTANIM_ZOOM == target_anim)
            {
                // 普通缩放:默认缩放值 → 半倍缩放(APP_TRANS_ANIM_ZOOM_NONE为基础缩放值)
                lv_multanim_set_zoom(p_tlv->multanim, APP_TRANS_ANIM_ZOOM_NONE, APP_TRANS_ANIM_ZOOM_NONE >> 1);
            }
            else if (LV_MULTANIM_3D == target_anim)
            {
                // 3D旋转:基础缩放+2 → 基础缩放*0.7(营造3D透视效果)
                lv_multanim_set_zoom(p_tlv->multanim, APP_TRANS_ANIM_ZOOM_NONE + 2, APP_TRANS_ANIM_ZOOM_NONE * 0.7f);
            }
            else if (LV_MULTANIM_SCALE == target_anim)
            {
                // Y轴旋转:固定缩放值(基础缩放+2),保持大小不变
                lv_multanim_set_zoom(p_tlv->multanim, APP_TRANS_ANIM_ZOOM_NONE + 2, APP_TRANS_ANIM_ZOOM_NONE + 2);
            }
            else
            {
                // 其他动画:使用默认缩放值(无缩放变化)
                lv_multanim_set_zoom(p_tlv->multanim, APP_TRANS_ANIM_ZOOM_NONE, APP_TRANS_ANIM_ZOOM_NONE);
            }
        }

        /* 步骤4:动画位置与进度控制(区分两种动画类型) */
        // 非缩放动画:将快照居中显示(缩放动画由参数控制位置)
        if (LV_MULTANIM_ZOOM != target_anim)
            lv_obj_center(item->snapshot);

        // 计算动画进度:偏移量映射到0~1024范围(LVGL动画进度常用1024为满值)
        int32_t proc = (offset << 10) / hor_res;

        // 动画类型1(默认:缩放/3D/旋转等)
        if (anim_type == TLV_ANIM_TYPE_FIRST)
        {
            // 偏移量绝对值 ≤ 屏幕3/4宽度:执行核心动画
            if (offset_abs <= (hor_res * 3 / 4))
            {
                // 偏移量绝对值 ≤ 屏幕1/4宽度:设置为主动画图片,恢复默认缩放
                if (offset_abs <= (hor_res >> 2))
                {
                    if (LV_MULTANIM_ZOOM != target_anim)
                        lv_img_set_zoom(item->snapshot, APP_TRANS_ANIM_ZOOM_NONE);
                    // 将当前快照设为动画的主图片(核心显示层)
                    lv_multanim_set_major_img(p_tlv->multanim, item->snapshot);
                }

                // 如果当前快照是次动画图片:调整进度值(修正方向)
                if (item->snapshot == lv_multanim_get_minor_img(p_tlv->multanim))
                    proc = proc > 0 ? (proc - 1024) : (proc + 1024);
                
                // 设置动画进度(驱动动画执行)
                lv_multanim_set_process(p_tlv->multanim, proc);
            }
            else
            {
                // 偏移量超出屏幕3/4宽度:将快照移至背景层,设为次动画图片
                lv_obj_move_background(item->snapshot);
                lv_multanim_set_minor_img(p_tlv->multanim, item->snapshot);
            }
        }
        // 动画类型2(渐变动画)
        else
        {
            // 偏移量绝对值 ≤ 屏幕1/2宽度:居中显示,设为主动画图片并更新进度
            if (offset_abs <= (hor_res >> 1))
            {
                lv_obj_center(item->snapshot);
                lv_multanim_set_major_img(p_tlv->multanim, item->snapshot);
                lv_multanim_set_process(p_tlv->multanim, proc);
            }
            else
            {
                // 偏移量超出屏幕1/2宽度:移至背景层,设为次动画图片
                lv_obj_move_background(item->snapshot);
                lv_multanim_set_minor_img(p_tlv->multanim, item->snapshot);
            }
        }
    }

    // 5. 更新原始元素位置(无论快照是否存在,保证元素跟随偏移量移动)
    if (item->element)
    {
        // 将原始元素居中对齐,水平偏移量为当前offset(垂直无偏移)
        lv_obj_align(item->element, LV_ALIGN_CENTER, offset, 0);
    }
}

效果展示

fishy

4. 注意事项

该控件仅针对动画本身做了按进度的功能封装,实际使用时需结合具体业务场景做适配处理。以 APP 切换动画为例,可在注册的切换回调中,调用该控件接口完成初始化与进度调控。