中国产业数据库及企业互动平台

杰理UI设计工具:页面转场动画

发布者:杰理微电子
时间:2025-08-01
收藏
已收藏

在现代移动应用和智能设备中,流畅的页面转场动画是提升用户体验的关键因素。本文将带您一步步实现类似手机的滑动页面切换效果,让你的界面更具现代感!

项目概述

本教程主要展示如何在UI工具中实现流畅的页面转场动画效果。通过自定义输入设备处理和页面切换逻辑,我们可以在LVGL框架上实现类似手机应用的滑动切换页面功能。

界面设计与布局

首先,我们需要使用UI工具创建几个标准页面作为我们的演示界面。

  1. 1. 使用UI工具创建一个空项目
  2. 2. 创建4个标准页面

图片
图1 页面布局

实现页面切换功能

原生的LVGL库并不支持直接按住页面滑动来切换的功能。为了实现这一效果,我们需要进行一些自定义开发。

第一步:创建页面列表

在UI工具的资源 - 页面列表管理中,添加一个页面列表,将我们刚才创建的4个页面全部添加进去:

图片
图2 页面列表

💡 关键点:页面列表是实现页面切换的基础,它定义了页面的切换顺序和可访问范围。

第二步:自定义输入设备处理

为了实现滑动切换功能,我们需要在custom目录下创建两个文件:indev_pointer.hindev_pointer.c。这两个文件将负责处理触摸输入并转换为页面切换动作。

// indev_pointer.h
#ifndef __INDEV_POINTER_H__
#define __INDEV_POINTER_H__

#ifdef __cplusplus
extern'C' {
#endif

#include 'lvgl.h'

void indev_pointer_init();

void indev_pointer_set_move_mode(int32_t mode);

#ifdef __cplusplus
}
#endif

#endif
// indev_pointer.c
#include 'indev_pointer.h'
#include 'lv_drivers/sdl/sdl.h'
#include 'gui_guider.h'
#if 1
#define USE_GUIBUILDER_SIMULATOR
#endif

static lv_indev_drv_t indev_pointer_drv;
static lv_indev_t *indev;

static gui_scr_switch_t scr_switch;
static gui_scr_switch_anim_t gui_scr_switch_anim;   

static bool is_switch_enable = true/* 标记是否启用页面切换 */
static bool is_first_press = false/* 标记是否为初次按下状态 */
static bool is_moving = false;      /* 标记是否为移动状态 */
static bool is_scroll_obj = false;  /* 标记当前是否有正在滑动的控件 */

static voidscr_switch_init();

static void indev_read_timer_cb(lv_timer_t *timer);
static void indev_proc(lv_indev_t * i, lv_indev_data_t * data);
static void indev_proc_press(_lv_indev_proc_t * proc);
static void indev_proc_press_top_layer(_lv_indev_proc_t * proc);
static void indev_proc_release(_lv_indev_proc_t * proc);
static void indev_proc_reset_query_handler(lv_indev_t * indev);

static void anim_start_cb(int32_t src_id);
static void anim_end_cb(int32_t src_id);
static void load_scr_cb(int32_t src_id);

void indev_pointer_init()
{
    lv_indev_drv_init(&indev_pointer_drv);
    indev_pointer_drv.type = LV_INDEV_TYPE_POINTER;
#ifdef USE_GUIBUILDER_SIMULATOR
    indev_pointer_drv.read_cb = sdl_mouse_read;
#else
    indev_pointer_drv.read_cb = sdl_mouse_read;//TODO: 需要修改为设备自定义的鼠标读取函数
#endif
    indev = lv_indev_drv_register(&indev_pointer_drv);
    if (indev->driver->read_timer) {
        lv_timer_del(indev->driver->read_timer);
    }
    indev->driver->read_timer = lv_timer_create(indev_read_timer_cb, LV_INDEV_DEF_READ_PERIOD, indev);

    scr_switch_init();
}

void indev_pointer_set_move_mode(int32_t mode)
{
    gui_scr_switch_anim.mode = mode;
}

static void scr_switch_init()
{
    scr_switch.move_en = true/* 是否启用页面切换 */
    scr_switch.slide_en = true/* 是否启用滑动切换 */
    scr_switch.loop_head = true/* 是否循环切换到第一个页面 */
    scr_switch.loop_tail = true/* 是否循环切换到最后一个页面 */
    gui_scr_switch_anim.mode = GUI_SCR_MOVE_MODE_ROTATE_EDGE; /* 默认使用RotateEdge模式 */
    gui_scr_switch_set_get_scr_cb(ui_get_setup_scr);
    gui_scr_switch_set_manager(&main_magger);
    
    //设置页面过渡时的背景页面的背景颜色
    lv_obj_t *bg_scr = gui_scr_switch_get_bg_scr();
    lv_obj_set_style_bg_color(bg_scr, lv_color_hex(0x000000), LV_PART_MAIN);
}

static void indev_read_timer_cb(lv_timer_t *timer)
{
    lv_indev_data_t data;

    if(indev->driver->disp == NULLreturn;

    indev_proc_reset_query_handler(indev);

    if(indev->proc.disabled) return;

    /*Read the data*/
    _lv_indev_read(indev, &data);

    /*The active object might be deleted even in the read function*/
    indev_proc_reset_query_handler(indev);

    indev->proc.state = data.state;

    /*Save the last activity time*/
    if(indev->proc.state == LV_INDEV_STATE_PRESSED)
    {
        indev->driver->disp->last_activity_time = lv_tick_get();
    }

    indev_proc(indev, &data);
}

static void indev_proc(lv_indev_t * i, lv_indev_data_t * data)
{
    lv_disp_t * disp = i->driver->disp;
    /*Save the raw points so they can be used again in _lv_indev_read*/
    i->proc.types.pointer.last_raw_point.x = data->point.x;
    i->proc.types.pointer.last_raw_point.y = data->point.y;

    if(disp->driver->rotated == LV_DISP_ROT_180 || disp->driver->rotated == LV_DISP_ROT_270) {
        data->point.x = disp->driver->hor_res - data->point.x - 1;
        data->point.y = disp->driver->ver_res - data->point.y - 1;
    }
    if(disp->driver->rotated == LV_DISP_ROT_90 || disp->driver->rotated == LV_DISP_ROT_270) {
        lv_coord_t tmp = data->point.y;
        data->point.y = data->point.x;
        data->point.x = disp->driver->ver_res - tmp - 1;
    }

    i->proc.types.pointer.act_point.x = data->point.x;
    i->proc.types.pointer.act_point.y = data->point.y;

    if(data->state == LV_INDEV_STATE_PRESSED)
    {
        indev_proc_press(&i->proc);
    }
    elseif(data->state == LV_INDEV_STATE_RELEASED)
    {
        indev_proc_release(&i->proc);
    }

    i->proc.types.pointer.last_point.x = i->proc.types.pointer.act_point.x;
    i->proc.types.pointer.last_point.y = i->proc.types.pointer.act_point.y;
}

static void indev_proc_press(_lv_indev_proc_t * proc)
{
    if (!is_first_press) {
        is_first_press = true;
        is_switch_enable = gui_scr_switch_is_enable() & scr_switch.move_en;
        if (!is_switch_enable) return;

        /* 初始化 */
        scr_switch.start_pos = proc->types.pointer.act_point;
        scr_switch.end_pos = proc->types.pointer.act_point;
        scr_switch.cur_pos = proc->types.pointer.act_point;
        scr_switch.start_left = proc->types.pointer.act_point.x;
        scr_switch.start_pos_x = proc->types.pointer.act_point.x;

        /* 获取当前页面的偏移量,如果不为0,说明是处于页面切换的动画中,需要保存当前偏移量 */
        lv_obj_t *scr = lv_scr_act();
        scr_switch.scr_offset_pos.x = lv_obj_get_x(scr);
        scr_switch.scr_offset_pos.y = lv_obj_get_y(scr);

        scr_switch.first_result = gui_scr_switch_get_dir(&scr_switch.start_pos, &scr_switch.end_pos);
        scr_switch.result = scr_switch.first_result;
    } else {
        if (!is_switch_enable) return;
        if (is_scroll_obj) return;

        /* 判断当前是否有滑动的控件,如果有,则不进行页面切换 */
        /* 这种方式只能判断容器有无进行滚动,针对Slider控件无效 */
        if (!is_scroll_obj && !is_moving) {
            lv_obj_t * act_obj = NULL;
            lv_disp_t * disp = lv_disp_get_default();
            act_obj = lv_indev_search_obj(lv_disp_get_layer_sys(disp), &proc->types.pointer.act_point);
            if (act_obj == NULL) act_obj = lv_indev_search_obj(lv_disp_get_layer_top(disp), &proc->types.pointer.act_point);
            if (act_obj == NULL) act_obj = lv_indev_search_obj(lv_disp_get_scr_act(disp), &proc->types.pointer.act_point);
            if (act_obj != NULL) {
                while (act_obj != NULL) {
                    if (lv_obj_is_scrolling(act_obj)) {
                        is_scroll_obj = true;
                        return;
                    }
                    act_obj = lv_obj_get_parent(act_obj);
                }
            }
        }
        if (is_scroll_obj) return;

        scr_switch.cur_pos = proc->types.pointer.act_point;
        scr_switch.result = gui_scr_switch_get_dir(&scr_switch.start_pos, &scr_switch.cur_pos);
        if (!is_moving) {
            /* 判断移动方向是否为左右,如果是,则设置为移动 */
            if (scr_switch.result & (LV_DIR_LEFT | LV_DIR_RIGHT)) {
                is_moving = true;
            }
        } else {
            gui_scr_switch_move(&scr_switch, gui_scr_switch_anim.mode);
        }
    }
}

static void indev_proc_press_top_layer(_lv_indev_proc_t * proc)
{
    /* 用于处理顶层页面的显示*/
}

static void indev_proc_release(_lv_indev_proc_t * proc)
{
    is_switch_enable = true;
    is_first_press = false;
    if (is_moving) {
        LV_LOG_WARN('release');
        is_moving = false;

        gui_scr_switch_anim.anim_start_cb = anim_start_cb;
        gui_scr_switch_anim.anim_end_cb = anim_end_cb;
        gui_scr_switch_anim.load_scr = load_scr_cb;
        gui_scr_switch_anim.src_id = scr_switch.src_in_touchdown;
        gui_scr_switch_move_anim(&gui_scr_switch_anim);
    }
    is_scroll_obj = false;
}

/**
 * Process a new point from LV_INDEV_TYPE_BUTTON input device
 * @param i pointer to an input device
 * @param data pointer to the data read from the input device
 * Reset input device if a reset query has been sent to it
 * @param indev pointer to an input device
 */

static void indev_proc_reset_query_handler(lv_indev_t * indev)
{
    if(indev->proc.reset_query) {
        indev->proc.types.pointer.act_obj           = NULL;
        indev->proc.types.pointer.last_obj          = NULL;
        indev->proc.types.pointer.scroll_obj          = NULL;
        indev->proc.long_pr_sent                    = 0;
        indev->proc.pr_timestamp                    = 0;
        indev->proc.longpr_rep_timestamp            = 0;
        indev->proc.types.pointer.scroll_sum.x        = 0;
        indev->proc.types.pointer.scroll_sum.y        = 0;
        indev->proc.types.pointer.scroll_dir = LV_DIR_NONE;
        indev->proc.types.pointer.scroll_throw_vect.x = 0;
        indev->proc.types.pointer.scroll_throw_vect.y = 0;
        indev->proc.types.pointer.gesture_sum.x     = 0;
        indev->proc.types.pointer.gesture_sum.y     = 0;
        indev->proc.reset_query                     = 0;
    }
}

static void anim_start_cb(int32_t src_id)
{
    LV_LOG_WARN('anim_start_cb %d', src_id);
}

static void anim_end_cb(int32_t src_id)
{
    LV_LOG_WARN('anim_end_cb %d', src_id);
}

static void load_scr_cb(int32_t src_id)
{
    LV_LOG_WARN('load_scr_cb %d', src_id);
    gui_scr_t *scr = gui_scr_get(src_id);
    gui_scr_t *act_scr = gui_scr_get_act();
    lv_disp_t *disp = lv_disp_get_default();
    if (scr == act_scr || scr == NULL) {
        disp->prev_scr = NULL;
        disp->bg_scr = NULL;
    } else {
        lv_obj_set_pos(scr->scr, 00);
        disp->prev_scr = NULL;
        disp->bg_scr = NULL;
        ui_load_scr_anim(&guider_ui, scr, LV_SCR_LOAD_ANIM_NONE, 00falsefalsefalse);
    }
}