杰理UI设计工具:页面转场动画
在现代移动应用和智能设备中,流畅的页面转场动画是提升用户体验的关键因素。本文将带您一步步实现类似手机的滑动页面切换效果,让你的界面更具现代感!
项目概述
本教程主要展示如何在UI工具中实现流畅的页面转场动画效果。通过自定义输入设备处理和页面切换逻辑,我们可以在LVGL框架上实现类似手机应用的滑动切换页面功能。
界面设计与布局
首先,我们需要使用UI工具创建几个标准页面作为我们的演示界面。
1. 使用UI工具创建一个空项目 2. 创建4个标准页面
原生的LVGL库并不支持直接按住页面滑动来切换的功能。为了实现这一效果,我们需要进行一些自定义开发。 在UI工具的 💡 关键点:页面列表是实现页面切换的基础,它定义了页面的切换顺序和可访问范围。 为了实现滑动切换功能,我们需要在实现页面切换功能
第一步:创建页面列表
资源
- 页面列表管理
中,添加一个页面列表,将我们刚才创建的4个页面全部添加进去:第二步:自定义输入设备处理
custom
目录下创建两个文件:indev_pointer.h
和indev_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 == NULL) return;
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, 0, 0);
disp->prev_scr = NULL;
disp->bg_scr = NULL;
ui_load_scr_anim(&guider_ui, scr, LV_SCR_LOAD_ANIM_NONE, 0, 0, false, false, false);
}
}