【LVGL-编程指南】

LVGL-(Light and Versatile Graphics Library)

■ LVGL-简介

LVGL采用的是面向对象的编程思想,以抽象的类来实例化不同的对象(部件)

■ LVGL-学习链接

官网编程函数查询
官网
github仓库
博客站点
sim 在线模拟器网站
嵌入式GUI框架对比
LVGL 爱洋葱
Get IoT !网站

■ LVGL-工具类

■ LVGL-codeblock

添加链接描述

■ LVGL-使用GUI Guider 拖拽式设计LVGL

LVGL-使用GUI Guider 拖拽式设计LVGL

■ LVGL-使用SquareLine Studio图形开发工具

添加链接描述

■ LVGL-PC环境搭建

LVGLPC环境搭建

选择不同版本就复制不用的实例来运行学习
在这里插入图片描述

■ LVGL-源码移植

LVGL源码移植

■ LVGL-工作机制

  1. 父子结构
    父对象可以作为其子对象的容器。每个对象只能一个父对象(屏幕除外),但是一个父对象可以有无限多个子对象。
    父对象的类型没有限制,但是有特殊的父对象(例如,按钮)和特殊的子对象(例如,标签)。
  2. 子对象仅在父对象的范围内可见
  3. 子对象的位置是相对于父对象的位置,父对象移动时,两者相对位置不变。

■ LVGL-系统层-顶层和活动屏幕

属性描述描述
系统层的指针lv_layer_sys()例如,它将鼠标光标放在那里以确保它始终可见。
返回指向顶层lv_layer_top()layer_top 来创建一些随处可见的内容。例如,菜单栏,弹出窗口等。
如果启用了 click 属性,那么 layer_top 将吸收所有用户单击并充当模态。lv_obj_set_click(lv_layer_top(), true);
获取活动屏幕lv_scr_act()
lv_obj_t * sys = lv_layer_sys();    系统层
lv_obj_t * top = lv_layer_top();    顶层
lv_obj_t * act = lv_scr_act();      默认屏幕

■ LVGL-基础对象(lv_obj_t)

LVGL-基础对象

■ LVGL-界面切换

添加链接描述

■ LVGL-Flex和Grid布局

LVGL-Flex和Grid布局

■ LVGL-lv_timer_t (定时器)

typedef struct _lv_timer_t {
    uint32_t period;             // 定时器运行的频率
    uint32_t last_run;           // 定时器上次运行的时间
    lv_timer_cb_t timer_cb;      // 定时器注册的回调函数
    void * user_data;            // 用户自定义数据
    int32_t repeat_count;        // 重复次数,-1为永久重复,0为关闭,大于0为重复次数。
    uint32_t paused : 1;         // 定时器运行状态
} lv_timer_t;

void my_timer_cb(lv_timer_t *tmr)
{
  LV_LOG_USER("my_timer_cb test ......");
}

lv_timer_t *my_time = NULL;
my_time = lv_timer_create(my_timer_cb, 1000, 0);   // 运行周期为lvgl的1000个滴答时钟
lv_timer_set_repeat_count(my_time, 5);             // 运行指定次数的定时器
lv_timer_reset(my_time);                           // 重新开始计时
lv_timer_pause(my_time);                           // 暂停
lv_timer_resume(my_time);                          // 暂停后恢复运行
lv_timer_ready(my_time);                           // 立马运行一次,下一次按周期运行
lv_timer_set_period(my_time, 5000);                // 5秒周期
lv_timer_del(my_time);                             // 删除定时器
lv_timer_set_cb(my_time, my_timer_cb);             // 设置回调函数 设置两个回调函数,最后一个有效。
lv_timer_t *my_time2 = lv_timer_get_next(my_time); // 遍历定时器


■ LVGL-lv_group_t

其他博主链接
自己的连接

■ LVGL-lv_theme_t

void ui_init(void)
{

    lv_disp_t * dispp = lv_disp_get_default();
    lv_theme_t * theme = lv_theme_default_init(dispp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED),
                                               true, LV_FONT_DEFAULT);
    lv_disp_set_theme(dispp, theme);
    ui_HomePage_screen_init();
    lv_disp_load_scr(ui_HomePage);
}

■ LVGL-颜色

lv_color_hex(0x808080)

■ LVGL-动画效果 lv_anim_t

  1. 使用动画在开始值和结束值之间自动更改变量的值。
/* 初始化动画 */
lv_anim_t a;
lv_anim_init(&a);

/* --- 必选设置 --- */

/* 设置“动画制作”功能 */
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t) lv_obj_set_x);

/* 设置“动画制作”功能 */
lv_anim_set_var(&a, obj);

/* 动画时长[ms] */
lv_anim_set_time(&a, duration);

/* 设置开始和结束值。例如。 0、150 */
lv_anim_set_values(&a, start, end);

/* --- 可选设置 --- */

/* 开始动画之前的等待时间[ms] */
lv_anim_set_delay(&a, delay);

/* 设置路径(曲线)。默认为线性 */
lv_anim_set_path(&a, &path);

/* 设置一个回调以在动画准备好时调用。 */
lv_anim_set_ready_cb(&a, ready_cb);

/* 设置在动画开始时(延迟后)调用的回调。 */
lv_anim_set_start_cb(&a, start_cb);

/* 在此持续时间内,也向后播放动画。默认值为0(禁用)[ms] */
lv_anim_set_playback_time(&a, wait_time);

/* 播放前延迟。默认值为0(禁用)[ms] */
lv_anim_set_playback_delay(&a, wait_time);

/* 重复次数。默认值为1。LV_ANIM_REPEAT_INFINIT用于无限重复 */
lv_anim_set_repeat_count(&a, wait_time);

/* 重复之前要延迟。默认值为0(禁用)[ms] */
lv_anim_set_repeat_delay(&a, wait_time);

/* true(默认):立即应用开始值,false:延迟设置动画后再应用开始值。真正开始。 */
lv_anim_set_early_apply(&a, true/false);

/* 应用动画效果 */
lv_anim_start(&a);                 

■ 路径设置

lv_anim_path_linear  		// 线性动画
lv_anim_path_step 	 		// 一步到位
lv_anim_path_ease_in 		// 渐进效果
lv_anim_path_ease_out 		// 渐退效果
lv_anim_path_ease_in_out 	// 渐进和渐退效果
lv_anim_path_overshoot 		// 超出最终值
lv_anim_path_bounce 		// 从最终值反弹一点(就像撞墙一样)

/* 初始化路径 */
lv_anim_path_t path;
lv_anim_path_init(&path);
lv_anim_path_set_cb(&path, lv_anim_path_<type>);
lv_anim_path_set_user_data(&path, &foo); /* 自定义数据(可选) */

/* 在动画中设置路径 */
lv_anim_set_path(&a, &path);

■ 速度设置

/* 将速度转换为时间再赋值 */
lv_anim_set_time(&a, lv_anim_speed_to_time(speed, start, end));

■ 删除动画

lv_anim_del(var,func)

■ LVGL-标签部件 lv_label_create

LVGL-标签部件

■ LVGL-按钮部件 lv_btn_create

■ LVGL-按钮矩阵部件 lv_btnmatrix_create

■ LVGL-图片按钮 lv_imgbtn_create

■ LVGL-复选框部件 lv_checkbox_create

添加链接描述
LVGL-按钮矩阵部件

■ LVGL-键盘部件 lv_keyboard_create

LVGL-键盘部件

■ LVGL-开关部件 lv_switch_create

LVGL-开关部件

■ LVGL-实体按键 lv_keyboard_create

LVGL-实体按键控制

■ LVGL-加载器部件 lv_spinner_create

在这里插入图片描述
在这里插入图片描述

== ■ 示例一:==

static void lv_example_spinner(void)
{
    spinner = lv_spinner_create(lv_scr_act(), 1000, 60);                            /* 创建加载器 */
    lv_obj_align(spinner, LV_ALIGN_CENTER, 0, -scr_act_height() / 15 );             /* 设置位置 */
    lv_obj_set_size(spinner, scr_act_height() / 5, scr_act_height() / 5);           /* 设置大小 */
    lv_obj_set_style_arc_width(spinner, scr_act_height() / 35, LV_PART_MAIN);       /* 设置主体圆弧宽度 */
    lv_obj_set_style_arc_width(spinner, scr_act_height() / 35, LV_PART_INDICATOR);  /* 设置指示器圆弧宽度 */
}

在这里插入图片描述

■ LVGL-下拉列表部件 lv_dropdown

LVGL-下拉列表部件

■ LVGL-内置图标字体

可以打开lv_symbol_def.h文件

#define LV_SYMBOL_AUDIO "\xef\x80\x81" /*61441, 0xF001*/
#define LV_SYMBOL_VIDEO "\xef\x80\x88" /*61448, 0xF008*/
#define LV_SYMBOL_LIST 	"\xef\x80\x8b" /*61451, 0xF00B*/
#define LV_SYMBOL_OK 	"\xef\x80\x8c" /*61452, 0xF00C*/

在这里插入图片描述

/* 直接调用 */
lv_label_set_text(my_label, LV_SYMBOL_OK);
/* 与字符一起用 */
lv_label_set_text(my_label, LV_SYMBOL_OK "Apply");
/* 多个符号一起用 */
lv_label_set_text(my_label, LV_SYMBOL_OK LV_SYMBOL_WIFI LV_SYMBOL_PLAY);

示例一:图标

void lv_mainstart(void)
{
	lv_obj_t *label = lv_label_create(lv_scr_act());
	lv_label_set_text(label, LV_SYMBOL_AUDIO"AUDIO");
}

在这里插入图片描述

■ LVGL-LED部件 lv_led_create

在这里插入图片描述
在这里插入图片描述
== 示例一:==

/**
 * @brief  LED事件回调
 * @param  *e :事件相关参数的集合,它包含了该事件的所有数据
 * @return 无
 */
static void led_event_cb(lv_event_t* e)
{
    lv_obj_t* led = lv_event_get_target(e);     /* 获取触发源 */
    lv_led_toggle(led);                         /* 翻转LED状态 */
}
/**
 * @brief  LED1
 * @param  无
 * @return 无
 */
static void lv_example_led_1(void)
{
    /* 根据活动屏幕宽度选择字体 */
    if (scr_act_width() <= 480)
    {
        font = &lv_font_montserrat_14;
    }
    else
    {
        font = &lv_font_montserrat_20;
    }

    /* 创建基础对象作为背景 */
    obj = lv_obj_create(lv_scr_act());
    lv_obj_set_size(obj, scr_act_width() * 5 /6 , scr_act_height() * 3 /5);
    lv_obj_align(obj, LV_ALIGN_CENTER, 0 , 0);
    lv_obj_set_style_bg_color(obj, lv_color_hex(0xefefef), LV_STATE_DEFAULT);

    lv_obj_t* led = lv_led_create(obj);                                                     /* 创建LED */
    lv_obj_set_size(led, scr_act_height() /5 , scr_act_height() /5);                        /* 设置LED大小 */
    lv_obj_align(led, LV_ALIGN_CENTER, -scr_act_width() * 4/ 15, -scr_act_height() /15);    /* 设置LED位置 */
    lv_led_off(led);                                                                        /* 关闭LED */
    lv_obj_add_event_cb(led, led_event_cb, LV_EVENT_CLICKED, NULL);                         /* 设置LED事件回调 */

    lv_obj_t *label = lv_label_create(lv_scr_act());                                        /* 创建LED功能标签 */
    lv_label_set_text(label, "ROOM 1");                                                     /* 设置文本 */
    lv_obj_set_style_text_font(label, font, LV_STATE_DEFAULT);                              /* 设置字体 */
    lv_obj_align_to(label, led, LV_ALIGN_OUT_BOTTOM_MID, 0, scr_act_height() /15 );         /* 设置位置 */
}

/**
 * @brief  LED2
 * @param  无
 * @return 无
 */
static void lv_example_led_2(void)
{
    lv_obj_t* led = lv_led_create(obj);                                                     /* 创建LED */
    lv_obj_set_size(led, scr_act_height() /5 , scr_act_height() /5);                        /* 设置LED大小 */
    lv_obj_align(led, LV_ALIGN_CENTER, 0, -scr_act_height() /15);                           /* 设置LED位置 */
    lv_led_set_color(led, lv_color_hex(0xff0000));                                          /* 设置LED颜色 */
    lv_led_on(led);                                                                         /* 打开LED */
    lv_obj_add_event_cb(led, led_event_cb, LV_EVENT_CLICKED, NULL);                         /* 设置LED事件回调 */

    lv_obj_t *label = lv_label_create(lv_scr_act());                                        /* 创建LED功能标签 */
    lv_label_set_text(label, "ROOM 2");                                                     /* 设置文本 */
    lv_obj_set_style_text_font(label, font, LV_STATE_DEFAULT);                              /* 设置字体 */
    lv_obj_align_to(label, led, LV_ALIGN_OUT_BOTTOM_MID, 0, scr_act_height() /15 );         /* 设置位置 */
}

/**
 * @brief  LED3
 * @param  无
 * @return 无
 */
static void lv_example_led_3(void)
{
    lv_obj_t* led = lv_led_create(obj);                                                     /* 创建LED */
    lv_obj_set_size(led, scr_act_height() /5 , scr_act_height() /5);                        /* 设置LED大小 */
    lv_obj_align(led, LV_ALIGN_CENTER, scr_act_width() * 4/ 15, -scr_act_height() /15);     /* 设置LED位置 */
    lv_led_set_color(led, lv_color_hex(0x2fc827));                                          /* 设置LED颜色 */
    lv_led_off(led);                                                                        /* 关闭LED */
    lv_obj_add_event_cb(led, led_event_cb, LV_EVENT_CLICKED, NULL);                         /* 设置LED事件回调 */

    lv_obj_t *label = lv_label_create(lv_scr_act());                                        /* 创建LED功能标签 */
    lv_label_set_text(label, "ROOM 3");                                                     /* 设置文本 */
    lv_obj_set_style_text_font(label, font, LV_STATE_DEFAULT);                              /* 设置字体 */
    lv_obj_align_to(label, led, LV_ALIGN_OUT_BOTTOM_MID, 0, scr_act_height() /15 );         /* 设置位置 */
}

在这里插入图片描述

■ LVGL-列表部件 lv_list_create

添加链接描述

■ LVGL-下拉列表选项 lv_dropdown_create

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

■ 示例一

/**
 * @brief  下拉列表事件回调
 * @param  无
 * @return 无
 */
static void dropdown_event_cb(lv_event_t* e)
{
    lv_event_code_t code = lv_event_get_code(e);                    /* 获取事件类型 */
    lv_obj_t *dropdown = lv_event_get_target(e);                    /* 获取触发源 */

    if (LV_EVENT_VALUE_CHANGED == code)                             /* 判断事件类型 */
    {
        char buf[10];
        lv_dropdown_get_selected_str(dropdown, buf, sizeof(buf));   /* 获取当前选项文本 */
        lv_label_set_text(label, buf);                              /* 显示当前选项文本 */
    }
}
/**
 * @brief  例1
 * @param  无
 * @return 无
 */
static void lv_example_dropdown_1(void)
{
    /* 根据屏幕宽度选择字体和列表宽度 */
    if (scr_act_width() <= 320) 
    {
        dropdown_font = &lv_font_montserrat_14;
        dropdown_width = 90;
    }
    else if (scr_act_width() <= 480) 
    {
        dropdown_font = &lv_font_montserrat_18;
        dropdown_width = 120;
    }
    else 
    {
        dropdown_font = &lv_font_montserrat_22;
        dropdown_width = 150;
    }

    lv_obj_t* dropdown = lv_dropdown_create(lv_scr_act());                                      /* 定义并创建下拉列表 */
    lv_dropdown_set_options_static(dropdown, options);                                          /* 添加下拉列表选项 */
    lv_obj_set_style_text_font(dropdown, dropdown_font, LV_PART_MAIN);                          /* 设置下拉列表字体 */
    lv_obj_set_width(dropdown, dropdown_width);                                                 /* 设置下拉列表宽度 */
    lv_obj_align(dropdown, LV_ALIGN_CENTER, -scr_act_width() / 3, 0);                           /* 设置下拉列表位置 */

    label = lv_label_create(lv_scr_act());                                                      /* 定义并创建标签 */
    lv_obj_set_style_text_font(label, dropdown_font, LV_PART_MAIN);                             /* 设置标签表字体 */
    lv_obj_set_width(label, dropdown_width);                                                    /* 设置标签宽度 */
    lv_obj_align_to(label, dropdown, LV_ALIGN_OUT_TOP_MID, 15, -scr_act_height() / 8);          /* 设置标签位置 */
    lv_label_set_text(label, "option 1");                                                       /* 设置标签文本 */

    lv_obj_add_event_cb(dropdown, dropdown_event_cb, LV_EVENT_VALUE_CHANGED, NULL);             /* 添加下拉列表回调 */
}

/**
 * @brief  例2
 * @param  无
 * @return 无
 */
static void lv_example_dropdown_2(void)
{
    lv_obj_t* dropdown;                                                                         /* 定义下拉列表 */
        
    dropdown = lv_dropdown_create(lv_scr_act());                                                /* 创建下拉列表 */
    lv_dropdown_set_options_static(dropdown, options);                                          /* 添加下拉列表选项 */
    lv_obj_set_style_text_font(dropdown, dropdown_font, LV_PART_MAIN);                          /* 设置下拉列表字体 */
    lv_obj_set_width(dropdown, dropdown_width);                                                 /* 设置下拉列表宽度 */
    lv_dropdown_set_dir(dropdown, LV_DIR_BOTTOM);                                               /* 设置下拉列表方向 */
    lv_dropdown_set_symbol(dropdown, LV_SYMBOL_DOWN);                                           /* 设置下拉列表符号 */
    lv_obj_align(dropdown, LV_ALIGN_CENTER, scr_act_width() / 8, -3 * scr_act_height() / 8);    /* 设置下拉列表位置 */

    dropdown = lv_dropdown_create(lv_scr_act());                                                /* 创建下拉列表 */
    lv_dropdown_set_options_static(dropdown, options);                                          /* 添加下拉列表选项 */
    lv_obj_set_style_text_font(dropdown, dropdown_font, LV_PART_MAIN);                          /* 设置下拉列表字体 */
    lv_obj_set_width(dropdown, dropdown_width);                                                 /* 设置下拉列表宽度 */
    lv_dropdown_set_dir(dropdown, LV_DIR_LEFT);                                                 /* 设置下拉列表方向 */
    lv_dropdown_set_symbol(dropdown, LV_SYMBOL_LEFT);                                           /* 设置下拉列表符号 */
    lv_obj_align(dropdown, LV_ALIGN_CENTER, scr_act_width() / 8, -1 * scr_act_height() / 8);    /* 设置下拉列表位置 */

    dropdown = lv_dropdown_create(lv_scr_act());                                                /* 创建下拉列表 */
    lv_dropdown_set_options_static(dropdown, options);                                          /* 添加下拉列表选项 */
    lv_obj_set_style_text_font(dropdown, dropdown_font, LV_PART_MAIN);                          /* 设置下拉列表字体 */
    lv_obj_set_width(dropdown, dropdown_width);                                                 /* 设置下拉列表宽度 */
    lv_dropdown_set_dir(dropdown, LV_DIR_RIGHT);                                                /* 设置下拉列表方向 */
    lv_dropdown_set_symbol(dropdown, LV_SYMBOL_RIGHT);                                          /* 设置下拉列表符号 */
    lv_obj_align(dropdown, LV_ALIGN_CENTER, scr_act_width() / 8, 1 * scr_act_height() / 8);     /* 设置下拉列表位置 */

    dropdown = lv_dropdown_create(lv_scr_act());                                                /* 创建下拉列表 */
    lv_dropdown_set_options_static(dropdown, options);                                          /* 添加下拉列表选项 */
    lv_obj_set_style_text_font(dropdown, dropdown_font, LV_PART_MAIN);                          /* 设置下拉列表字体 */
    lv_obj_set_width(dropdown, dropdown_width);                                                 /* 设置下拉列表宽度 */
    lv_dropdown_set_dir(dropdown, LV_DIR_TOP);                                                  /* 设置下拉列表方向 */
    lv_dropdown_set_symbol(dropdown, LV_SYMBOL_UP);                                             /* 设置下拉列表符号 */
    lv_obj_align(dropdown, LV_ALIGN_CENTER, scr_act_width() / 8, 3 * scr_act_height() / 8);     /* 设置下拉列表位置 */
}

在这里插入图片描述

■ 使用 SCROLL控件实现滚动功能

在这里插入图片描述

#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
 
#include "esp_log.h"
#if LV_BUILD_EXAMPLES && LV_USE_FLEX
 
/**
 * @brief 滚动结束后的事件
 * @param event 
*/
static void scroll_end_event(lv_event_t * e)
{
    lv_obj_t * cont = lv_event_get_target(e);                                               // 获取事件的初始对象
 
    /* 获取事件的事件代码 */
    if(lv_event_get_code(e) == LV_EVENT_SCROLL_END) 
    {  
        /* 判断是否在滚动中 */
        if (lv_obj_is_scrolling(cont))
        {
            return;
        }
 
        lv_coord_t child_cnt = lv_obj_get_child_cnt(cont);                                  // 获取子界面的数量
        lv_coord_t mid_btn_index = (child_cnt - 1) / 2;                                     // 中间界面的位置
 
        /* 获取父对象y轴的中心坐标值 */
        lv_area_t cont_a;
        lv_obj_get_coords(cont, &cont_a);                                                   // 将cont对象的坐标复制到cont_a
        lv_coord_t cont_y_center = cont_a.y1 + lv_area_get_width(&cont_a) / 2;              // 获取界面的宽像素大小/2 
 
        /* 注意,这里的中心显示界面的坐标不在正中心,所以这里加上了差值 */
        cont_y_center += 69;
 
        /* 遍历子界面 */
        for (lv_coord_t i = 0; i < child_cnt; i++) {
            lv_obj_t* child = lv_obj_get_child(cont, i);                                    // 通过索引获取子对象
 
            /* 获取子对象y轴的中心坐标值 */
            lv_area_t child_a;
            lv_obj_get_coords(child, &child_a);
            lv_coord_t child_y_center = child_a.y1 + lv_area_get_width(&child_a) / 2;       // 获取界面中按钮宽像素值的大小/2
            /* 子界面的坐标与父界面的坐标相等时,说明当前界面在父界面中显示 */
            if (child_y_center == cont_y_center)
            {
                /* 当前显示界面的索引 */
                lv_coord_t current_btn_index = lv_obj_get_index(child);
 
                /* 判断界面移动的数数据,并将当前界面的索引改为中间位置 */
                /* 因为是在滑动结束后实现的,建议界面较多的情况下使用此方式,当界面较少,一次滑动太多界面时,容易滑倒边界出现卡顿现象 */
                lv_coord_t move_btn_quantity = LV_ABS(current_btn_index - mid_btn_index);
                for (lv_coord_t j = 0; j < move_btn_quantity; j++)
                {
                    /* 向右滑动 */
                    if (current_btn_index < mid_btn_index)
                    {
                        lv_obj_move_to_index(lv_obj_get_child(cont, child_cnt - 1), 0);                     // 将最后一个界面索引改为第一个界面
                        lv_obj_scroll_to_view(lv_obj_get_child(cont, mid_btn_index), LV_ANIM_OFF);          // lv_obj_get_child 通过子索引获取对象的子对象
                    }
                    /* 向左滑动 */
                    if (current_btn_index > mid_btn_index)
                    {
                        lv_obj_move_to_index(lv_obj_get_child(cont, 0), child_cnt - 1);                     // 将第一个界面的索引值改为最后一个界面
                        lv_obj_scroll_to_view(lv_obj_get_child(cont, mid_btn_index), LV_ANIM_OFF);          // lv_obj_get_child 通过子索引获取对象的子对象
                    }
                }
                /* 保证界面居中显示 */
                lv_obj_set_style_translate_y(lv_obj_get_child(cont, mid_btn_index), 0, 0);
                break;
            }
        }            
    }
}
 
static void scroll_event_cb(lv_event_t * e)
{
    lv_obj_t * cont = lv_event_get_target(e);
 
    lv_area_t cont_a;
    lv_obj_get_coords(cont, &cont_a);
    lv_coord_t cont_y_center = cont_a.y1 + lv_area_get_height(&cont_a) / 2;
 
    lv_coord_t r = lv_obj_get_height(cont) * 7 / 10;
    uint32_t i;
    uint32_t child_cnt = lv_obj_get_child_cnt(cont);
    for(i = 0; i < child_cnt; i++) {
        lv_obj_t * child = lv_obj_get_child(cont, i);
        lv_area_t child_a;
        lv_obj_get_coords(child, &child_a);
 
        lv_coord_t child_y_center = child_a.y1 + lv_area_get_height(&child_a) / 2;
 
        lv_coord_t diff_y = child_y_center - cont_y_center;
        diff_y = LV_ABS(diff_y);
 
        /*Get the x of diff_y on a circle.*/
        lv_coord_t x;
        /*If diff_y is out of the circle use the last point of the circle (the radius)*/
        if(diff_y >= r) {
            x = r;
        }
        else {
            /*Use Pythagoras theorem to get x from radius and y*/
            uint32_t x_sqr = r * r - diff_y * diff_y;
            lv_sqrt_res_t res;
            lv_sqrt(x_sqr, &res, 0x8000);   /*Use lvgl's built in sqrt root function*/
            x = r - res.i;
        }
 
        /*Translate the item by the calculated X coordinate*/
        lv_obj_set_style_translate_x(child, x, 0);
 
        /*Use some opacity with larger translations*/
        lv_opa_t opa = lv_map(x, 0, r, LV_OPA_TRANSP, LV_OPA_COVER);
        lv_obj_set_style_opa(child, LV_OPA_COVER - opa, 0);
    }
}
 
/**
 * Translate the object as they scroll
 */
void lvgl_scroll_test(void)
{
    lv_obj_t * cont = lv_obj_create(lv_scr_act());
    lv_obj_set_size(cont, 200, 200);
    lv_obj_center(cont);
    lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);
 
    // lv_obj_add_event_cb(cont, scroll_event_cb, LV_EVENT_SCROLL, NULL);       // 为了方便实现,先把滚动的动画屏蔽
    lv_obj_add_event_cb(cont, scroll_end_event, LV_EVENT_SCROLL_END, NULL);
 
    lv_obj_set_style_radius(cont, LV_RADIUS_CIRCLE, 0);
    lv_obj_set_style_clip_corner(cont, true, 0);
    lv_obj_set_scroll_dir(cont, LV_DIR_VER);
    lv_obj_set_scroll_snap_y(cont, LV_SCROLL_SNAP_CENTER);
    lv_obj_set_scrollbar_mode(cont, LV_SCROLLBAR_MODE_OFF);
 
    uint32_t i;
    for(i = 0; i < 20; i++) {
        lv_obj_t * btn = lv_btn_create(cont);
        lv_obj_set_width(btn, lv_pct(100));
 
        lv_obj_t * label = lv_label_create(btn);
        lv_label_set_text_fmt(label, "Button %"LV_PRIu32, i);
    }
 
    /*---------------------------------------- 指定中心显示界面 ----------------------------------------*/
    lv_coord_t mid_btn_index = (lv_obj_get_child_cnt(cont) - 1) / 2;                   // 如果界面为偶数,将中间数向下取整的界面设置为中间界面
    lv_coord_t child_cnt = lv_obj_get_child_cnt(cont);                                 // 获取子界面的数量
 
    int roll_direction = mid_btn_index - mid_btn_index;                                      // 确定滚动方向
 
    /* 通过循环将指定界面移到中心位置 */
    for (lv_coord_t i = 0; i < LV_ABS(roll_direction); i++)
    {
        if (roll_direction > 0)
        {
            lv_obj_move_to_index(lv_obj_get_child(cont, child_cnt - 1), 0);            // 将最后一个界面的索引更改为 0 (移至第一个界面)
        }
        else
        {
            lv_obj_move_to_index(lv_obj_get_child(cont, 0), child_cnt - 1);            // 将第一个界面的索引值改为最大值(移至最后一个界面)
        }    
    }
 
    /*当按钮数为偶数时,确保按钮居中*/
    lv_obj_scroll_to_view(lv_obj_get_child(cont, mid_btn_index), LV_ANIM_OFF);         // 滚动到一个对象,直到它在其父对象上可见
}
 
#endif
 

list 链接

■ LVGL-滑动部件 lv_slider_create

添加链接描述

■ LVGL-滚动部件 lv_roller_create

添加链接描述

■ LVGL-进度条部件 lv_bar_create

添加链接描述

■ LVGL-圆弧部件 lv_arc_create

添加链接描述

■ LVGL-线条部件 lv_line_create

添加链接描述

■ LVGL-图片部件 lv_img_create

LVGL-图片部件

■ LVGL-LVGL-BMP,PNG,JPEG,GIF

LVGL-BMP,PNG,JPEG,GIF

■ LVGL-色环部件 lv_colorwheel_create

添加链接描述

■ LVGL-文本区域 lv_textarea_create

添加链接描述

■ LVGL-选项卡 lv_tabview_create

添加链接描述

■ LVGL-平铺视图 lv_tileview_create

添加链接描述

■ LVGL-窗口部件 lv_win_create

添加链接描述

■ LVGL-消息框 lv_msgbox

添加链接描述

■ LVGL-微调 lv_spinbox_create

添加链接描述

■ LVGL-表格 lv_table_create

添加链接描述

■ LVGL- ASCII ,UTF-8 编码

用户需要在 LVGL 工程中启用UTF-8 编码, 可以打开 lv_conf.h 文件, 修改 LV_TXT_ENC 配置。

/* 为字符串选择字符编码.
* IDE 或编辑器应该具有相同的字符编码
* 1. - LV_TXT_ENC_UTF8
* 2. - LV_TXT_ENC_ASCII
* */
#define LV_TXT_ENC LV_TXT_ENC_UTF8
建议大家将 MDK 软件设置为 Chinese GB2312 编码, 以更好地兼容中文。

■ LVGL-文件系统移植

添加链接描述

■ LVGL-字库应用

添加链接描述

■ LVGL-二维码库

添加链接描述

■ LVGL-重点

■ 手势事件

void ui_event_HomePage(lv_event_t * e)
{
    lv_event_code_t event_code = lv_event_get_code(e);
    lv_obj_t * target = lv_event_get_target(e);

    if(event_code == LV_EVENT_GESTURE)  //手势事件
    {
		if(lv_indev_get_gesture_dir(lv_indev_get_act()) == LV_DIR_RIGHT) //获取一个手势
		{
			user_Stack_Pop(&ScrRenewStack);
			ui_MenuPage_screen_init();
			lv_scr_load_anim(ui_MenuPage,LV_SCR_LOAD_ANIM_MOVE_RIGHT,100,0,true);
			user_Stack_Push(&ScrRenewStack,(long long int)&ui_HomePage);
			user_Stack_Push(&ScrRenewStack,(long long int)&ui_MenuPage);
		}
	}
}

■ 电子书项目 页面翻页 -> 手势事件

== ui_ebook_txt 控件才有手势事件 ,子控件验证没有手势事件==

lv_obj_t * ui_ebook_txt = NULL;   //电子书root

void ui_ebook_screen_init(void)
{   
	ui_ebook_txt = lv_obj_create(NULL);     、、
	lv_obj_clear_flag(ui_ebook_txt, LV_OBJ_FLAG_SCROLLABLE);
	lv_obj_set_width(ui_ebook_txt, LV_PCT(100));    
	lv_obj_set_height(ui_ebook_txt, LV_PCT(100));
	lv_obj_add_event_cb(ui_ebook_txt, ui_event_ebook, LV_EVENT_ALL, NULL);
	lv_obj_add_event_cb(ui_ebook_txt, ebook_keyinput_event_cb, LV_EVENT_GESTURE, NULL);
	lv_obj_set_style_bg_color(ui_ebook_txt, lv_color_hex(0xfcfcfc), LV_PART_MAIN | LV_STATE_DEFAULT);
	lv_obj_set_style_bg_opa(ui_ebook_txt, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
}

void ebook_keyinput_event_cb(lv_event_t *event)
{
	lv_event_code_t code = lv_event_get_code(event);
	lv_obj_t *target=lv_event_get_target(event);
	
    if(code == LV_EVENT_GESTURE)
	{
		if(target==ui_ebook_txt)
		{
			lv_dir_t dir =lv_indev_get_gesture_dir(lv_indev_get_act());
			switch (dir){
			case LV_DIR_LEFT:   
			case LV_DIR_TOP:
				// printf("shine+ %s,%d,dir=%d\n",__func__,__LINE__,dir);
				change_ebook_txt_info(LV_KEY_DOWN,0);
				break;
			case LV_DIR_RIGHT:
			case LV_DIR_BOTTOM:
				// printf("shine+ %s,%d,dir=%d\n",__func__,__LINE__,dir);
				change_ebook_txt_info(LV_KEY_UP,0);
				break;
			default :
				break;
			}
		}
	}
}

■ 按键处理

■ 按键驱动

//初始化按键线程
void api_key_get_init()
{
	pthread_t thread_id = 0;
    pthread_attr_t attr;    

    key_queue_init(&m_key_queue);
    pthread_attr_init(&attr);
    pthread_attr_setstacksize(&attr, 0x2000);
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //release task resource itself
    if(pthread_create(&thread_id, &attr, _key_task, NULL)) {
        return;
    }
    pthread_attr_destroy(&attr);

	key_simu_start();
}

//进程中读取按键消息
static void *_key_task(void *arg)
{
	struct pollfd *pfd;
	int cnt;
	KEY_MSG_s key_msg;
	struct input_event *t;
	int input_cnt = 0;
	uint8_t input_name[32];
	int key_fds[INPUT_DEV_MAX] = {0,};
    int i;

	for (i = 0; i < INPUT_DEV_MAX; i++)
	{
#if (SOWYE_PLAYER_APP == 1)
		if(i==1)    // 触摸屏  event1
		{
			continue;
		}
#endif
		sprintf(input_name, "/dev/input/event%d", i);
		key_fds[input_cnt] = open(input_name, O_RDONLY);
		if (key_fds[input_cnt] < 0){
			break;
		}
		printf("\nopen  %s, line:%d. i=%d, %s\r\n",__func__,i,__LINE__,input_name);
		input_cnt++;
	}

	if (0 == input_cnt){
		printf("\n%s(), line:%d. No Input device!!!\r\n", __func__, __LINE__);
		return NULL;
	}

	printf("%s(), input device num: %d.\r\n", __func__, input_cnt);
	pfd = (struct pollfd*)malloc(sizeof(struct pollfd)*input_cnt);
	memset(pfd, 0, sizeof(struct pollfd)*input_cnt);

	for (i = 0; i < input_cnt; i ++){
		pfd[i].fd = key_fds[i];
		pfd[i].events = POLLIN | POLLRDNORM;
	}

	t = &key_msg.key_event;
	#ifdef BLUETOOTH_SUPPORT
	bluetooth_ir_key_init(bt_key_queue_write_ctrl);
	#endif

	// update_tp = POINT_CLEAR_STATE;
	while (1){
		if (poll(pfd, input_cnt, -1) <= 0){
			continue;
		}

		for (i = 0; i < input_cnt; i ++)
		{
			if (pfd[i].revents != 0){
				bzero(t, sizeof(struct input_event));
				cnt = read(key_fds[i], t, sizeof(struct input_event));

				if (m_key_off){
					api_sleep_ms(10);
					continue;
				}

				if (cnt != sizeof(struct input_event)){
					printf("cnt = %d, err(%d):%s\r\n", cnt, errno, strerror(errno));
					api_sleep_ms(5);
				}
#if 0
				// 遥控器
				// type1 : 0x0001, code : 0x0200, value : 0x00000000 ,event 0, keytype=-1515870811
				// type1 : 0x0000, code : 0x0000, value : 0x00000000 ,event 0, keytype=-1515870811

				// 触摸屏
				// type1 : 0x0003, code : 0x0035, value : 0x00000565 ,event 1, keytype=-1515870811
				// type1 : 0x0003, code : 0x0036, value : 0x00000347 ,event 1, keytype=-1515870811

				// key
				// type1 : 0x0004, code : 0x0004, value : 0x00000105 ,event 2, keytype=-1515870811
				// type1 : 0x0000, code : 0x0000, value : 0x00000000 ,event 2, keytype=-1515870811

				printf("type1 : 0x%04X, code : 0x%04X, value : 0x%08d ,event%2d, keytype=%d\r\n",t->type, t->code, t->value ,i ,(int)key_msg.key_type);

#endif
				if (_key_event_is_valid(t))
				{
					//here may change the key type, for sometims
					//event0 may be IR, may be GPIO or ADC key.
					if (0 == i)
						key_msg.key_type = KEY_TYPE_IR;
					else if (1 == i)
						key_msg.key_type = KEY_TYPE_ADC;
					else if (2 == i)
						key_msg.key_type = KEY_TYPE_GPIO;

                   // printf("shine555 func=%s,line=%d,key_msg.key_type=%d,t->type=%d, t->code=%d, t->value=%ld \n", __func__,__LINE__,
                   //         		key_msg.key_type, t->type, t->code, t->value);
					key_queue_write(&m_key_queue, &key_msg);

					// 长按 遥控器
					// shine555 func=_key_task,line=560,key_msg.key_type=0,t->type=1, t->code=106, t->value=1;
					// shine555 func=_key_task,line=560,key_msg.key_type=0,t->type=4, t->code=106, t->value=1;       //4
					// shine555 func=_key_task,line=560,key_msg.key_type=0,t->type=4, t->code=106, t->value=1;       //4
					// shine555 func=_key_task,line=560,key_msg.key_type=0,t->type=4, t->code=106, t->value=1;       //4
					// shine555 func=_key_task,line=560,key_msg.key_type=0,t->type=1, t->code=106, t->value=0;       //     0

					//短按 遥控器
					// shine555 func=_key_task,line=560,key_msg.key_type=0,t->type=1, t->code=28, t->value=1;
					// shine555 func=_key_task,line=560,key_msg.key_type=0,t->type=1, t->code=28, t->value=0;


					//+按键-短按
					// shine555 func=_key_task,line=560,key_msg.key_type=1,t->type=1, t->code=352, t->value=1
					// shine555 func=_key_task,line=560,key_msg.key_type=1,t->type=1, t->code=352, t->value=0

					//+按键-长按
					// shine555 func=_key_task,line=560,key_msg.key_type=1,t->type=1, t->code=352, t->value=1
					// shine555 func=_key_task,line=560,key_msg.key_type=1,t->type=4, t->code=352, t->value=1
					// shine555 func=_key_task,line=560,key_msg.key_type=1,t->type=4, t->code=352, t->value=1
					// shine555 func=_key_task,line=560,key_msg.key_type=1,t->type=4, t->code=352, t->value=1
					// shine555 func=_key_task,line=560,key_msg.key_type=1,t->type=1, t->code=352, t->value=0

					//-按键-短按
					// shine555 func=_key_task,line=560,key_msg.key_type=1,t->type=1, t->code=105, t->value=1
					// shine555 func=_key_task,line=560,key_msg.key_type=1,t->type=1, t->code=105, t->value=0

					//-按键-长按
					// shine555 func=_key_task,line=560,key_msg.key_type=1,t->type=1, t->code=105, t->value=1
					// shine555 func=_key_task,line=560,key_msg.key_type=1,t->type=4, t->code=105, t->value=1
					// shine555 func=_key_task,line=560,key_msg.key_type=1,t->type=1, t->code=105, t->value=0
				}
			}
		}
		api_sleep_ms(5);
	}
	for (i = 0; i < input_cnt; i ++){
		if (key_fds[i] > 0)
		close(key_fds[i]);
	}
	free(pfd);

	return 0;	
}

//读取消息队列中的按键
KEY_MSG_s *api_key_msg_get(void)
{
	KEY_MSG_s *key_msg = NULL;

	if (key_queue_read(&m_key_queue, &key_msg))
		return key_msg;
	else
		return NULL;
}

/*Get the currently being pressed key.  0 if no key is pressed*/
static uint32_t keypad_read_key(lv_indev_data_t  *lv_key)
{
    /*Your code comes here*/
    struct input_event *t;
    uint32_t ret = 0;
    KEY_MSG_s *key_msg = NULL;

    key_msg = api_key_msg_get();
    if (!key_msg){
        return 0;
    }
    t = &key_msg->key_event;
    // printf("shine + %s,%d,key_msg->key_type=%d,t->type=%d, t->code=%d, t->value=%ld \n",
    //         __func__,__LINE__,
    //         key_msg->key_type, t->type, t->code, t->value);  //t->type = EV_KEY ==0x01

    if(t->value == 1) //pressed
    {
        ret = key_preproc(t->code);
        lv_key->state = (ret==0)?LV_INDEV_STATE_REL:LV_INDEV_STATE_PR;
    }
    else if(t->value == 0) //released
    {
        ret = t->code;
        lv_key->state = LV_INDEV_STATE_REL;
    }

    //power key is valid while key release.    
    if(t->code == KEY_POWER && t->value == 0){
    #ifdef LVGL_MBOX_STANDBY_SUPPORT
        win_open_lvmbox_standby();
        lv_key->state = LV_INDEV_STATE_PR;
        ret = V_KEY_POWER;
    #else
        enter_standby();
    #endif
    }

    lv_key->key = keypad_key_map2_lvkey(ret);
    // printf("shine = %s,%d,key_msg->key_type=%d,t->type=%d, t->code=%d, t->value=%ld,lv_key->key=%d \n",
    //         __func__,__LINE__,
    //         key_msg->key_type, t->type, t->code, t->value, lv_key->key);
    return ret;
}

/*Will be called by the library to read the mouse*/
void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    /*Get whether the a key is pressed and save the pressed key*/
    keypad_read_key(data);
}

//lvgl 按键初始化
int key_init(void)
{
    static lv_indev_drv_t keypad_driver;
    // keypad_init();  //_key_task 线程中有打开。

    lv_indev_drv_init(&keypad_driver);
    keypad_driver.type = LV_INDEV_TYPE_KEYPAD;
    keypad_driver.read_cb = keypad_read;
    indev_keypad = lv_indev_drv_register(&keypad_driver);

    g = lv_group_create();  //注释也正常运行
    lv_group_set_default(g);
    lv_indev_set_group(indev_keypad, g);

    return 0;
}

■ 按键上层处理

按键 事件 显示页面一定要有btn 才能有这个事件触发。

static void sowye_test_event_handle(lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);

    if(code == LV_EVENT_SCREEN_LOAD_START) {
		// key_set_group(sowye_test_page_g);
    }else if(code == LV_EVENT_GESTURE)
    {
        if(++index1 >= COLORSIZE) { index1=0;}
        lv_obj_set_style_bg_color(obj, color1[index1], LV_PART_MAIN | LV_STATE_DEFAULT);
    }
}

static void sowye_obj_event_handle(lv_event_t *e)
{
	lv_event_code_t code = lv_event_get_code(e);
	lv_obj_t *target = lv_event_get_target(e);
    if (code == LV_EVENT_KEY)
	{
        printf("%s,%d\n",__func__,__LINE__);
        uint8_t key = lv_indev_get_key(lv_indev_get_act());
        if (key == LV_KEY_ENTER)
		{
            if(++index1 >= COLORSIZE)
            {
                index1=0;
            }
            lv_obj_set_style_bg_color(obj, color1[index1], LV_PART_MAIN | LV_STATE_DEFAULT);
        }
    }
}

lv_obj_t* sowye_ui_test_init()
{
    sowye_ui_test_page = lv_obj_create(NULL);
    lv_obj_remove_style_all(sowye_ui_test_page);
    lv_obj_set_size(sowye_ui_test_page,LV_PCT(100),LV_PCT(100));
    lv_obj_add_event_cb(sowye_ui_test_page, sowye_test_event_handle, LV_EVENT_ALL, 0);

    // 创建电池外壳
    obj = lv_obj_create(sowye_ui_test_page);
    lv_obj_remove_style_all(obj);
    lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100));
    lv_obj_align(obj, LV_ALIGN_CENTER, 0, 0);
    lv_obj_add_event_cb(obj, sowye_obj_event_handle, LV_EVENT_ALL, 0);

    lv_obj_set_style_bg_color(obj, color1[index1], LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(obj, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_scrollbar_mode(obj, LV_SCROLLBAR_MODE_OFF);                // 清除垂直和水平滚动条

	//如果页面没有按钮,就不会有key事件。
	btn1 = lv_btn_create(obj);
    lv_obj_set_size(btn1, LV_PCT(100), LV_PCT(10));
    lv_obj_align(btn1, LV_ALIGN_BOTTOM_MID, 0, 0);
    lv_obj_add_event_cb(btn1, sowye_btn1_event_handle, LV_EVENT_ALL, 0);

    return sowye_ui_test_page;
}

■ 触摸处理

void lv_port_indev_init(void)
{
    /**
     * Here you will find example implementation of input devices supported by LittelvGL:
     *  - Touchpad
     *  - Mouse (with cursor support)
     *  - Keypad (supports GUI usage only with key)
     *  - Encoder (supports GUI usage only with: left, right, push)
     *  - Button (external buttons to press points on the screen)
     *
     *  The `..._read()` function are only examples.
     *  You should shape them according to your hardware
     */
	 
    static lv_indev_drv_t indev_drv;

    /*------------------
     * Touchpad
     * -----------------*/

    /*Initialize your touchpad if you have*/
    touchpad_init();

    /*Register a touchpad input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touchpad_read;
    indev_touchpad = lv_indev_drv_register(&indev_drv);
}

static void touchpad_init(void)
{
    /*Your code comes here*/
}

static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    static lv_coord_t last_x = 0;
    static lv_coord_t last_y = 0;

    /*Save the pressed coordinates and the state*/
    if(touchpad_is_pressed()) {
        touchpad_get_xy(&last_x, &last_y);
        data->state = LV_INDEV_STATE_PR;
    }else{
        data->state = LV_INDEV_STATE_REL;
    }

    /*Set the last pressed coordinates*/
    data->point.x = last_x;
    data->point.y = last_y;
}

/*Get the x and y coordinates if the touchpad is pressed*/
static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
{
    /*Your code comes here*/
		CST816_Get_XY_AXIS();
    (*x) = CST816_Instance.X_Pos;
    (*y) = CST816_Instance.Y_Pos;
}

/*Return true is the touchpad is pressed*/
static bool touchpad_is_pressed(void)
{
    /*Your code comes here*/
	if(CST816_Get_FingerNum()!=0x00 && CST816_Get_FingerNum()!=0xFF)
	{return true;}
	else
	{return false;}
}

/*
*********************************************************************************************************
*	函 数 名: CST816_Get_FingerNum
*	功能说明: 读取触摸屏的手指触摸个数,0xFF为睡眠
*	形    参:无
*	返 回 值: 返回芯片ID
*********************************************************************************************************
*/
uint8_t CST816_Get_FingerNum(void)
{
	return CST816_IIC_ReadREG(FingerNum);
}

/*
*********************************************************************************************************
*	函 数 名: CST816_IIC_ReadREG
*	功能说明: 读取触摸屏单个寄存器的数据
*	形    参:reg:寄存器地址
*	返 回 值: 返回寄存器存储的数据
*********************************************************************************************************
*/
uint8_t CST816_IIC_ReadREG(uint8_t addr)
{
	return IIC_Read_One_Byte(&CST816_dev,Device_Addr,addr);
}

unsigned char IIC_Read_One_Byte(iic_bus_t *bus, uint8_t daddr,uint8_t reg)
{
	unsigned char dat;
	IICStart(bus);
	IICSendByte(bus,daddr<<1);
	IICWaitAck(bus);
	IICSendByte(bus,reg);
	IICWaitAck(bus);
	
	IICStart(bus);
	IICSendByte(bus,(daddr<<1)+1);
	IICWaitAck(bus);
	dat = IICReceiveByte(bus);
	IICSendNotAck(bus);
	IICStop(bus);
	return dat;
}


■ LVGL bug

添加链接描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光芒Shine

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值