[LVGL] 从0开始,学LVGL:进阶应用与项目实战(上)

第4部分:进阶应用与项目实战(上)

文章目录

  • **第4部分:进阶应用与项目实战(上)**
    • [**第11章:数据展示三剑客 - 图表、列表、滚轮**](#第11章:数据展示三剑客 - 图表、列表、滚轮)
      • [**11.1 图表:数据的可视化艺术**](#11.1 图表:数据的可视化艺术)
        • [**11.1.1 图表的基本数学模型**](#11.1.1 图表的基本数学模型)
        • [**11.1.2 创建实时数据监控图表**](#11.1.2 创建实时数据监控图表)
        • [**11.1.3 高级图表特性**](#11.1.3 高级图表特性)
      • [**11.2 列表:结构化数据的展示**](#11.2 列表:结构化数据的展示)
        • [**11.2.1 列表的数学模型**](#11.2.1 列表的数学模型)
        • [**11.2.2 创建功能完整的消息列表**](#11.2.2 创建功能完整的消息列表)
      • [**11.3 滚轮:优雅的选择器**](#11.3 滚轮:优雅的选择器)
        • [**11.3.1 滚轮的物理模型**](#11.3.1 滚轮的物理模型)
        • [**11.3.2 创建日期时间选择器**](#11.3.2 创建日期时间选择器)
    • **第12章:图片与字体**
      • [**12.1 图片:视觉元素的载体**](#12.1 图片:视觉元素的载体)
        • [**12.1.1 图片的数学表示**](#12.1.1 图片的数学表示)
        • [**12.1.2 图片存储格式对比**](#12.1.2 图片存储格式对比)
        • [**12.1.3 实战:创建图片库浏览器**](#12.1.3 实战:创建图片库浏览器)
      • [**12.2 字体:文字渲染的艺术**](#12.2 字体:文字渲染的艺术)
        • [**12.2.1 字体的数学基础**](#12.2.1 字体的数学基础)
        • [**12.2.2 使用外部字体**](#12.2.2 使用外部字体)
        • [**12.2.3 自定义字体生成**](#12.2.3 自定义字体生成)
      • **本章总结与挑战**

第11章:数据展示三剑客 - 图表、列表、滚轮

11.1 图表:数据的可视化艺术

在现代GUI应用中,数据可视化至关重要。LVGL提供了强大的图表控件,能够将抽象数据转化为直观的图形。

11.1.1 图表的基本数学模型

图表的核心是将数据点映射到屏幕坐标:

设数据序列 D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x n , y n ) } D = \{(x_1, y_1), (x_2, y_2), ..., (x_n, y_n)\} D={(x1,y1),(x2,y2),...,(xn,yn)},图表区域尺寸为 ( W , H ) (W, H) (W,H),值域为 [ y m i n , y m a x ] [y_{min}, y_{max}] [ymin,ymax]。

则每个数据点的屏幕坐标 ( X i , Y i ) (X_i, Y_i) (Xi,Yi) 计算为:
X i = x i − x m i n x m a x − x m i n × W X_i = \frac{x_i - x_{min}}{x_{max} - x_{min}} \times W Xi=xmax−xminxi−xmin×W
Y i = H − y i − y m i n y m a x − y m i n × H Y_i = H - \frac{y_i - y_{min}}{y_{max} - y_{min}} \times H Yi=H−ymax−yminyi−ymin×H

11.1.2 创建实时数据监控图表

让我们创建一个传感器数据实时监控界面:

cpp 复制代码
#include <lvgl.h>
#include <stdlib.h>
#include <time.h>

// 图表数据结构
typedef struct {
    lv_obj_t * chart;
    lv_chart_series_t * temp_series;
    lv_chart_series_t * humi_series;
    lv_chart_series_t * press_series;
    uint32_t data_count;
} chart_manager_t;

static chart_manager_t chart_mgr;

/**
 * 创建多系列图表
 */
void create_multi_series_chart() {
    lv_obj_t * parent = lv_scr_act();
    
    // 创建图表容器
    lv_obj_t * container = lv_obj_create(parent);
    lv_obj_set_size(container, 450, 300);
    lv_obj_align(container, LV_ALIGN_TOP_LEFT, 20, 20);
    lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_style_pad_all(container, 15, 0);
    
    // 标题
    lv_obj_t * title = lv_label_create(container);
    lv_label_set_text(title, "📊 环境传感器数据");
    lv_obj_set_style_text_font(title, &lv_font_montserrat_18, 0);
    
    // 创建图表对象
    chart_mgr.chart = lv_chart_create(container);
    lv_obj_set_size(chart_mgr.chart, LV_PCT(100), LV_PCT(80));
    
    // 配置图表基本属性
    lv_chart_set_type(chart_mgr.chart, LV_CHART_TYPE_LINE);
    lv_chart_set_range(chart_mgr.chart, LV_CHART_AXIS_PRIMARY_Y, 0, 100);
    lv_chart_set_point_count(chart_mgr.chart, 50);  // 显示50个数据点
    lv_chart_set_div_line_count(chart_mgr.chart, 5, 5); // 网格线
    
    // 启用滚动模式 - 新数据从右侧进入
    lv_chart_set_zoom_x(chart_mgr.chart, 256); // 无缩放
    lv_chart_set_update_mode(chart_mgr.chart, LV_CHART_UPDATE_MODE_SHIFT);
    
    // 添加坐标轴标签
    lv_obj_t * x_axis_label = lv_label_create(container);
    lv_label_set_text(x_axis_label, "时间序列");
    lv_obj_set_style_text_align(x_axis_label, LV_TEXT_ALIGN_CENTER, 0);
    lv_obj_set_width(x_axis_label, LV_PCT(100));
    
    // 创建数据系列
    chart_mgr.temp_series = lv_chart_add_series(chart_mgr.chart, 
                                               lv_color_hex(0xFF6B6B), 
                                               LV_CHART_AXIS_PRIMARY_Y);
    chart_mgr.humi_series = lv_chart_add_series(chart_mgr.chart, 
                                               lv_color_hex(0x4ECDC4), 
                                               LV_CHART_AXIS_PRIMARY_Y);
    chart_mgr.press_series = lv_chart_add_series(chart_mgr.chart, 
                                                lv_color_hex(0x45B7D1), 
                                                LV_CHART_AXIS_PRIMARY_Y);
    
    // 初始化一些随机数据
    srand(time(NULL));
    for(int i = 0; i < 10; i++) {
        lv_chart_set_next_value(chart_mgr.chart, chart_mgr.temp_series, 20 + rand() % 10);
        lv_chart_set_next_value(chart_mgr.chart, chart_mgr.humi_series, 40 + rand() % 30);
        lv_chart_set_next_value(chart_mgr.chart, chart_mgr.press_series, 60 + rand() % 20);
        chart_mgr.data_count++;
    }
    
    // 创建图例
    create_chart_legend(container);
    
    // 启动数据更新定时器
    lv_timer_t * update_timer = lv_timer_create(update_chart_data, 1000, NULL);
    lv_timer_set_repeat_count(update_timer, 0); // 无限重复
}

/**
 * 创建图表图例
 */
void create_chart_legend(lv_obj_t * parent) {
    lv_obj_t * legend = lv_obj_create(parent);
    lv_obj_set_size(legend, LV_PCT(100), 30);
    lv_obj_set_style_bg_opa(legend, LV_OPA_0, 0);
    lv_obj_set_style_border_width(legend, 0, 0);
    lv_obj_set_flex_flow(legend, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(legend, LV_FLEX_ALIGN_SPACE_AROUND, 
                         LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    
    // 温度图例
    lv_obj_t * temp_legend = create_legend_item(legend, "温度", lv_color_hex(0xFF6B6B));
    lv_obj_t * humi_legend = create_legend_item(legend, "湿度", lv_color_hex(0x4ECDC4));
    lv_obj_t * press_legend = create_legend_item(legend, "压力", lv_color_hex(0x45B7D1));
}

/**
 * 创建单个图例项
 */
lv_obj_t * create_legend_item(lv_obj_t * parent, const char * text, lv_color_t color) {
    lv_obj_t * container = lv_obj_create(parent);
    lv_obj_set_size(container, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
    lv_obj_set_style_bg_opa(container, LV_OPA_0, 0);
    lv_obj_set_style_border_width(container, 0, 0);
    lv_obj_set_flex_flow(container, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(container, LV_FLEX_ALIGN_CENTER, 
                         LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    
    // 颜色标识
    lv_obj_t * color_indicator = lv_obj_create(container);
    lv_obj_set_size(color_indicator, 12, 12);
    lv_obj_set_style_bg_color(color_indicator, color, 0);
    lv_obj_set_style_radius(color_indicator, 6, 0);
    
    // 文本标签
    lv_obj_t * label = lv_label_create(container);
    lv_label_set_text(label, text);
    lv_obj_set_style_text_font(label, &lv_font_montserrat_12, 0);
    
    return container;
}

/**
 * 更新图表数据 - 模拟传感器数据
 */
void update_chart_data(lv_timer_t * timer) {
    // 模拟传感器读数
    int temp = 20 + rand() % 10 + (rand() % 10 - 5) * 0.1;  // 20-30°C,带小数波动
    int humi = 40 + rand() % 30 + (rand() % 10 - 5) * 0.1;  // 40-70%
    int press = 60 + rand() % 20 + (rand() % 10 - 5) * 0.1; // 60-80%
    
    // 添加新数据点
    lv_chart_set_next_value(chart_mgr.chart, chart_mgr.temp_series, temp);
    lv_chart_set_next_value(chart_mgr.chart, chart_mgr.humi_series, humi);
    lv_chart_set_next_value(chart_mgr.chart, chart_mgr.press_series, press);
    
    chart_mgr.data_count++;
    
    // 每100个数据点清除一次旧数据(可选)
    if(chart_mgr.data_count > 100) {
        lv_chart_refresh(chart_mgr.chart);
        chart_mgr.data_count = 50;
    }
}
11.1.3 高级图表特性

多Y轴支持:

cpp 复制代码
// 创建次要Y轴
lv_chart_set_axis_tick(chart, LV_CHART_AXIS_PRIMARY_Y, 5, 2, 6, 2, true, 40);
lv_chart_set_axis_tick(chart, LV_CHART_AXIS_SECONDARY_Y, 5, 2, 6, 2, true, 40);

// 为系列指定不同的Y轴
lv_chart_series_t * series = lv_chart_add_series(chart, color, LV_CHART_AXIS_SECONDARY_Y);

事件交互:

cpp 复制代码
// 启用光标
lv_chart_set_cursor_point(chart, cursor, &point);

// 添加点击事件
lv_obj_add_event_cb(chart, [](lv_event_t * e) {
    lv_obj_t * chart = lv_event_get_target(e);
    lv_chart_series_t * ser = lv_chart_get_series_next(chart, NULL);
    
    // 获取点击位置对应的数据
    uint16_t id;
    lv_chart_get_pressed_point(chart, &id);
    if(id != LV_CHART_POINT_NONE) {
        lv_coord_t * y_array = lv_chart_get_y_array(chart, ser);
        printf("点击的数据点 %d: 值=%d\n", id, y_array[id]);
    }
}, LV_EVENT_CLICKED, NULL);

11.2 列表:结构化数据的展示

列表控件非常适合展示结构化数据,如消息、设置项、文件等。

11.2.1 列表的数学模型

列表可以看作一个线性序列:
L = [ i 1 , i 2 , . . . , i n ] L = [i_1, i_2, ..., i_n] L=[i1,i2,...,in]

其中每个列表项 i k i_k ik 包含:

  • 文本内容 t k t_k tk
  • 图标 i c o n k icon_k iconk(可选)
  • 元数据 m e t a k meta_k metak(可选)

列表的渲染高度计算:
H t o t a l = ∑ k = 1 n h k + ( n − 1 ) × s p a c i n g H_{total} = \sum_{k=1}^n h_k + (n-1) \times spacing Htotal=k=1∑nhk+(n−1)×spacing

11.2.2 创建功能完整的消息列表
cpp 复制代码
#include <lvgl.h>

// 消息数据结构
typedef struct {
    const char * sender;
    const char * preview;
    const char * time;
    bool unread;
    lv_color_t color;
} message_t;

// 模拟消息数据
static message_t messages[] = {
    {"张三", "你好,项目进展如何?", "10:30", true, LV_COLOR_MAKE(0x34, 0x98, 0xDB)},
    {"李四", "会议改到下午3点", "09:15", true, LV_COLOR_MAKE(0x2E, 0xCC, 0x71)},
    {"王五", "文档已更新,请查看", "昨天", false, LV_COLOR_MAKE(0xE7, 0x4C, 0x3C)},
    {"赵六", "周末一起吃饭?", "昨天", false, LV_COLOR_MAKE(0xF3, 0x9C, 0x12)},
    {"钱七", "报销单已提交", "周三", false, LV_COLOR_MAKE(0x9B, 0x59, 0xB6)},
    {"孙八", "新的需求文档", "周二", false, LV_COLOR_MAKE(0x1A, 0xBC, 0x9C)},
};

/**
 * 创建消息列表
 */
void create_message_list() {
    lv_obj_t * parent = lv_scr_act();
    
    // 创建列表容器
    lv_obj_t * container = lv_obj_create(parent);
    lv_obj_set_size(container, 350, 400);
    lv_obj_align(container, LV_ALIGN_TOP_RIGHT, -20, 20);
    lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_style_pad_all(container, 0, 0);
    
    // 标题栏
    lv_obj_t * header = lv_obj_create(container);
    lv_obj_set_size(header, LV_PCT(100), 50);
    lv_obj_set_style_bg_color(header, lv_color_hex(0x3498DB), 0);
    lv_obj_set_style_radius(header, 0, 0);
    lv_obj_set_flex_flow(header, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(header, LV_FLEX_ALIGN_SPACE_BETWEEN, 
                         LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_set_style_pad_hor(header, 15, 0);
    
    lv_obj_t * title = lv_label_create(header);
    lv_label_set_text(title, "💬 消息");
    lv_obj_set_style_text_color(title, lv_color_white(), 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_18, 0);
    
    lv_obj_t * count_label = lv_label_create(header);
    lv_label_set_text(count_label, "2条未读");
    lv_obj_set_style_text_color(count_label, lv_color_white(), 0);
    
    // 创建列表对象
    lv_obj_t * list = lv_list_create(container);
    lv_obj_set_size(list, LV_PCT(100), LV_PCT(100));
    lv_obj_set_style_bg_opa(list, LV_OPA_0, 0);
    lv_obj_set_style_pad_all(list, 0, 0);
    lv_obj_set_style_radius(list, 0, 0);
    
    // 设置列表样式
    lv_obj_set_style_bg_opa(list, LV_OPA_0, LV_PART_MAIN);
    lv_obj_set_style_border_width(list, 0, LV_PART_MAIN);
    
    // 添加消息项
    for(int i = 0; i < sizeof(messages) / sizeof(messages[0]); i++) {
        add_message_to_list(list, &messages[i]);
    }
}

/**
 * 添加消息到列表
 */
void add_message_to_list(lv_obj_t * list, message_t * msg) {
    // 创建列表项
    lv_obj_t * list_btn = lv_list_add_btn(list, NULL, NULL); // 不使用内置图标
    
    // 设置列表项样式
    lv_obj_set_style_bg_opa(list_btn, LV_OPA_0, 0);
    lv_obj_set_style_pad_all(list_btn, 15, 0);
    lv_obj_set_style_border_side(list_btn, LV_BORDER_SIDE_BOTTOM, 0);
    lv_obj_set_style_border_color(list_btn, lv_color_hex(0xECF0F1), 0);
    lv_obj_set_style_border_width(list_btn, 1, 0);
    
    // 如果有未读消息,添加背景色
    if(msg->unread) {
        lv_obj_set_style_bg_opa(list_btn, LV_OPA_10, 0);
        lv_obj_set_style_bg_color(list_btn, lv_color_hex(0x3498DB), 0);
    }
    
    // 创建Flex容器来组织内容
    lv_obj_t * content = lv_obj_create(list_btn);
    lv_obj_set_size(content, LV_PCT(100), LV_SIZE_CONTENT);
    lv_obj_set_style_bg_opa(content, LV_OPA_0, 0);
    lv_obj_set_style_border_width(content, 0, 0);
    lv_obj_set_flex_flow(content, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(content, LV_FLEX_ALIGN_START, 
                         LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER);
    
    // 左侧:头像/图标
    lv_obj_t * avatar = lv_obj_create(content);
    lv_obj_set_size(avatar, 40, 40);
    lv_obj_set_style_bg_color(avatar, msg->color, 0);
    lv_obj_set_style_radius(avatar, 20, 0);
    lv_obj_set_style_shadow_width(avatar, 5, 0);
    lv_obj_set_style_shadow_color(avatar, msg->color, 0);
    lv_obj_set_style_shadow_opa(avatar, LV_OPA_30, 0);
    
    // 头像文字(首字符)
    char avatar_text[3];
    snprintf(avatar_text, sizeof(avatar_text), "%c", msg->sender[0]);
    lv_obj_t * avatar_label = lv_label_create(avatar);
    lv_label_set_text(avatar_label, avatar_text);
    lv_obj_set_style_text_color(avatar_label, lv_color_white(), 0);
    lv_obj_set_style_text_font(avatar_label, &lv_font_montserrat_14, 0);
    lv_obj_center(avatar_label);
    
    // 中间:消息内容
    lv_obj_t * text_container = lv_obj_create(content);
    lv_obj_set_size(text_container, LV_PCT(70), LV_SIZE_CONTENT);
    lv_obj_set_style_bg_opa(text_container, LV_OPA_0, 0);
    lv_obj_set_style_border_width(text_container, 0, 0);
    lv_obj_set_flex_flow(text_container, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(text_container, LV_FLEX_ALIGN_START, 
                         LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
    lv_obj_set_style_pad_left(text_container, 10, 0);
    
    // 发件人
    lv_obj_t * sender_label = lv_label_create(text_container);
    lv_label_set_text(sender_label, msg->sender);
    lv_obj_set_style_text_font(sender_label, &lv_font_montserrat_14, 0);
    lv_obj_set_style_text_color(sender_label, lv_color_black(), 0);
    if(msg->unread) {
        lv_obj_set_style_text_color(sender_label, lv_color_hex(0x2C3E50), 0);
    }
    
    // 消息预览
    lv_obj_t * preview_label = lv_label_create(text_container);
    lv_label_set_text(preview_label, msg->preview);
    lv_obj_set_style_text_color(preview_label, lv_color_hex(0x7F8C8D), 0);
    lv_obj_set_style_text_font(preview_label, &lv_font_montserrat_12, 0);
    
    // 右侧:时间和其他信息
    lv_obj_t * right_container = lv_obj_create(content);
    lv_obj_set_size(right_container, LV_PCT(20), LV_SIZE_CONTENT);
    lv_obj_set_style_bg_opa(right_container, LV_OPA_0, 0);
    lv_obj_set_style_border_width(right_container, 0, 0);
    lv_obj_set_flex_flow(right_container, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(right_container, LV_FLEX_ALIGN_END, 
                         LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER);
    
    // 时间
    lv_obj_t * time_label = lv_label_create(right_container);
    lv_label_set_text(time_label, msg->time);
    lv_obj_set_style_text_color(time_label, lv_color_hex(0x95A5A6), 0);
    lv_obj_set_style_text_font(time_label, &lv_font_montserrat_12, 0);
    
    // 未读标识
    if(msg->unread) {
        lv_obj_t * unread_indicator = lv_obj_create(right_container);
        lv_obj_set_size(unread_indicator, 8, 8);
        lv_obj_set_style_bg_color(unread_indicator, lv_color_hex(0xE74C3C), 0);
        lv_obj_set_style_radius(unread_indicator, 4, 0);
    }
    
    // 添加点击事件
    lv_obj_add_event_cb(list_btn, [](lv_event_t * e) {
        lv_obj_t * btn = lv_event_get_target(e);
        message_t * msg_data = (message_t *)lv_event_get_user_data(e);
        
        printf("打开消息来自: %s\n", msg_data->sender);
        printf("内容: %s\n", msg_data->preview);
        
        // 标记为已读
        if(msg_data->unread) {
            msg_data->unread = false;
            lv_obj_set_style_bg_opa(btn, LV_OPA_0, 0);
            
            // 移除未读标识
            lv_obj_t * right_container = lv_obj_get_child(btn, 0);
            right_container = lv_obj_get_child(right_container, 2); // 获取右侧容器
            if(lv_obj_get_child_cnt(right_container) > 1) {
                lv_obj_del(lv_obj_get_child(right_container, 1)); // 删除未读红点
            }
        }
    }, LV_EVENT_CLICKED, msg);
}

11.3 滚轮:优雅的选择器

滚轮控件提供了一种直观的方式来选择值,特别适合日期、时间等连续或离散值的选择。

11.3.1 滚轮的物理模型

滚轮的选择行为可以模拟物理滚动:

设滚轮角速度为 ω \omega ω,转动惯量为 I I I,阻尼系数为 β \beta β,则运动方程为:
I d ω d t + β ω = τ e x t I \frac{d\omega}{dt} + \beta \omega = \tau_{ext} Idtdω+βω=τext

在离散时间步长 Δ t \Delta t Δt 下:
ω t + 1 = ω t ⋅ e − β I Δ t \omega_{t+1} = \omega_t \cdot e^{-\frac{\beta}{I} \Delta t} ωt+1=ωt⋅e−IβΔt

11.3.2 创建日期时间选择器
cpp 复制代码
#include <lvgl.h>

/**
 * 创建日期时间选择器
 */
void create_datetime_picker() {
    lv_obj_t * parent = lv_scr_act();
    
    // 创建选择器容器
    lv_obj_t * container = lv_obj_create(parent);
    lv_obj_set_size(container, 380, 280);
    lv_obj_center(container);
    lv_obj_set_style_bg_color(container, lv_color_hex(0x2C3E50), 0);
    lv_obj_set_style_radius(container, 20, 0);
    lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_style_pad_all(container, 20, 0);
    
    // 标题
    lv_obj_t * title = lv_label_create(container);
    lv_label_set_text(title, "📅 选择日期和时间");
    lv_obj_set_style_text_color(title, lv_color_white(), 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_18, 0);
    lv_obj_set_align(title, LV_ALIGN_TOP_MID);
    
    // 滚轮容器
    lv_obj_t * wheel_container = lv_obj_create(container);
    lv_obj_set_size(wheel_container, LV_PCT(100), 180);
    lv_obj_set_style_bg_opa(wheel_container, LV_OPA_0, 0);
    lv_obj_set_style_border_width(wheel_container, 0, 0);
    lv_obj_set_flex_flow(wheel_container, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(wheel_container, LV_FLEX_ALIGN_SPACE_AROUND, 
                         LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    
    // 创建年份滚轮 (2020-2030)
    lv_obj_t * year_wheel = lv_roller_create(wheel_container);
    lv_obj_set_size(year_wheel, 80, 160);
    
    // 构建年份选项
    char years[128] = "";
    for(int year = 2020; year <= 2030; year++) {
        char year_str[8];
        snprintf(year_str, sizeof(year_str), "%d\n", year);
        strcat(years, year_str);
    }
    lv_roller_set_options(year_wheel, years, LV_ROLLER_MODE_INFINITE);
    
    // 创建月份滚轮
    lv_obj_t * month_wheel = lv_roller_create(wheel_container);
    lv_obj_set_size(month_wheel, 80, 160);
    lv_roller_set_options(month_wheel, "1月\n2月\n3月\n4月\n5月\n6月\n7月\n8月\n9月\n10月\n11月\n12月", 
                         LV_ROLLER_MODE_INFINITE);
    
    // 创建日期滚轮 (1-31)
    lv_obj_t * day_wheel = lv_roller_create(wheel_container);
    lv_obj_set_size(day_wheel, 60, 160);
    
    char days[256] = "";
    for(int day = 1; day <= 31; day++) {
        char day_str[8];
        snprintf(day_str, sizeof(day_str), "%d日\n", day);
        strcat(days, day_str);
    }
    lv_roller_set_options(day_wheel, days, LV_ROLLER_MODE_INFINITE);
    
    // 创建时间滚轮容器
    lv_obj_t * time_container = lv_obj_create(wheel_container);
    lv_obj_set_size(time_container, 100, 160);
    lv_obj_set_style_bg_opa(time_container, LV_OPA_0, 0);
    lv_obj_set_style_border_width(time_container, 0, 0);
    lv_obj_set_flex_flow(time_container, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(time_container, LV_FLEX_ALIGN_SPACE_AROUND, 
                         LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    
    // 小时滚轮
    lv_obj_t * hour_wheel = lv_roller_create(time_container);
    lv_obj_set_size(hour_wheel, 45, 160);
    
    char hours[256] = "";
    for(int hour = 0; hour < 24; hour++) {
        char hour_str[8];
        snprintf(hour_str, sizeof(hour_str), "%02d\n", hour);
        strcat(hours, hour_str);
    }
    lv_roller_set_options(hour_wheel, hours, LV_ROLLER_MODE_INFINITE);
    
    // 分隔符
    lv_obj_t * separator = lv_label_create(time_container);
    lv_label_set_text(separator, ":");
    lv_obj_set_style_text_color(separator, lv_color_white(), 0);
    lv_obj_set_style_text_font(separator, &lv_font_montserrat_16, 0);
    
    // 分钟滚轮
    lv_obj_t * minute_wheel = lv_roller_create(time_container);
    lv_obj_set_size(minute_wheel, 45, 160);
    
    char minutes[512] = "";
    for(int minute = 0; minute < 60; minute++) {
        char minute_str[8];
        snprintf(minute_str, sizeof(minute_str), "%02d\n", minute);
        strcat(minutes, minute_str);
    }
    lv_roller_set_options(minute_wheel, minutes, LV_ROLLER_MODE_INFINITE);
    
    // 设置样式
    lv_obj_t * wheels[] = {year_wheel, month_wheel, day_wheel, hour_wheel, minute_wheel};
    for(int i = 0; i < 5; i++) {
        lv_obj_set_style_text_color(wheels[i], lv_color_white(), LV_PART_MAIN);
        lv_obj_set_style_text_color(wheels[i], lv_color_hex(0x3498DB), LV_PART_SELECTED);
        lv_obj_set_style_text_font(wheels[i], &lv_font_montserrat_16, LV_PART_MAIN);
        lv_obj_set_style_text_font(wheels[i], &lv_font_montserrat_18, LV_PART_SELECTED);
        lv_obj_set_style_bg_color(wheels[i], lv_color_hex(0x34495E), LV_PART_MAIN);
        lv_obj_set_style_bg_color(wheels[i], lv_color_hex(0x2C3E50), LV_PART_SELECTED);
    }
    
    // 设置当前日期时间
    set_current_datetime(year_wheel, month_wheel, day_wheel, hour_wheel, minute_wheel);
    
    // 底部按钮
    lv_obj_t * btn_container = lv_obj_create(container);
    lv_obj_set_size(btn_container, LV_PCT(100), 50);
    lv_obj_set_style_bg_opa(btn_container, LV_OPA_0, 0);
    lv_obj_set_style_border_width(btn_container, 0, 0);
    lv_obj_set_flex_flow(btn_container, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(btn_container, LV_FLEX_ALIGN_SPACE_AROUND, 
                         LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    
    // 取消按钮
    lv_obj_t * cancel_btn = lv_btn_create(btn_container);
    lv_obj_set_size(cancel_btn, 100, 40);
    lv_obj_set_style_bg_color(cancel_btn, lv_color_hex(0x7F8C8D), 0);
    lv_obj_set_style_radius(cancel_btn, 10, 0);
    
    lv_obj_t * cancel_label = lv_label_create(cancel_btn);
    lv_label_set_text(cancel_label, "取消");
    lv_obj_center(cancel_label);
    
    // 确定按钮
    lv_obj_t * confirm_btn = lv_btn_create(btn_container);
    lv_obj_set_size(confirm_btn, 100, 40);
    lv_obj_set_style_bg_color(confirm_btn, lv_color_hex(0x3498DB), 0);
    lv_obj_set_style_radius(confirm_btn, 10, 0);
    
    lv_obj_t * confirm_label = lv_label_create(confirm_btn);
    lv_label_set_text(confirm_label, "确定");
    lv_obj_center(confirm_label);
    
    // 事件处理
    lv_obj_add_event_cb(confirm_btn, [](lv_event_t * e) {
        lv_obj_t * container = lv_obj_get_parent(lv_obj_get_parent((lv_obj_t *)e->user_data));
        lv_obj_t * wheel_container = lv_obj_get_child(container, 1);
        
        // 获取所有滚轮
        lv_obj_t * year_wheel = lv_obj_get_child(wheel_container, 0);
        lv_obj_t * month_wheel = lv_obj_get_child(wheel_container, 1);
        lv_obj_t * day_wheel = lv_obj_get_child(wheel_container, 2);
        lv_obj_t * time_container = lv_obj_get_child(wheel_container, 3);
        lv_obj_t * hour_wheel = lv_obj_get_child(time_container, 0);
        lv_obj_t * minute_wheel = lv_obj_get_child(time_container, 2);
        
        // 获取选中的值
        uint16_t year_sel = lv_roller_get_selected(year_wheel);
        uint16_t month_sel = lv_roller_get_selected(month_wheel);
        uint16_t day_sel = lv_roller_get_selected(day_wheel);
        uint16_t hour_sel = lv_roller_get_selected(hour_wheel);
        uint16_t minute_sel = lv_roller_get_selected(minute_wheel);
        
        printf("选择的日期时间: %d年%d月%d日 %02d:%02d\n", 
               2020 + year_sel, month_sel + 1, day_sel + 1, hour_sel, minute_sel);
        
        // 关闭选择器
        lv_obj_del(container);
    }, LV_EVENT_CLICKED, wheel_container);
    
    lv_obj_add_event_cb(cancel_btn, [](lv_event_t * e) {
        lv_obj_t * container = lv_obj_get_parent(lv_obj_get_parent((lv_obj_t *)e->user_data));
        lv_obj_del(container);
    }, LV_EVENT_CLICKED, wheel_container);
}

/**
 * 设置当前日期时间
 */
void set_current_datetime(lv_obj_t * year_wheel, lv_obj_t * month_wheel, 
                         lv_obj_t * day_wheel, lv_obj_t * hour_wheel, 
                         lv_obj_t * minute_wheel) {
    // 这里应该获取系统时间,这里使用固定值演示
    int current_year = 2024;
    int current_month = 5;    // 6月 (0-based)
    int current_day = 15;     // 16日 (0-based)
    int current_hour = 14;
    int current_minute = 30;
    
    lv_roller_set_selected(year_wheel, current_year - 2020, LV_ANIM_OFF);
    lv_roller_set_selected(month_wheel, current_month, LV_ANIM_OFF);
    lv_roller_set_selected(day_wheel, current_day, LV_ANIM_OFF);
    lv_roller_set_selected(hour_wheel, current_hour, LV_ANIM_OFF);
    lv_roller_set_selected(minute_wheel, current_minute, LV_ANIM_OFF);
}

第12章:图片与字体

12.1 图片:视觉元素的载体

图片是GUI设计中不可或缺的元素。LVGL支持多种图片格式和存储方式。

12.1.1 图片的数学表示

数字图片可以表示为像素矩阵:
I = [ p i j ] m × n I = [p_{ij}]_{m \times n} I=[pij]m×n

其中 p i j p_{ij} pij 表示位置 ( i , j ) (i, j) (i,j) 的像素值,对于RGB格式:
p i j = ( R i j , G i j , B i j ) p_{ij} = (R_{ij}, G_{ij}, B_{ij}) pij=(Rij,Gij,Bij)

图片缩放变换:
I s c a l e d ( x ′ , y ′ ) = I ( x ′ s x , y ′ s y ) I_{scaled}(x', y') = I\left(\frac{x'}{s_x}, \frac{y'}{s_y}\right) Iscaled(x′,y′)=I(sxx′,syy′)

其中 ( s x , s y ) (s_x, s_y) (sx,sy) 是缩放因子。

12.1.2 图片存储格式对比

LVGL支持多种图片存储格式:
图片存储格式 C数组 文件系统 外部存储器 编译时嵌入 快速访问 占用Flash 运行时加载 需要文件系统 灵活更新 专用解码器 大图片支持 复杂实现

12.1.3 实战:创建图片库浏览器
cpp 复制代码
#include <lvgl.h>

// 图片数据(实际项目中从文件加载)
// 这里使用内置符号模拟图片
static const char * image_symbols[] = {
    LV_SYMBOL_AUDIO,
    LV_SYMBOL_VIDEO,
    LV_SYMBOL_LIST,
    LV_SYMBOL_OK,
    LV_SYMBOL_CLOSE,
    LV_SYMBOL_POWER,
    LV_SYMBOL_SETTINGS,
    LV_SYMBOL_HOME,
    LV_SYMBOL_DOWNLOAD,
    LV_SYMBOL_DRIVE,
    LV_SYMBOL_REFRESH,
    LV_SYMBOL_MUTE,
    LV_SYMBOL_VOLUME_MID,
    LV_SYMBOL_BATTERY_3,
    LV_SYMBOL_BATTERY_2,
    LV_SYMBOL_BATTERY_1,
};

/**
 * 创建图片库浏览器
 */
void create_image_gallery() {
    lv_obj_t * parent = lv_scr_act();
    
    // 创建图片库容器
    lv_obj_t * gallery = lv_obj_create(parent);
    lv_obj_set_size(gallery, 400, 450);
    lv_obj_center(gallery);
    lv_obj_set_style_bg_color(gallery, lv_color_hex(0x34495E), 0);
    lv_obj_set_style_radius(gallery, 15, 0);
    lv_obj_set_flex_flow(gallery, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_style_pad_all(gallery, 15, 0);
    
    // 标题
    lv_obj_t * title = lv_label_create(gallery);
    lv_label_set_text(title, "🖼️ 图片库");
    lv_obj_set_style_text_color(title, lv_color_white(), 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
    
    // 创建网格容器
    lv_obj_t * grid = lv_obj_create(gallery);
    lv_obj_set_size(grid, LV_PCT(100), LV_PCT(85));
    lv_obj_set_style_bg_opa(grid, LV_OPA_0, 0);
    lv_obj_set_style_border_width(grid, 0, 0);
    lv_obj_set_flex_flow(grid, LV_FLEX_FLOW_ROW_WRAP);
    lv_obj_set_flex_align(grid, LV_FLEX_ALIGN_SPACE_EVENLY, 
                         LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
    lv_obj_set_style_pad_gap(grid, 10, 0);
    
    // 添加图片项
    for(int i = 0; i < sizeof(image_symbols) / sizeof(image_symbols[0]); i++) {
        create_image_item(grid, image_symbols[i], i);
    }
}

/**
 * 创建单个图片项
 */
void create_image_item(lv_obj_t * parent, const char * symbol, int index) {
    // 创建图片容器
    lv_obj_t * item = lv_obj_create(parent);
    lv_obj_set_size(item, 80, 100);
    lv_obj_set_style_bg_color(item, lv_color_hex(0x4A6572), 0);
    lv_obj_set_style_radius(item, 12, 0);
    lv_obj_set_style_shadow_width(item, 8, 0);
    lv_obj_set_style_shadow_color(item, lv_color_hex(0x2C3E50), 0);
    lv_obj_set_flex_flow(item, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_flex_align(item, LV_FLEX_ALIGN_SPACE_AROUND, 
                         LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    
    // 创建"图片"(使用符号字体)
    lv_obj_t * img = lv_label_create(item);
    lv_label_set_text(img, symbol);
    lv_obj_set_style_text_color(img, lv_color_white(), 0);
    lv_obj_set_style_text_font(img, &lv_font_montserrat_24, 0);
    
    // 图片标题
    char title[16];
    snprintf(title, sizeof(title), "图片 %d", index + 1);
    lv_obj_t * label = lv_label_create(item);
    lv_label_set_text(label, title);
    lv_obj_set_style_text_color(label, lv_color_white(), 0);
    lv_obj_set_style_text_font(label, &lv_font_montserrat_12, 0);
    
    // 添加点击事件
    lv_obj_add_event_cb(item, [](lv_event_t * e) {
        lv_obj_t * item = lv_event_get_target(e);
        lv_obj_t * label = lv_obj_get_child(item, 1);
        const char * title = lv_label_get_text(label);
        
        printf("点击了: %s\n", title);
        
        // 添加点击动画
        lv_anim_t a;
        lv_anim_init(&a);
        lv_anim_set_var(&a, item);
        lv_anim_set_values(&a, 255, 200, 255);
        lv_anim_set_time(&a, 300);
        lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_style_opa);
        lv_anim_start(&a);
    }, LV_EVENT_CLICKED, NULL);
}

/**
 * 从文件系统加载图片示例
 */
void load_image_from_filesystem() {
    // 注意:此功能需要配置LVGL文件系统支持
    
    lv_obj_t * img = lv_img_create(lv_scr_act());
    
    // 从文件加载图片
    lv_img_set_src(img, "S:/images/picture.jpg"); // "S:" 是文件系统驱动器字母
    
    // 设置图片属性
    lv_obj_set_size(img, 200, 150);
    lv_obj_center(img);
    
    // 设置图片样式
    lv_obj_set_style_radius(img, 10, 0);
    lv_obj_set_style_shadow_width(img, 15, 0);
    lv_obj_set_style_shadow_color(img, lv_color_hex(0x2C3E50), 0);
}

12.2 字体:文字渲染的艺术

字体是UI设计的灵魂,合适的字体能极大提升用户体验。

12.2.1 字体的数学基础

字体渲染涉及复杂的几何计算:

字符边界框:

每个字符 c c c 可以定义边界框 B ( c ) = ( x m i n , y m i n , x m a x , y m a x ) B(c) = (x_{min}, y_{min}, x_{max}, y_{max}) B(c)=(xmin,ymin,xmax,ymax)

基线对齐:

字符在基线上下分布:

  • 上升高度: a = y m a x − b a s e l i n e a = y_{max} - baseline a=ymax−baseline
  • 下降高度: d = b a s e l i n e − y m i n d = baseline - y_{min} d=baseline−ymin
  • 行高: h = a + d + l e a d i n g h = a + d + leading h=a+d+leading
12.2.2 使用外部字体

LVGL支持从外部文件加载字体,让我们集成中文字体:

cpp 复制代码
#include <lvgl.h>

// 字体声明(实际项目中从文件加载)
LV_FONT_DECLARE(lv_font_montserrat_16);
LV_FONT_DECLARE(lv_font_montserrat_24);
LV_FONT_DECLARE(lv_font_montserrat_32);

/**
 * 创建字体展示界面
 */
void create_font_showcase() {
    lv_obj_t * parent = lv_scr_act();
    
    // 创建字体展示容器
    lv_obj_t * container = lv_obj_create(parent);
    lv_obj_set_size(container, 380, 500);
    lv_obj_center(container);
    lv_obj_set_style_bg_color(container, lv_color_hex(0x2C3E50), 0);
    lv_obj_set_style_radius(container, 15, 0);
    lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_style_pad_all(container, 20, 0);
    lv_obj_set_style_pad_gap(container, 15, 0);
    
    // 标题
    lv_obj_t * title = lv_label_create(container);
    lv_label_set_text(title, "🔤 字体展示");
    lv_obj_set_style_text_color(title, lv_color_white(), 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_24, 0);
    
    // 字体示例区域
    create_font_example(container, "小字体 (16px)", &lv_font_montserrat_16, 
                       "The quick brown fox jumps over the lazy dog");
    
    create_font_example(container, "中等字体 (24px)", &lv_font_montserrat_24, 
                       "The quick brown fox jumps over the lazy dog");
    
    create_font_example(container, "大字体 (32px)", &lv_font_montserrat_32, 
                       "The quick brown fox");
    
    // 多语言支持示例
    create_multilingual_example(container);
}

/**
 * 创建字体示例
 */
void create_font_example(lv_obj_t * parent, const char * font_name, 
                        const lv_font_t * font, const char * sample_text) {
    // 字体示例容器
    lv_obj_t * example = lv_obj_create(parent);
    lv_obj_set_size(example, LV_PCT(100), LV_SIZE_CONTENT);
    lv_obj_set_style_bg_color(example, lv_color_hex(0x4A6572), 0);
    lv_obj_set_style_radius(example, 10, 0);
    lv_obj_set_flex_flow(example, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_style_pad_all(example, 15, 0);
    
    // 字体名称
    lv_obj_t * name_label = lv_label_create(example);
    lv_label_set_text(name_label, font_name);
    lv_obj_set_style_text_color(name_label, lv_color_hex(0xBDC3C7), 0);
    lv_obj_set_style_text_font(name_label, &lv_font_montserrat_14, 0);
    
    // 示例文本
    lv_obj_t * text_label = lv_label_create(example);
    lv_label_set_text(text_label, sample_text);
    lv_obj_set_style_text_color(text_label, lv_color_white(), 0);
    lv_obj_set_style_text_font(text_label, font, 0);
    lv_obj_set_style_text_align(text_label, LV_TEXT_ALIGN_LEFT, 0);
    lv_obj_set_width(text_label, LV_PCT(100));
}

/**
 * 创建多语言示例
 */
void create_multilingual_example(lv_obj_t * parent) {
    lv_obj_t * example = lv_obj_create(parent);
    lv_obj_set_size(example, LV_PCT(100), LV_SIZE_CONTENT);
    lv_obj_set_style_bg_color(example, lv_color_hex(0x4A6572), 0);
    lv_obj_set_style_radius(example, 10, 0);
    lv_obj_set_flex_flow(example, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_style_pad_all(example, 15, 0);
    
    // 标题
    lv_obj_t * title = lv_label_create(example);
    lv_label_set_text(title, "🌍 多语言支持");
    lv_obj_set_style_text_color(title, lv_color_hex(0xBDC3C7), 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_14, 0);
    
    // 多语言文本示例
    const char * multilingual_text = 
        "English: Hello World!\n"
        "中文: 你好,世界!\n"
        "Español: ¡Hola Mundo!\n"
        "Français: Bonjour le monde!\n"
        "日本語: こんにちは世界!";
    
    lv_obj_t * text_label = lv_label_create(example);
    lv_label_set_text(text_label, multilingual_text);
    lv_obj_set_style_text_color(text_label, lv_color_white(), 0);
    lv_obj_set_style_text_font(text_label, &lv_font_montserrat_16, 0);
    lv_obj_set_width(text_label, LV_PCT(100));
}

/**
 * 动态字体切换示例
 */
void create_font_switcher() {
    lv_obj_t * parent = lv_scr_act();
    
    lv_obj_t * container = lv_obj_create(parent);
    lv_obj_set_size(container, 300, 200);
    lv_obj_align(container, LV_ALIGN_BOTTOM_RIGHT, -20, -20);
    lv_obj_set_style_bg_color(container, lv_color_hex(0x34495E), 0);
    lv_obj_set_style_radius(container, 15, 0);
    lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_style_pad_all(container, 15, 0);
    
    // 标题
    lv_obj_t * title = lv_label_create(container);
    lv_label_set_text(title, "字体切换器");
    lv_obj_set_style_text_color(title, lv_color_white(), 0);
    
    // 动态文本
    lv_obj_t * dynamic_text = lv_label_create(container);
    lv_label_set_text(dynamic_text, "这段文字会改变字体!");
    lv_obj_set_style_text_color(dynamic_text, lv_color_white(), 0);
    lv_obj_set_style_text_font(dynamic_text, &lv_font_montserrat_16, 0);
    
    // 字体选择按钮
    lv_obj_t * btn_container = lv_obj_create(container);
    lv_obj_set_size(btn_container, LV_PCT(100), LV_SIZE_CONTENT);
    lv_obj_set_style_bg_opa(btn_container, LV_OPA_0, 0);
    lv_obj_set_flex_flow(btn_container, LV_FLEX_FLOW_ROW_WRAP);
    lv_obj_set_flex_align(btn_container, LV_FLEX_ALIGN_SPACE_EVENLY, 
                         LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    
    const char * font_names[] = {"小字体", "中字体", "大字体"};
    const lv_font_t * fonts[] = {
        &lv_font_montserrat_16,
        &lv_font_montserrat_24, 
        &lv_font_montserrat_32
    };
    
    for(int i = 0; i < 3; i++) {
        lv_obj_t * btn = lv_btn_create(btn_container);
        lv_obj_set_size(btn, 80, 35);
        lv_obj_set_style_bg_color(btn, lv_color_hex(0x3498DB), 0);
        
        lv_obj_t * label = lv_label_create(btn);
        lv_label_set_text(label, font_names[i]);
        lv_obj_center(label);
        
        // 字体切换事件
        lv_obj_add_event_cb(btn, [](lv_event_t * e) {
            int font_index = (int)e->user_data;
            lv_obj_t * text_label = (lv_obj_t *)lv_event_get_user_data(e);
            
            lv_obj_set_style_text_font(text_label, fonts[font_index], 0);
            printf("切换到字体: %s\n", font_names[font_index]);
        }, LV_EVENT_CLICKED, dynamic_text);
        
        // 存储字体索引
        lv_obj_set_user_data(btn, (void*)(intptr_t)i);
    }
}
12.2.3 自定义字体生成

在实际项目中,你可能需要生成自定义字体:

bash 复制代码
# 使用LVGL字体转换工具
python lv_font_conv.py \
  --font MyFont.ttf \
  --range 0x20-0x7F,0x4E00-0x9FFF \
  --size 16 \
  --format lvgl \
  --bpp 4 \
  --no-compress \
  -o my_font_16.c

然后在代码中使用:

cpp 复制代码
LV_FONT_DECLARE(my_font_16);

// 应用自定义字体
lv_obj_set_style_text_font(label, &my_font_16, 0);

本章总结与挑战

恭喜!你已经掌握了LVGL进阶数据展示和资源管理的核心技术。现在你能够:

  1. 创建专业的数据图表,实现实时数据可视化
  2. 构建功能完整的列表界面,展示结构化数据
  3. 实现优雅的选择器,提供优秀的用户体验
  4. 管理和使用图片资源,丰富界面视觉效果
  5. 集成和使用自定义字体,提升文本显示质量

综合挑战:

  1. 智能家居仪表盘升级:扩展之前的项目,添加:

    • 实时环境数据图表
    • 设备状态历史列表
    • 多语言支持界面
  2. 媒体播放器界面:创建一个完整的媒体播放器:

    • 专辑封面图片展示
    • 播放列表(使用高级列表)
    • 进度控制(使用改进的滑块和动画)
  3. 天气预报应用:实现一个天气预报应用:

    • 多日温度曲线图表
    • 天气图标字体
    • 位置选择器(使用滚轮)

在下一部分,我们将进入深入原理与移植优化,学习LVGL的内部机制和性能优化技术,让你从使用者成长为LVGL专家!


研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)


相关推荐
望获linux3 小时前
【实时Linux实战系列】Linux 内核的实时组调度(Real-Time Group Scheduling)
java·linux·服务器·前端·数据库·人工智能·深度学习
MC丶科3 小时前
【SpringBoot常见报错与解决方案】端口被占用?Spring Boot 修改端口号的 3 种方法,第 3 种 90% 的人不知道!
java·linux·spring boot
江公望4 小时前
ubuntu kylin(优麒麟)和标准ubuntu的区别浅谈
linux·服务器·ubuntu·kylin
Lynnxiaowen4 小时前
今天我们开始学习python语句和模块
linux·运维·开发语言·python·学习
生态笔记4 小时前
PPT宏代码
linux·服务器·powerpoint
mucheni4 小时前
迅为RK3588开发板Ubuntu 系统开发ubuntu终端密码登录
linux·运维·ubuntu
skywoodsky4 小时前
Ubuntu 24.04环境下的挂起转休眠
linux
小云数据库服务专线4 小时前
GaussDB 应用侧报Read timed out解决方法
linux·服务器·gaussdb
资源补给站5 小时前
服务器高效操作指南:Python 环境退出与 Linux 终端快捷键全解析
linux·服务器·python