网格列表
1、定义图标结构体
图标大小+中心点坐标
// 保存网格中图标大小、位置XY的结构体
typedef struct {
float size;
float center_x;
float center_y;
} grid_item_info;
2、构建网格数据
使用二维数组保存数据
/**
* 构建指定行数的网格数据(rows*3)(正常大小、无缩放/偏移)
*/
grid_item_info **build_gird_items(int rows)
{
grid_item_info **items = (grid_item_info **)malloc(rows * sizeof(grid_item_info *));
for (int i = 0; i < rows; i++)
{
items[i] = (grid_item_info *)malloc(3 * sizeof(grid_item_info));
init_row_data(items[i], i);
}
return items;
}
3、排列图标
两重For循环创建图标控件,并设置到指定位置
// 创建网格项
for (int i = 0; i < panel_data->row_count; i++)
{
for (int j = 0; j < 3; j++)
{
lv_obj_t *child = create_item(
panel,
panel_data->grid_items[i][j].size,
panel_data->grid_items[i][j].center_x,
panel_data->grid_items[i][j].center_y + panel_data->total_scroll_distance);
// 创建并初始化拖拽用户数据
drag_user_data_t *drag_data = (drag_user_data_t *)malloc(sizeof(drag_user_data_t));
memset(drag_data, 0, sizeof(drag_user_data_t));
drag_data->pressed_item_index = -1;
drag_data->last_pressed_point_x = -1;
drag_data->last_pressed_point_y = -1;
lv_obj_set_user_data(child, drag_data);
// 添加事件回调
lv_obj_add_flag(child, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_event_cb(child, child_event_cb, LV_EVENT_LONG_PRESSED, NULL);
lv_obj_add_event_cb(child, child_event_cb, LV_EVENT_PRESSING, NULL);
lv_obj_add_event_cb(child, child_event_cb, LV_EVENT_RELEASED, NULL);
}
}
4、滑动处理
获取滑动距离,然后更新所有子控件
// 获取点击坐标
lv_event_code_t code = lv_event_get_code(e);
lv_indev_t *indev = lv_event_get_indev(e);
lv_point_t point;
lv_indev_get_point(indev, &point);
...
panel_data->slide_diff_y = point.y - panel_data->last_point_y;
panel_data->last_point_y = point.y;
// 累加总的滑动距离
add_scroll_distance(panel_data->slide_diff_y, panel_data);
...
// For循环遍历所有子控件,更新滑动后的位置
for (size_t i = 0; i < lv_obj_get_child_cnt(panel); i++)
{
lv_obj_t *child = lv_obj_get_child(panel, i);
lv_coord_t child_y = lv_obj_get_y(child);
lv_coord_t child_h = lv_obj_get_height(child);
grid_item_info info = panel_data->grid_items[i / 3][i % 3];
update_item(child, info.size, info.center_x, info.center_y + panel_data->total_scroll_distance);
}
惯性滚动
1、计算速度
使用上一次的滑动距离作为开始速度
// 计算滑动距离
panel_data->slide_diff_y = point.y - panel_data->last_point_y;
panel_data->last_point_y = point.y;
// 使用上一次滑动距离作为惯性滚动的开始速度
panel_data->fling_start_diff_y = panel_data->slide_diff_y;
2、惯性滚动动画
重点:动画时长、滚动减速、动画结束后自动对齐图标
if (code == LV_EVENT_RELEASED)
{
// 使用上一次滑动距离作为惯性滚动的开始速度
panel_data->fling_start_diff_y = panel_data->slide_diff_y;
printf("SPEED = %d\n", panel_data->fling_start_diff_y);
panel_data->last_point_y = -1;
panel_data->slide_diff_y = 0;
panel_data->no_slide_flag = true;
// 构建惯性滚动动画
lv_anim_init(&panel_data->fling_anim);
lv_anim_set_var(&panel_data->fling_anim, panel);
lv_anim_set_values(&panel_data->fling_anim, 0, 1000);
lv_anim_set_exec_cb(&panel_data->fling_anim, fling_animation_cb);
lv_anim_set_path_cb(&panel_data->fling_anim, lv_anim_path_ease_out);
// 依据开始速度,计算动画结束时间
int fling_time = abs(panel_data->fling_start_diff_y) * 10; // lv_anim_speed_to_time(10, 0, abs(panel_data->fling_start_diff_y));
lv_anim_set_time(&panel_data->fling_anim, fling_time);
lv_anim_start(&panel_data->fling_anim);
return;
}
/**
* 惯性滚动动画的回调
*/
static void fling_animation_cb(void *var, int v)
{
lv_obj_t *panel = (lv_obj_t *)var;
panel_user_data_t *panel_data = (panel_user_data_t *)lv_obj_get_user_data(panel);
// 按动画进度计算出当前滚动速度,然后更新滑动总距离
float anim_value = v * 1.0f / 1000;
add_scroll_distance(panel_data->fling_start_diff_y * (1 - anim_value), panel_data);
float scroll_factor = panel_data->total_scroll_distance * 1.0f / app_item_size;
transform_grid(panel_data->grid_items, panel_data->row_count, -scroll_factor);
// 遍历子控件,更新子控件滑动后的位置
for (size_t i = 0; i < lv_obj_get_child_cnt(panel); i++)
{
lv_obj_t *child = lv_obj_get_child(panel, i);
grid_item_info info = panel_data->grid_items[i / 3][i % 3];
update_item(child, info.size, info.center_x,
info.center_y + panel_data->total_scroll_distance);
}
// 动画结束后执行自动对齐动画
if (anim_value == 1)
....
}
}
自动对齐
1、计算对齐图标的最短滚动距离
总的滑动距离 对 图标的高度 求余,然后和 图标高度/2 比较大小。
panel_data->snap_start_distance = panel_data->total_scroll_distance;
panel_data->snap_distance_consumed = 0;
// 计算出图标对齐需要的最短滚动距离
panel_data->snap_distance = (distance > app_item_size / 2) ? distance - app_item_size : distance;
2、自动对齐动画
// 构建动画并执行
lv_anim_init(&panel_data->snap_anim);
lv_anim_set_var(&panel_data->snap_anim, panel);
lv_anim_set_values(&panel_data->snap_anim, 0, 1000);
lv_anim_set_exec_cb(&panel_data->snap_anim, snap_animation_cb);
lv_anim_set_path_cb(&panel_data->snap_anim, lv_anim_path_ease_in);
int snap_time = abs(panel_data->snap_distance) * 10; // lv_anim_speed_to_time(50, 0, panel_data->snap_distance);
lv_anim_set_time(&panel_data->snap_anim, snap_time);
lv_anim_start(&panel_data->snap_anim);
/**
* 图标自动对其动画的回调
*/
static void snap_animation_cb(void *var, int v)
{
lv_obj_t *panel = (lv_obj_t *)var;
panel_user_data_t *panel_data = (panel_user_data_t *)lv_obj_get_user_data(panel);
// 按动画进度计算出当前滚动速度,然后更新滑动总距离
float anim_value = v * 1.0f / 1000;
panel_data->snap_distance_consumed = panel_data->snap_distance * anim_value;
add_scroll_distance(panel_data->snap_start_distance +
panel_data->snap_distance_consumed - panel_data->total_scroll_distance,
panel_data);
float scroll_factor = panel_data->total_scroll_distance * 1.0f / app_item_size;
transform_grid(panel_data->grid_items, panel_data->row_count, -scroll_factor);
// 遍历子控件,更新子控件滑动后的位置
for (size_t i = 0; i < lv_obj_get_child_cnt(panel); i++)
{
lv_obj_t *child = lv_obj_get_child(panel, i);
grid_item_info info = panel_data->grid_items[i / 3][i % 3];
update_item(child, info.size, info.center_x,
info.center_y + panel_data->total_scroll_distance);
}
}
拖动排序
1、开始拖动
记录拖动控件的信息(下标、拖动前的位置等信息)
if (code == LV_EVENT_LONG_PRESSED && !drag_data->child_slide_flag)
{
// 拖动控件的下标
drag_data->pressed_item_index = lv_obj_get_index(item);
drag_data->pressed_item = item;
drag_data->first_pressed_point_x = point.x;
drag_data->first_pressed_point_y = point.y;
// 拖动前的位置信息等
lv_coord_t size = lv_obj_get_width(drag_data->pressed_item);
lv_coord_t center_x = lv_obj_get_x(drag_data->pressed_item) + size / 2;
lv_coord_t center_y = lv_obj_get_y(drag_data->pressed_item) + size / 2;
drag_data->default_center_x = center_x;
drag_data->default_center_y = center_y;
printf("child_event_cb skip. 5\n");
return;
}
2、图标跟手
计算拖动距离,更新拖动图标位置
lv_coord_t size = lv_obj_get_width(drag_data->pressed_item);
lv_coord_t center_x = lv_obj_get_x(drag_data->pressed_item) + size / 2;
lv_coord_t center_y = lv_obj_get_y(drag_data->pressed_item) + size / 2;
lv_obj_move_foreground(drag_data->pressed_item);
lv_obj_set_style_border_color(drag_data->pressed_item, lv_color_hex(0xff0000), LV_PART_MAIN);
// 计算图标拖动距离,更新图标位置
lv_coord_t cx = drag_data->default_center_x + (point.x - drag_data->first_pressed_point_x);
lv_coord_t cy = drag_data->default_center_y + (point.y - drag_data->first_pressed_point_y);
update_item(drag_data->pressed_item, size, cx, cy);
3、拖动结束
找到拖动的终点/目标位置,执行图标交换动画
if (drag_data->pressed_item != NULL && drag_data->pressed_item_index >= 0)
{
// 找到拖动的目标位置(下标)
int target = find_item_index_under(point.x, point.y, panel_data);
if (target < 0)
{
lv_obj_move_to_index(drag_data->pressed_item, drag_data->pressed_item_index);
}
else
{
// 重置图标的属性
lv_obj_set_style_opa(drag_data->pressed_item, 0, LV_PART_MAIN);
lv_obj_move_to_index(drag_data->pressed_item, drag_data->pressed_item_index);
lv_obj_set_style_border_color(drag_data->pressed_item, lv_color_hex(0xffffff), LV_PART_MAIN);
// 开始交换位置
change_order(drag_data->pressed_item->parent, drag_data->pressed_item_index, target);
}
}
4、计算交换动画的开始数据和结束数据
froms、tos
// 计算动画过程中的关键数据:图标动画前的位置信息(froms),图标动画后的位置信息(tos)
for (int i = 0; i < size; i++)
{
int index = source < target ? source + i : source - i;
lv_obj_t *child = lv_obj_get_child(panel, index);
// 图标动画前的位置信息(froms)
panel_data->order_data->froms[i] = (grid_item_info){
.size = lv_obj_get_width(child),
.center_x = lv_obj_get_x(child) + lv_obj_get_width(child) / 2,
.center_y = lv_obj_get_y(child) + lv_obj_get_height(child) / 2};
// 图标动画后的位置信息(tos)
if (i == 0)
{
// source位置的图标需要拖动到target位置,不是回到前一个位置
init_grid_item(&panel_data->order_data->tos[i], target);
panel_data->order_data->tos[i].center_y += panel_data->total_scroll_distance;
}
else
{
// 非source位置图标,统统往前移动
int pre_index = index + (source < target ? -1 : 1);
init_grid_item(&panel_data->order_data->tos[i], pre_index);
panel_data->order_data->tos[i].center_y += panel_data->total_scroll_distance;
}
}
5、开启排序动画
// 启动动画
lv_anim_init(&panel_data->range_anim);
lv_anim_set_var(&panel_data->range_anim, panel);
lv_anim_set_values(&panel_data->range_anim, 0, 1000);
lv_anim_set_exec_cb(&panel_data->range_anim, range_animation_cb);
lv_anim_set_path_cb(&panel_data->range_anim, lv_anim_path_linear);
lv_anim_set_time(&panel_data->range_anim, 450);
lv_anim_start(&panel_data->range_anim);
/**
* 图标拖动后排序动画的回调
*/
static void range_animation_cb(void *var, int v)
{
lv_obj_t *panel = (lv_obj_t *)var;
panel_user_data_t *panel_data = (panel_user_data_t *)lv_obj_get_user_data(panel);
order_animation_data_t *order_data = panel_data->order_data;
float anim_value = v * 1.0f / 1000;
int size = abs(order_data->target - order_data->source) + 1;
lv_obj_t *source_item = NULL;
// 遍历位置改变的图标
for (int i = 0; i < size; i++)
{
int index = order_data->source + (order_data->source < order_data->target ? i : -i);
lv_obj_t *child = lv_obj_get_child(panel, index);
// 拿到该图标动画前和动画后的位置信息
grid_item_info from = order_data->froms[i];
grid_item_info to = order_data->tos[i];
// 按动画进度更新图标位置
update_item(
child,
from.size + (to.size - from.size) * anim_value,
from.center_x + (to.center_x - from.center_x) * anim_value,
from.center_y + (to.center_y - from.center_y) * anim_value);
if (i == 0 && anim_value > 0.1f)
{
lv_obj_set_style_opa(child, 255, LV_PART_MAIN);
}
// 动画结束,更新图标的Index(current->previous)
if (anim_value == 1.0f)
{
if (i != 0)
{
int pre_index = index + (order_data->source < order_data->target ? -1 : 1);
lv_obj_move_to_index(child, pre_index);
}
else
{
source_item = child;
}
}
}
// 动画结束,更新图标的Index(source->target)
if (anim_value == 1.0f && source_item != NULL)
{
lv_obj_move_to_index(source_item, order_data->target);
}
}
图标缩放
1、定义图标转换/变化参数表
// 保存图标缩放值、位置数据的结构体
typedef struct {
float scale;
float offset_x;
float offset_y;
} transform_info;
/**
* 5*3的图标变换信息表
*/
const transform_info transforms[5][3] = {
// transform_info{scale, offset_x, offset_y}
{{+0.32, +0.08, +0.10}, {+0.34, +0.00, +0.05}, {+0.32, -0.08, +0.10}},
{{+0.75, +0.01, +0.03}, {+1.00, +0.00, -0.02}, {+0.75, -0.01, +0.03}},
{{+1.00, -0.04, +0.00}, {+1.20, +0.00, +0.00}, {+1.00, +0.04, +0.00}},
{{+0.75, +0.01, -0.03}, {+1.00, +0.00, +0.02}, {+0.75, -0.01, -0.03}},
{{+0.32, +0.08, -0.10}, {+0.34, +0.00, -0.05}, {+0.32, -0.08, -0.10}},
};
2、将参数表应用到网格列表
/**
* 依据transforms设置的缩放等数据,对网格列表进行缩放、偏移
*
* @param rows 列表总行数
* @param scroll_row_number 当前滚动的行数,可以为为小数
*/
void transform_grid(grid_item_info **items, int rows, float scroll_row_number)
{
float start_pos = scroll_row_number;
int start_index = (int)start_pos;
float fraction = start_pos - start_index;
if (fraction < 0.0f)
{
start_index--;
fraction += 1.0f;
}
// 重置图标数据为无缩放状态
for (int i = 0; i < rows; i++)
{
init_row_data(items[i], i);
}
// 按行遍历数据,从start_index往后遍历5行
for (int i = 0; i < 5 + 1; i++)
{
int base_index = i + start_index;
// 行数边界判断
if (base_index >= 0 && base_index < rows)
{
transform_row(items[base_index], i + fraction);
}
}
}
/**
* 依据transforms设置的缩放等数据,对网格中的某一行进行变换(图标大小缩放、位置XY偏移)
*
* @param transform_factor 表示当前这一行Items,相对于相对于第一行的位置,往下方 移动/滑动/偏移 了多少行(小数)
*/
static void transform_row(grid_item_info *row, float transform_factor)
{
int transform_index = (int)transform_factor;
float fraction = transform_factor - transform_index;
for (int i = 0; i < 3; i++)
{
// 从transforms表中拿到当前整数行对应的缩放值、偏移值
float scale = transforms[transform_index][i].scale;
float offset_x = transforms[transform_index][i].offset_x;
float offset_y = transforms[transform_index][i].offset_y;
// 如果滚动的行数不是整数,还有部分小数,则使用线性插值的方式计算出合适的中间值
if (fraction != 0.0f)
{
float offset_x_outer = (i == 0) ? 0.08f : (i == 2 ? -0.08f : 0.0f);
// 当前行的“变换”数据
transform_info current = (transform_index < 5) ? transforms[transform_index][i] : (transform_info){0, offset_x_outer, -0.21f};
// 上一行的“变换”数据
transform_info previous = (transform_index > 0) ? transforms[transform_index - 1][i] : (transform_info){0, offset_x_outer, +0.21f};
// 线性插值
scale = previous.scale * fraction + current.scale * (1.0f - fraction);
offset_x = previous.offset_x * fraction + current.offset_x * (1.0f - fraction);
offset_y = previous.offset_y * fraction + current.offset_y * (1.0f - fraction);
}
// 保存原始数据 缩放、偏移后 的数据
row[i].size = row[i].size * (1.0f - (1.0f - scale) * global_transform_factor);
row[i].center_x = row[i].center_x + (offset_x * global_transform_factor) * screen_size;
row[i].center_y = row[i].center_y + (offset_y * global_transform_factor) * screen_size;
}
}
评论区