「2024 DigiKey 创意大赛 」4、儿童互动数学学习设计
[复制链接]
本帖最后由 walker2048 于 2024-10-29 21:59 编辑
前言
幼小衔接阶段的数学计算通常包括基础的数数、认识数字、简单的加减法等。这个阶段的目标是让孩子对数学有一个初步的认识和兴趣,为小学阶段的学习打下基础。
例如:识别1到20的简单数字,能区分数字大小,例如4和7谁大。以及一些简单的加减法。
产品设计背景
在幼儿教育中,数学启蒙是关键一环,但传统教学方法往往难以激发孩子的兴趣。为了解决这一问题,开发一个专为幼小衔接儿童打造的互动式数学学习应用。该应用通过图形化界面和触摸屏操作,让孩子们在玩乐中学习加减法,提高他们的计算能力和逻辑思维。
功能设计:
设计一个适合小朋友的加减法学习应用,可以采用以下简化的步骤:
-
界面设计:使用明亮的颜色和卡通形象,以及大按钮和图标,确保界面友好且易于操作。
-
互动性:通过触摸屏操作,让孩子通过拖动或点击图形化的图示(如水果、动物等)来完成加减法题目。
-
倒计时:设置一个倒计时功能,增加游戏感,让孩子在有限时间内完成题目,提高专注力。
-
错题复习:自动记录孩子做错的题目,并提供复习模式,帮助孩子巩固易错点。
-
即时反馈:每当孩子答对题目,给予积极的反馈,如动画效果和表扬声音;答错时,提供正确答案和鼓励。
界面设计:
我们完全可以参考实体书的一些做法,例如使用一些常见的水果,通过添加删除线或者外框,组织一些简单的加减法算式。
题目界面设计
在网上可以找到一些免费的扁平化水果图片和删除线条,组织成类似以下的一个界面设计。
从上到下分别是进度条提示、题目图形化提示,题目和答案选择按钮。
出题函数逻辑设计:
-
随机选择操作类型:决定是生成加法题目还是减法题目。
-
随机生成数字A:随机选择一个数字A,确保它在1到9之间。
-
根据数字A生成另一个随机数:
- 对于加法,随机生成一个数字B,使得A和B的和小于或等于10。
- 对于减法,随机生成一个数字B,使得A和B的差大于0。
-
生成答案选项:为题目提供几个可能的答案选项,包括正确答案和几个错误的选项。
生成题目的具体代码
- #define MAX_QUESTION_NUM 10
- #define OPERATION_ADD 1
- #define OPERATION_SUB 0
-
-
- typedef struct {
- uint8_t numbers[2];
- uint16_t operation : 2;
- uint16_t solution : 14;
- } math_question_t;
-
-
- math_question_t math_questions[MAX_QUESTION_NUM];
-
- uint8_t score = 0;
-
-
-
-
-
-
-
- uint32_t get_random(uint32_t min, uint32_t max) {
-
- static bool seeded = false;
- if (!seeded) {
-
- srand(esp_random());
- seeded = true;
- }
-
- return (rand()) % (max - min + 1) + min;
- }
-
-
- void gen_question() {
- int8_t last_value = 0;
- for (int i = 0; i < MAX_QUESTION_NUM; i++) {
- int8_t operation = get_random(0, 1);
- math_questions[i].numbers[0] = get_random(0, 10);
-
- while (last_value == math_questions[i].numbers[0]) {
- math_questions[i].numbers[0] = get_random(0, 10);
- }
- last_value = math_questions[i].numbers[0];
-
-
- if (operation == OPERATION_ADD) {
- math_questions[i].operation = OPERATION_ADD;
-
- math_questions[i].numbers[1] =
- get_random(0, 10 - math_questions[i].numbers[0]);
- math_questions[i].solution = math_questions[i].numbers[0] +
- math_questions[i].numbers[1];
- } else {
- math_questions[i].operation = OPERATION_SUB;
-
- math_questions[i].numbers[1] =
- get_random(0, math_questions[i].numbers[0]);
- math_questions[i].solution = math_questions[i].numbers[0] -
- math_questions[i].numbers[1];
- }
- }
- }
编译程序验证代码
按原有设计编写lvgl的界面部分:
过程就不浪费时间说了,代码如下:
- LV_IMG_DECLARE(chengzi);
- LV_IMG_DECLARE(taozi);
- LV_IMG_DECLARE(mangguo);
- LV_IMG_DECLARE(lizi);
- LV_IMG_DECLARE(yangtao);
-
- const lv_img_dsc_t *src_array[5] = {&chengzi, &taozi, &mangguo, &lizi,
- &yangtao};
-
- LV_IMG_DECLARE(_fork);
- LV_IMG_DECLARE(quan);
-
-
- void create_and_position_image(lv_obj_t *img_ptr, const lv_img_dsc_t *src,
- int16_t x_offset, int16_t y_offset, int index) {
- img_ptr = lv_img_create(lv_scr_act());
- lv_img_set_src(img_ptr, src);
- lv_obj_align(img_ptr, LV_ALIGN_LEFT_MID, x_offset,
- y_offset + (index % 2 == 0
- ? 0
- : FRUIT_OFFSET));
- }
-
-
- void create_ans_button(lv_obj_t *btn, uint8_t value, uint8_t index) {
-
- lv_obj_set_style_bg_color(btn, lv_palette_main(LV_PALETTE_BLUE), 0);
-
- lv_obj_set_style_radius(btn, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
-
- lv_obj_set_pos(btn, 20 + 160 * index, 350);
-
- lv_obj_set_size(btn, 120, 100);
-
- lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL);
-
- lv_obj_add_style(btn, &f_style, 0);
-
- lv_obj_t *label = lv_label_create(btn);
-
- char buff[10] = "";
- sprintf(buff, "%d", value);
-
- lv_label_set_text(label, buff);
-
- lv_obj_center(label);
- }
-
-
- void create_images(math_question_t *que) {
- const lv_img_dsc_t *src = src_array[get_random(0, 4)];
- uint8_t total_images = que->solution;
- if (total_images > 10)
- return;
-
- lv_obj_t *img_array[total_images];
- lv_obj_t *opt_array[que->numbers[1]];
-
-
- int16_t y_offset = -140;
- int16_t x_offset = 60;
-
- uint8_t count = que->numbers[0];
- if (que->operation != OPERATION_ADD) {
- count = que->numbers[0] - que->numbers[1];
- }
-
-
- for (int i = 0; i < count; i++) {
- create_and_position_image(img_array[i], src, x_offset, y_offset, i);
- x_offset += (i % 2 == 1) ? FRUIT_OFFSET : 0;
- }
-
-
- for (int i = 0; i < que->numbers[1]; i++) {
- uint8_t img_index = que->numbers[0] + i;
-
- if (que->operation == OPERATION_ADD) {
- create_and_position_image(opt_array[i], &quan, x_offset, y_offset,
- i + count);
- }
- create_and_position_image(img_array[img_index], src, x_offset, y_offset,
- i + count);
-
- if (que->operation != OPERATION_ADD) {
- create_and_position_image(opt_array[i], &_fork, x_offset, y_offset,
- i + count);
- }
- x_offset +=
- ((i + count) % 2 == 1) ? FRUIT_OFFSET : 0;
- }
-
- lv_obj_t *textlabel = NULL;
-
- textlabel = lv_label_create(lv_scr_act());
-
- sprintf(question_str, "%d %s %d = ( )", que->numbers[0],
- que->operation ? "+" : "-", que->numbers[1]);
-
- lv_label_set_text(textlabel, question_str);
-
- lv_obj_add_style(textlabel, &f_style, LV_STATE_DEFAULT);
-
- lv_obj_set_style_text_color(lv_scr_act(), lv_color_hex(0x333333),
- LV_PART_MAIN);
-
- lv_obj_align(textlabel, LV_ALIGN_CENTER, 0, 40);
-
- uint8_t right_index = get_random(0, 2);
- uint8_t v = -1;
-
- for (int i = 0; i < 3; i++) {
- answer_btn[i] =
- lv_btn_create(lv_scr_act());
-
- if (right_index == i) {
- lv_obj_set_user_data(answer_btn[i], (void *)(intptr_t)que->solution);
- create_ans_button(answer_btn[i], que->solution,
- right_index);
- } else {
-
- if (que->solution == 0) {
- v = 2;
- }
- int32_t wrong_solution = 0;
- wrong_solution = que->solution + v;
-
- lv_obj_set_user_data(answer_btn[i], (void *)(intptr_t)wrong_solution);
- create_ans_button(answer_btn[i], wrong_solution, i);
- v = 1;
- }
- }
- }
-
- void app_main(void) {
-
- bsp_display_start();
-
-
- bsp_display_backlight_on();
-
- LV_FONT_DECLARE(OPPOSans);
- lv_style_set_text_font(&f_style, &OPPOSans);
-
-
- xquestionSemaphore = xSemaphoreCreateBinary();
-
- gen_question();
- for (int i = 0; i < MAX_QUESTION_NUM; i++) {
- curren_que = i;
- create_images(&math_questions[i]);
- xSemaphoreTake(xquestionSemaphore, portMAX_DELAY);
- vTaskDelay(pdMS_TO_TICKS(300));
- lv_obj_clean(lv_scr_act());
- }
- lv_obj_t *textlabel = NULL;
-
- textlabel = lv_label_create(lv_scr_act());
- sprintf(question_str, "太棒了\n%d 分", score);
- lv_label_set_text(textlabel, question_str);
- lv_obj_add_style(textlabel, &f_style, LV_STATE_DEFAULT);
- lv_obj_set_style_text_color(lv_scr_act(), lv_color_hex(0x333333),
- LV_PART_MAIN);
- lv_obj_align(textlabel, LV_ALIGN_CENTER, 0, -50);
- }
-
这样就把出题和答题部分的内容做好了。
-
- static SemaphoreHandle_t xquestionSemaphore = NULL;
-
- static lv_style_t f_style;
- lv_obj_t *answer_btn[3] = {0};
-
- uint8_t score = 0;
- uint8_t curren_que = 0;
-
-
- static void btn_event_cb(lv_event_t *e) {
-
- lv_event_code_t code = lv_event_get_code(e);
-
- intptr_t value = (intptr_t)lv_obj_get_user_data(lv_event_get_target(e));
-
- if (code == LV_EVENT_CLICKED) {
-
- if (curren_que < MAX_QUESTION_NUM &&
- value == math_questions[curren_que].solution) {
-
- strcpy(question_str, "答对了!");
- score += 10;
- } else {
-
- strcpy(question_str, "答错了。");
- }
-
- lv_obj_clean(lv_scr_act());
-
- lv_obj_t *textlabel = lv_label_create(lv_scr_act());
- lv_label_set_text(textlabel, question_str);
-
- lv_obj_add_style(textlabel, &f_style, LV_STATE_DEFAULT);
-
- lv_obj_set_style_text_color(lv_scr_act(), lv_color_hex(0x333333),
- LV_PART_MAIN);
-
- lv_obj_align(textlabel, LV_ALIGN_CENTER, 0, 40);
-
- xSemaphoreGive(xquestionSemaphore);
- }
- }
按钮使用统一的函数触发判断,根据传入的自定义数据判断按钮是否是正确的答案。
最终的效果如下图。
|