LVGL源码(1):LVGL Keypad键值介绍以及禁止长按连续触发方法

    最近在使用LVGL的Keypad输入设备进行UI界面的编写,不过发现网上对这方面介绍比较少,于是打算在此记录一下自己对Keypad的一些看法:

LVGL版本:8.3

首先我们来到有关Keypad键值定义的lv_group.h文件,其中关于键值定义的代码为:

lv_group.h:

/*********************
 *      DEFINES
 *********************/
/*Predefined keys to control the focused object via lv_group_send(group, c)*/

enum {
    LV_KEY_UP        = 17,  /*0x11*/
    LV_KEY_DOWN      = 18,  /*0x12*/
    LV_KEY_RIGHT     = 19,  /*0x13*/
    LV_KEY_LEFT      = 20,  /*0x14*/
    LV_KEY_ESC       = 27,  /*0x1B*/
    LV_KEY_DEL       = 127, /*0x7F*/
    LV_KEY_BACKSPACE = 8,   /*0x08*/
    LV_KEY_ENTER     = 10,  /*0x0A, '\n'*/
    LV_KEY_NEXT      = 9,   /*0x09, '\t'*/
    LV_KEY_PREV      = 11,  /*0x0B, '*/
    LV_KEY_HOME      = 2,   /*0x02, STX*/
    LV_KEY_END       = 3,   /*0x03, ETX*/
};
typedef uint8_t lv_key_t;

上面有段注释:

/*Predefined keys to control the focused object via lv_group_send(group, c)*/

      翻译过来大意为通过 lv_group_send(group,c)函数传递预定义键键值来控制当前聚焦对象,我搜索了一下lv_group_send(group,c)函数,没有找到,只有lv_group_send_data(lv_group_t * group, uint32_t c)函数,功能是一致的不过名称不一样罢了;lv_group_send_data函数在LVGL源码的lv_group.c文件中定义,在indev.c文件中被使用,我们先来讲一下lv_group_send_data函数,即Keypad的键值是如何被传递的。

      首先给一个事件触发回调函数的案例:

//事件触发回调函数案例
static void Screen_Main_event_handler (lv_event_t *e)
{
    lv_event_code_t code = lv_event_get_code(e);      //获取当前事件触发的触发类型
    lv_obj_t *target = lv_event_get_target(e);        //获取触发该回调的控件

    switch (code) {
        case LV_EVENT_KEY:
        {
            uint32_t key = lv_event_get_key(e);    //获取当前事件触发对应的keypad键值
       
            if (key == LV_KEY_BACKSPACE) {
             
            }
            break;
        }
        default:
            break;
    }
}

     我们从上面这个例子可以知道,在确认事件类型为LV_EVENT_KEY后,即该事件由keypad按键触发,接下来我们就通过 lv_event_get_key(e);函数来获取触发该LV_EVENT_KEY事件的具体键值;我们看一下lv_event_get_key(e);函数的具体实现如下:

lv_event.c:

uint32_t lv_event_get_key(lv_event_t * e)
{
    if(e->code == LV_EVENT_KEY) {
        uint32_t * k = lv_event_get_param(e);
        if(k) return *k;
        else return 0;
    }
    else {
        LV_LOG_WARN("Not interpreted with this event code");
        return 0;
    }
}


void * lv_event_get_param(lv_event_t * e)
{
    return e->param;
}

        比较简单,基本上就是返回当前事件触发结构体lv_event_t * e中的parm;那按理来说这个parm就是我们要的键值了,我们可以合理推断键值传递函数lv_res_t lv_group_send_data(lv_group_t * group, uint32_t c)就是把键值传递到e->param,然后由用户通过lv_event_get_key函数获取,我们看看是不是这样:

lv_group.c:

//键值传递函数
lv_res_t lv_group_send_data(lv_group_t * group, uint32_t c)
{
    lv_obj_t * act = lv_group_get_focused(group);
    if(act == NULL) return LV_RES_OK;

    if(lv_obj_has_state(act, LV_STATE_DISABLED)) return LV_RES_OK;

    return lv_event_send(act, LV_EVENT_KEY, &c);
}



lv_event.c:

lv_res_t lv_event_send(lv_obj_t * obj, lv_event_code_t event_code, void * param)
{
    if(obj == NULL) return LV_RES_OK;

    LV_ASSERT_OBJ(obj, MY_CLASS);

    lv_event_t e;
    e.target = obj;
    e.current_target = obj;
    e.code = event_code;
    e.user_data = NULL;
    e.param = param;
    e.deleted = 0;
    e.stop_bubbling = 0;
    e.stop_processing = 0;

    /*Build a simple linked list from the objects used in the events
     *It's important to know if this object was deleted by a nested event
     *called from this `event_cb`.*/
    e.prev = event_head;
    event_head = &e;

    /*Send the event*/
    lv_res_t res = event_send_core(&e);

    /*Remove this element from the list*/
    event_head = e.prev;

    return res;
}

    我们从上面的代码中可以看出来,键值传递函数lv_group_send_data(lv_group_t * group, uint32_t c)内部首先获取了输入参数group中聚焦的对象,当对象不为disable状态时,就将当前对象act和事件触发类型LV_EVENT_KEY以及输入参数c通过lv_event_send函数传递给事件触发结构体lv_event_t * e,而lv_event_send函数又将参数c传递到e->param;这里如果想要验证我们的刚刚的猜测,如果输入参数c为按下的键值,那么一切都合理了。另外我们可以猜测lv_group_send_data(lv_group_t * group, uint32_t c)的参数group应该就是和当前输入设备绑定的组;

    前面我们提到了lv_group_send_data函数LVGL源码的indev.c文件中被使用,我们现在来看看这个函数是如何被使用的:

indev.c:

/**
 * Process a new point from LV_INDEV_TYPE_KEYPAD input device
 * @param i pointer to an input device
 * @param data pointer to the data read from the input device
 */
static void indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data)
{
    if(data->state == LV_INDEV_STATE_PRESSED && i->proc.wait_until_release) return;

    if(i->proc.wait_until_release) {
        i->proc.wait_until_release      = 0;
        i->proc.pr_timestamp            = 0;
        i->proc.long_pr_sent            = 0;
        i->proc.types.keypad.last_state = LV_INDEV_STATE_RELEASED; /*To skip the processing of release*/
    }

    lv_group_t * g = i->group;
    if(g == NULL) return;

    indev_obj_act = lv_group_get_focused(g);
    if(indev_obj_act == NULL) return;

    bool dis = lv_obj_has_state(indev_obj_act, LV_STATE_DISABLED);

    /*Save the last key to compare it with the current latter on RELEASE*/
    uint32_t prev_key = i->proc.types.keypad.last_key;

    /*Save the last key.
     *It must be done here else `lv_indev_get_key` will return the last key in events*/
    i->proc.types.keypad.last_key = data->key;

    /*Save the previous state so we can detect state changes below and also set the last state now
     *so if any event handler on the way returns `LV_RES_INV` the last state is remembered
     *for the next time*/
    uint32_t prev_state             = i->proc.types.keypad.last_state;
    i->proc.types.keypad.last_state = data->state;

    /*Key press happened*/
    if(data->state == LV_INDEV_STATE_PRESSED && prev_state == LV_INDEV_STATE_RELEASED) {
        LV_LOG_INFO("%" LV_PRIu32 " key is pressed", data->key);
        i->proc.pr_timestamp = lv_tick_get();

        /*Move the focus on NEXT*/
        if(data->key == LV_KEY_NEXT) {
            lv_group_set_editing(g, false); /*Editing is not used by KEYPAD is be sure it is disabled*/
            lv_group_focus_next(g);
            if(indev_reset_check(&i->proc)) return;
        }
        /*Move the focus on PREV*/
        else if(data->key == LV_KEY_PREV) {
            lv_group_set_editing(g, false); /*Editing is not used by KEYPAD is be sure it is disabled*/
            lv_group_focus_prev(g);
            if(indev_reset_check(&i->proc)) return;
        }
        else if(!dis) {
            /*Simulate a press on the object if ENTER was pressed*/
            if(data->key == LV_KEY_ENTER) {
                /*Send the ENTER as a normal KEY*/
                lv_group_send_data(g, LV_KEY_ENTER);
                if(indev_reset_check(&i->proc)) return;

                if(!dis) lv_event_send(indev_obj_act, LV_EVENT_PRESSED, indev_act);
                if(indev_reset_check(&i->proc)) return;
            }
            else if(data->key == LV_KEY_ESC) {
                /*Send the ESC as a normal KEY*/
                lv_group_send_data(g, LV_KEY_ESC);
                if(indev_reset_check(&i->proc)) return;

                lv_event_send(indev_obj_act, LV_EVENT_CANCEL, indev_act);
                if(indev_reset_check(&i->proc)) return;
            }
            /*Just send other keys to the object (e.g. 'A' or `LV_GROUP_KEY_RIGHT`)*/
            else {
                lv_group_send_data(g, data->key);
                if(indev_reset_check(&i->proc)) return;
            }
        }
    }
    /*Pressing*/
    else if(!dis && data->state == LV_INDEV_STATE_PRESSED && prev_state == LV_INDEV_STATE_PRESSED) {

        if(data->key == LV_KEY_ENTER) {
            lv_event_send(indev_obj_act, LV_EVENT_PRESSING, indev_act);
            if(indev_reset_check(&i->proc)) return;
        }

        /*Long press time has elapsed?*/
        if(i->proc.long_pr_sent == 0 && lv_tick_elaps(i->proc.pr_timestamp) > i->driver->long_press_time) {
            i->proc.long_pr_sent = 1;
            if(data->key == LV_KEY_ENTER) {
                i->proc.longpr_rep_timestamp = lv_tick_get();
                lv_event_send(indev_obj_act, LV_EVENT_LONG_PRESSED, indev_act);
                if(indev_reset_check(&i->proc)) return;
            }
        }
        /*Long press repeated time has elapsed?*/
        else if(i->proc.long_pr_sent != 0 &&
                lv_tick_elaps(i->proc.longpr_rep_timestamp) > i->driver->long_press_repeat_time) {

            i->proc.longpr_rep_timestamp = lv_tick_get();

            /*Send LONG_PRESS_REP on ENTER*/
            if(data->key == LV_KEY_ENTER) {
                lv_event_send(indev_obj_act, LV_EVENT_LONG_PRESSED_REPEAT, indev_act);
                if(indev_reset_check(&i->proc)) return;
            }
            /*Move the focus on NEXT again*/
            else if(data->key == LV_KEY_NEXT) {
                lv_group_set_editing(g, false); /*Editing is not used by KEYPAD is be sure it is disabled*/
                lv_group_focus_next(g);
                if(indev_reset_check(&i->proc)) return;
            }
            /*Move the focus on PREV again*/
            else if(data->key == LV_KEY_PREV) {
                lv_group_set_editing(g, false); /*Editing is not used by KEYPAD is be sure it is disabled*/
                lv_group_focus_prev(g);
                if(indev_reset_check(&i->proc)) return;
            }
            /*Just send other keys again to the object (e.g. 'A' or `LV_GROUP_KEY_RIGHT)*/
            else {
                lv_group_send_data(g, data->key);
                if(indev_reset_check(&i->proc)) return;
            }
        }
    }
    /*Release happened*/
    else if(!dis && data->state == LV_INDEV_STATE_RELEASED && prev_state == LV_INDEV_STATE_PRESSED) {
        LV_LOG_INFO("%" LV_PRIu32 " key is released", data->key);
        /*The user might clear the key when it was released. Always release the pressed key*/
        data->key = prev_key;
        if(data->key == LV_KEY_ENTER) {

            lv_event_send(indev_obj_act, LV_EVENT_RELEASED, indev_act);
            if(indev_reset_check(&i->proc)) return;

            if(i->proc.long_pr_sent == 0) {
                lv_event_send(indev_obj_act, LV_EVENT_SHORT_CLICKED, indev_act);
                if(indev_reset_check(&i->proc)) return;
            }

            lv_event_send(indev_obj_act, LV_EVENT_CLICKED, indev_act);
            if(indev_reset_check(&i->proc)) return;

        }
        i->proc.pr_timestamp = 0;
        i->proc.long_pr_sent = 0;
    }
    indev_obj_act = NULL;
}

该函数代码有点长,我们看其中一部分和键值传递函数有关的:

static void indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data)
{
......

 /*Just send other keys to the object (e.g. 'A' or `LV_GROUP_KEY_RIGHT`)*/
            else {
                lv_group_send_data(g, data->key);
                if(indev_reset_check(&i->proc)) return;
            }
......
}

    我们可以看到,在源码中使用键值传递函数时,输入参数分别为g和data->key,而indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data)函数中定义lv_group_t * g = i->group;i在indev_keypad_proc上面的注释中有说明为输入设备,我们很自然就可以联想到应该就是当前输入设备,因此“lv_group_send_data(lv_group_t * group, uint32_t c)的参数group应该就是和当前输入设备绑定的组”这个猜测成立;其中 if(indev_reset_check(&i->proc)) return;这个判断是为了检查输入设备是否需要重置,如果需要重置,清理相关全局状态,并指示调用方中止当前输入查询流程,一般我们不会主动重置输入设备,所以这个一般不用管。

/**
 * Process a new point from LV_INDEV_TYPE_KEYPAD input device
 * @param i pointer to an input device
 * @param data pointer to the data read from the input device
 */
static void indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data)
{
    if(data->state == LV_INDEV_STATE_PRESSED && i->proc.wait_until_release) return;

    if(i->proc.wait_until_release) {
        i->proc.wait_until_release      = 0;
        i->proc.pr_timestamp            = 0;
        i->proc.long_pr_sent            = 0;
        i->proc.types.keypad.last_state = LV_INDEV_STATE_RELEASED; /*To skip the processing of release*/
    }

    lv_group_t * g = i->group;

    ......
}

    而键值传递函数lv_group_send_data(g, data->key);在这里的另一个参数为data->key,通过indev_keypad_proc()函数上面的注释我们可以知道data为输入设备对应的结构体,而data->key我们从lv_port_indev.c文件中关于keypad的读取函数static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)可以知道,data->key就是我们输入的keypad键值。那么键值传递函数lv_res_t lv_group_send_data(lv_group_t * group, uint32_t c)就是把键值传递到e->param这个猜测也成立,且输入参数c就为按下的键值;

lv_port_indev.c:

static void keypad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    static uint32_t last_key = 0;
  
    /*Get whether the a key is pressed and save the pressed key*/
    uint32_t act_key = keypad_get_key();

    if(act_key != 0) {
        data->state = LV_INDEV_STATE_PR;

        /*Translate the keys to LVGL control characters according to your key definitions*/
        switch(act_key) {
            case 1:
                act_key = LV_KEY_ESC;  
                break;
            case 2:
                act_key = LV_KEY_BACKSPACE;
                break;
            case 3:
                act_key = LV_KEY_DOWN;
                break;
            case 4:
                act_key = LV_KEY_UP;
                break;
			default:
				break;
        }

        last_key = act_key;
				
    }
    else {
        data->state = LV_INDEV_STATE_REL;
    }

    data->key = last_key;
}


/*Get the currently being pressed key.  0 if no key is pressed*/
static uint32_t keypad_get_key(void)
{
    /*Your code comes here*/
	  uint8_t i;
       /*Check to buttons see which is being pressed (assume there are 2 buttons)*/
    for(i = 0; i < keypad_num; i++) {
        /*Return the pressed button's ID*/
        if(keypad_is_pressed(i)) {
					return i + 1;       //由于keypad值不能为0,因此这里+1,保证从1开始
        }
    }
    
    /*No button pressed*/
    return 0;   //当没有按钮按下时需要返回0
    
}

static bool keypad_is_pressed(uint8_t id)
{
  if(id >= keypad_num) {
    return false;  // 检查id是否在有效范围
  }
		
	//增加按钮消抖
	return HAL_GPIO_ReadPin(keypad_ports[id], keypad_pins[id]) == GPIO_PIN_RESET;
}

    由此,我们知道了LVGL中关于Keypad键值传递的大致流程,同时需要注意的是,在indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data)中,我们可以看出源码对预定义的键值如  LV_KEY_ESC、LV_KEY_ENTER、LV_KEY_NEXT、LV_KEY_PREV的功能进行了一些预定义,因此在使用上述键值时需要注意,比如LV_KEY_NEXT、LV_KEY_PREV这两个个键值被触发,用lv_event_get_key()函数是获取不到键值的,因为这两个按键按下后并没有调用lv_group_send_data键值传递函数,还有就是ENTER和ESC在一些情况下不止会触发LV_EVENT_KEY事件,还会触发其他类型事件,具体可以自行看函数内部的实现;其他的键值则没有做功能预定义,使用时在事件触发回调函数中直接用v_event_get_key()函数获取键值然后做对应处理即可。同时由于typedef uint8_t lv_key_t,键值类型为uint8_t,最大255,因此我们还可以在键值定义的enum中定义自己的keypad键,只要保证键值不重复即可,类似:

enum {
    LV_KEY_UP        = 17,  /*0x11*/
    LV_KEY_DOWN      = 18,  /*0x12*/
    LV_KEY_RIGHT     = 19,  /*0x13*/
    LV_KEY_LEFT      = 20,  /*0x14*/
    LV_KEY_ESC       = 27,  /*0x1B*/
    LV_KEY_DEL       = 127, /*0x7F*/
    LV_KEY_BACKSPACE = 8,   /*0x08*/
    LV_KEY_ENTER     = 10,  /*0x0A, '\n'*/
    LV_KEY_NEXT      = 9,   /*0x09, '\t'*/
    LV_KEY_PREV      = 11,  /*0x0B, '*/
    LV_KEY_HOME      = 2,   /*0x02, STX*/
    LV_KEY_END       = 3,   /*0x03, ETX*/


    //自定义键值
    LV_KEY_MODE       = 200,   /*0x03, ETX*/
    LV_KEY_BACK       = 201,   /*0x03, ETX*/
};
typedef uint8_t lv_key_t;

    我们知道,在LVGL中关于设备参数_lv_indev_drv_t中有两个值,分别是long_press_time和 long_press_repeat_time;它们的定义如下,我们创建设备初始化时这两个参数的值分别为400和100,意思是按键按下400ms后就认为是长按,在接下来的时间里,如果保持按下,那么每100ms就会执行一次重复触发事件;反映到keypad键值里,就是当你按下keypad的时候,如果长按的话就会频繁触发该键值和对应的回调函数事件。

typedef struct _lv_indev_drv_t {

    /**< Input device type*/
    lv_indev_type_t type;

    /**< Function pointer to read input device data.*/
    void (*read_cb)(struct _lv_indev_drv_t * indev_drv, lv_indev_data_t * data);

    /** Called when an action happened on the input device.
     * The second parameter is the event from `lv_event_t`*/
    void (*feedback_cb)(struct _lv_indev_drv_t *, uint8_t);

#if LV_USE_USER_DATA
    void * user_data;
#endif

    /**< Pointer to the assigned display*/
    struct _lv_disp_t * disp;

    /**< Timer to periodically read the input device*/
    lv_timer_t * read_timer;

    /**< Number of pixels to slide before actually drag the object*/
    uint8_t scroll_limit;

    /**< Drag throw slow-down in [%]. Greater value means faster slow-down*/
    uint8_t scroll_throw;

    /**< At least this difference should be between two points to evaluate as gesture*/
    uint8_t gesture_min_velocity;

    /**< At least this difference should be to send a gesture*/
    uint8_t gesture_limit;

    /**< Long press time in milliseconds*/
    uint16_t long_press_time;

    /**< Repeated trigger period in long press [ms]*/
    uint16_t long_press_repeat_time;
} lv_indev_drv_t;

   那么如果你想你按下禁止keypad按键由于按下超过long_press_repeat_time导致重复触发回调函数该怎么做呢?我目前有两种方法:

    第一种在输入设备初始化时把long_press_repeat_time设置为最大值,即0xFFFF,这样只能保证按下按键大概一分钟内不会重复触发LV_EVENT_KEY事件回调函数;

 lv_indev_port.c:
void lv_port_indev_init(void)
{
  ......

 /*Initialize your keypad or keyboard if you have*/
    keypad_init();

    /*Register a keypad input device*/
    lv_indev_drv_init(&indev_keypad_drv);  //初始化输入设备结构体
    indev_keypad_drv.type = LV_INDEV_TYPE_KEYPAD;
    indev_keypad_drv.read_cb = keypad_read;

    indev_keypad_drv.long_press_time = 1000;   //自定义
    indev_keypad_drv.long_press_repeat_time = 0xFFFF;
    indev_keypad = lv_indev_drv_register(&indev_keypad_drv);
  ......
}

    第二种就是修改LVGL源码中indev.c文件中的indev_keypad_proc函数,将长按连续触发判断中发送LV_EVENT_KEY事件的代码注释掉,这样按下按键不管多久都能禁止重复触发LV_EVENT_KEY事件回调函数,如下:

lv_indev_port.c:
static void indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data)
{
    ......
    /*Long press repeated time has elapsed?*/
    ......
            /*Just send other keys again to the object (e.g. 'A' or `LV_GROUP_KEY_RIGHT)*/
            else {
                  //lv_group_send_data(g, data->key);
                  if(indev_reset_check(&i->proc)) return;
            }
        }
    ......
}

    另外开一个坑,前面我们提到了一个特别长的函数为static void indev_keypad_proc(lv_indev_t * i, lv_indev_data_t * data),这个是关于keypad设备的键值处理函数,同时还有其他例如encoder编码器的键值处理函数等,他们都在设备定时器回调函数lv_indev_read_timer_cb()函数中被调用,我大概看了一下,我们的事件处理函数lv_task_handler();里面就有调用定时器链表并执行了相关回调函数,lv_indev_read_timer_cb()函数应该也是在lv_task_handler();里面被执行,由此就实现了对外部输入按键的实时监测和判断。感觉这个定时器链表还挺有意思的,后面有时间可以讲一下lv_task_handler()是如何工作的,同时lv_indev_read_timer_cb()又是如何被调用的


void lv_indev_read_timer_cb(lv_timer_t * timer)
{
    INDEV_TRACE("begin");

    lv_indev_data_t data;

    indev_act = timer->user_data;

    /*Read and process all indevs*/
    if(indev_act->driver->disp == NULL) return; /*Not assigned to any displays*/

    /*Handle reset query before processing the point*/
    indev_proc_reset_query_handler(indev_act);

    if(indev_act->proc.disabled ||
       indev_act->driver->disp->prev_scr != NULL) return; /*Input disabled or screen animation active*/
    bool continue_reading;
    do {
        /*Read the data*/
        _lv_indev_read(indev_act, &data);
        continue_reading = data.continue_reading;

        /*The active object might be deleted even in the read function*/
        indev_proc_reset_query_handler(indev_act);
        indev_obj_act = NULL;

        indev_act->proc.state = data.state;

        /*Save the last activity time*/
        if(indev_act->proc.state == LV_INDEV_STATE_PRESSED) {
            indev_act->driver->disp->last_activity_time = lv_tick_get();
        }
        else if(indev_act->driver->type == LV_INDEV_TYPE_ENCODER && data.enc_diff) {
            indev_act->driver->disp->last_activity_time = lv_tick_get();
        }

        if(indev_act->driver->type == LV_INDEV_TYPE_POINTER) {
            indev_pointer_proc(indev_act, &data);
        }
        else if(indev_act->driver->type == LV_INDEV_TYPE_KEYPAD) {
            indev_keypad_proc(indev_act, &data);
        }
        else if(indev_act->driver->type == LV_INDEV_TYPE_ENCODER) {
            indev_encoder_proc(indev_act, &data);
        }
        else if(indev_act->driver->type == LV_INDEV_TYPE_BUTTON) {
            indev_button_proc(indev_act, &data);
        }
        /*Handle reset query if it happened in during processing*/
        indev_proc_reset_query_handler(indev_act);
    } while(continue_reading);

    /*End of indev processing, so no act indev*/
    indev_act     = NULL;
    indev_obj_act = NULL;

    INDEV_TRACE("finished");
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值