本文主要是介绍韦东山数码相框任务需求分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 前言
- 需求界面
- 抽象流程
- 界面分解
- Page 结构体
- static void (*Display)();
- static void (*GetInputEvent)();
- 第一界面:主界面
- 第二界面:选择界面
- 第三界面:浏览界面
- 第四界面:连播界面
- 第五界面:设置界面
- 第六界面:间隔界面
- 其他过程抽象
- 显示接口
- 读取图片/图标
- GetPicFmts(Icon 图片信息):
- 输入接口
- 调试输出接口
- 优化接口
- 显示内存管理
- 后续想法
- 阅读界面
前言
只是简单分析了下各个结构体的由来,意淫编程
整体框架参考了韦东山数码相框修正调整,另类介绍程序代码结构
需求界面
整个需求如下图
抽象流程
理解为是各个界面,通过不同的按钮相关切换,所以将界面抽象出来
‘
总共分解成六个小界面,针对每个界面,这时可以想到的操作有:
- 显示界面内容 ==》显示数据准备
- 响应界面上的触摸事件 =》按键位置判断为哪个按钮
针对界面,则有管理问题,是数组,还是链表?
这里所能想到的对应结构体基本结构应为:
Page {char *name; // 页面名字 void (*Display)(); // 页面的运行函数int (*GetInputEvent)(); // 获得输入数据的函数 Page *ptNext; // 链表管理
}
界面分解
每个界面又分为类似如下几个图标:
针对这些图标,想到的可能属性有:
- 位置
- 使用哪里图片
对应结构体:
Icon{iTopLeftX // 左上角坐标 iTopLeftYiBotRightX // 右下角坐标 iBotRightYstrIconName // 图片位置
}
而图标数目这种明显跟界面强相关,需要保存在界面 Page 结构体中
Page {char *name; // 页面名字 void (*Display)(); // 页面的运行函数int (*GetInputEvent)(); // 获得输入数据的函数 Page *ptNext; // 链表管理Icon[] // 所有包含的图标
}
再梳理流程:
Main -> Browser -> manual ||---> Auto||--->Setting --> interval
这个时候需要一个更高层次的来调用组织 Page,暂时叫 App 结构体吧,但是抽象了发现,切换到哪个界面跟只有界面自己知道,这个逻辑最简单,高内聚,每个程序管好自己内部就行了,切出去时自己指定切到谁。这里发现就需要在 Page 内部做逻辑切换。为此在 Page 内部增加一个 Run() 函数
Page {char *name; // 页面名字 void (*Run)(); // 页面运行函数 int (*GetInputEvent)(); // 获得输入数据的函数 void (*Display)(); // 页面显示函数Page *ptNext; // 链表管理Icon[] // 所有包含的图标
}
梳理下 Run() 流程大致如下:
Run()Display(); // 显示主界面for(;;)GetInputEvent(); // 获取哪个按钮被点击switch()PageSelect("目的界面")->Run();
这里发现 Display() 跟 GetInputEvent() 似乎不会被其他模块调用,属于 Private 内部就好了
Page {char *name; // 页面名字 void (*Run)(); // 页面运行函数static int (*GetInputEvent)(); // 获得输入数据的函数 static void (*Display)(); // 页面显示函数Page *ptNext; // 链表管理Icon[] // 所有包含的图标
}
到这里,大的切换框架已经可以实现了,整体程序暂时框架为:
main()PageInit(); // 注册所有界面到链表 PageList 中管理PageSelect("Main")->Run(); // 选择主界面运行
剩下再细究先研究 Page 结构体对应函数功能, 然后再针对每个界面不同重载的特殊处理。
Page 结构体
Page {char *name; // 页面名字 void (*Run)(); // 页面运行函数static int (*GetInputEvent)(); // 获得输入数据的函数 static void (*Display)(); // 页面显示函数Page *ptNext; // 链表管理Icon[] // 所有包含的图标
}
static void (*Display)();
比如像这种怎么显示到界面上
Display() 流程:1. 获取一块内存2. 填充内存2.1 读取图片2.2 加载到显示内存指定位置3. 刷新到显示中
static void (*GetInputEvent)();
static int (*GetInputEvent)(); // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标
第一界面:主界面
都是些界面跳转,最简单的一页,流程不需要特别改
Page {char *name; // 页面名字 void (*Run)(); // 页面运行函数Display(); // 显示主界面for(;;)GetInputEvent(); // 获取哪个按钮被点击switch()case: 浏览按钮PageSelect("选择界面")->Run(); case: 连播按钮PageSelect("连播界面")->Run(); case: 设置按钮PageSelect("设置界面")->Run(); static int (*GetInputEvent)(); // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标static void (*Display)(); // 页面显示函数1. 获取一块内存2. 填充内存2.1 读取图片2.2 加载到显示内存指定位置3. 刷新到显示中Page *ptNext; // 链表管理Icon[] // 所有包含的图标
}
第二界面:选择界面
涉及到目录切换与图标显示,而且实际图标只有四个,剩下 9 个图标是可变的
Page {char *name; // 页面名字 void (*Run)(); // 页面运行函数Display(“当前根目录”,1); // 显示主界面1 打开目录2 遍历目录,保存 文件名+是否文件, 目录名+是否目录到缓存中for(;;)GetInputEvent(); // 获取哪个按钮被点击判断是这 13 个按钮哪个被按到,是文件还是目录文件或目录的话,则保存名称及类型switch()case: 向上:当前目录缩短一段1 打开目录2 遍历目录,保存 文件名+是否文件, 目录名+是否目录到缓存中Display(“当前目录”,缓存[index 第几个 9]);case: 选择:目录:当前目录增加一段1 打开目录2 遍历目录,保存 文件名+是否文件, 目录名+是否目录到缓存中Display(“当前目录”,缓存[index 第几个 9]);文件:目前仅支持图片当前路径增加一段PageSelect("浏览界面")->Run("当前图片路径"); # Run() 需要增加参数case: 上一页:index++Display(“当前目录”,缓存[index 第几个 9]);case: 下一页 index--Display(“当前目录”,缓存[index 第几个 9]);static int (*GetInputEvent)(); // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标static void (*Display)("目录路径",缓存[index 第几个 9]); // 页面显示函数1. 获取一块内存2. 填充内存2.1 根据 缓存[index 第几个 9] 更新下九个图标的名称2.1 读取图标2.2 刷新到显示中3. 刷新到显示中Page *ptNext; // 链表管理Icon[] // 所有包含的图标
}
第三界面:浏览界面
Page {
char *name; // 页面名字
void (*Run)("当前图片路径"); // 页面运行函数Display(); // 显示主界面1 当前图片路径2 遍历目录,保存 文件名 缓存中for(;;)GetInputEvent(); // 获取哪个按钮被点击switch()case: 返回退出当前 Run()case: 缩小缩放标志--Display();case: 放大缩放标志++Display();case: 上一张 更新当前图片路径为上一张图片Display();case: 下一张 更新当前图片路径为下一张图片Display();case: 连播 PageSelect("连播界面")->Run("当前图片路径");static int (*GetInputEvent)(); // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标static void (*Display)(); // 页面显示函数1. 获取一块内存2. 填充内存2.1 读取图标2.2 读取当前图片路径 + 缩放标志2.2 加载到显示内存指定位置3. 刷新到显示中Page *ptNext; // 链表管理Icon[] // 所有包含的图标
}
第四界面:连播界面
Page {
char *name; // 页面名字
void (*Run)("当前图片路径"); // 页面运行函数Display(); // 显示主界面1 当前图片路径2 遍历目录,保存 文件名 缓存中for(;;)GetInputEvent(); // 获取哪个按钮被点击switch()case: 返回退出当前 Run()延时指定时间间隔更新当前图片路径为下一张 Display()static int (*GetInputEvent)(); // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标static void (*Display)(); // 页面显示函数1. 获取一块内存2. 填充内存2.1 读取图标2.2 读取当前图片路径 + 缩放标志2.3 加载到显示内存指定位置3. 刷新到显示中Page *ptNext; // 链表管理Icon[] // 所有包含的图标
}
第五界面:设置界面
Page {
char *name; // 页面名字
void (*Run)("当前图片路径"); // 页面运行函数Display(); // 显示主界面当前图片路径 = 根目录for(;;)GetInputEvent(); // 获取哪个按钮被点击switch()case: 选择目录PageSelect("选择界面")->Run("当前图片路径");case: 设置间隔PageSelect("连播界面")->Run("当前图片路径");static int (*GetInputEvent)(); // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标static void (*Display)(); // 页面显示函数1. 获取一块内存2. 填充内存2.1 读取图标2.2 加载到显示内存指定位置3. 刷新到显示中Page *ptNext; // 链表管理Icon[] // 所有包含的图标
}
第六界面:间隔界面
这里发现需要在添加一个时间间隔全局变量
Page {char *name; // 页面名字 void (*Run)("当前图片路径"); // 页面运行函数Display(); // 显示主界面当前图片路径 = 根目录for(;;)GetInputEvent(); // 获取哪个按钮被点击switch()case: 增加时间间隔++Display()case: 减小时间间隔--Display()static int (*GetInputEvent)(); // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标static void (*Display)(); // 页面显示函数1. 获取一块内存2. 填充内存 2.1 根据 时间间隔 选择中间图标用哪张图2.2 读取图标2.3 加载到显示内存指定位置3. 刷新到显示中Page *ptNext; // 链表管理Icon[] // 所有包含的图标
}
到这里,感觉程序整体框架已经搭完了,下面思考下各个页面中使用到的模块抽象
其他过程抽象
显示接口
显示在哪里使用呢?在 Display() 流程有使用
Display() 流程:1. 获取一块内存2. 填充内存2.1 读取图片2.2 加载到显示内存指定位置3. 刷新到显示中
针对 Linux 的话,显示就是将显存映射为一块内存,然后往内存里面填东西就能显示
// 显示接口
Display{char *name; // 显示接口名称void (*Init)(); // 显示接口初始化流程,比如打开,映射显示设备void (*Flush)(“包含显示的缓存”); // 刷新显示
}
这些都是根据上面流程想到的比较直接的接口定义
读取图片/图标
-
图片图标使用位置
Display() 流程:1. 获取一块内存2. 填充内存2.1 读取图片2.2 加载到显示内存指定位置3. 刷新到显示中
-
图片,图标那肯定有不同的格式的,所以会需要不同的格式解析模块
-
不同图片格式那也是需要管理的,链表吧,就用 g_PicFmtsList
PicFmt{char *name; // 图片解析模块名称,比如 Bmp, Pngvoid (*Read)(显示缓存,Icon 图片信息); // 读取图片到显示缓存中指定位置ptNext // 下一个模块}
这里还有个问题,程序还需要判断这是什么图片类型后,才好调用具体的 PicFmts 格式处理的,所以 PicFmts 还需要有个判断本模块是否支持的功能
PicFmt{char *name; // 图片解析模块名称void (*Read)(显示缓存,Icon 图片信息); // 读取图片到显示缓存中指定位置1. 针对 Icon 所有图片,打开图片2. 解析图片内容放进显示缓存指定位置void (*isSupport)(Icon 图片信息)1. 打开图片2. 判断格式是否是本模块支持的ptNext // 下一个模块
}
这样针对每个传入的 Icon 图片,需要先遍历链表 g_PicFmtsList 通过 isSupport() 找到对应模块,再调用读入函数
GetPicFmts(Icon 图片信息):
需要先遍历链表 g_PicFmtsList 通过 isSupport() 找到对应模块,再调用读入函数
1. 遍历 g_PicFmtsList 链表,调用 PicFmt->isSupport() 判断是否有模块支持
2. 返回支持的 PicFmt 结构体
所以 Display 流程会更新类似如下:
Display() 流程:1. 获取一块内存2. 填充内存2.1 遍历当前页面 Icon[]2.2 GetPicFmts(Icon):获取对应处理格式模块2.3 PicFmt->Read(显示缓存,Icon ):读入显存中3. 刷新到显示中
输入接口
输入接口使用位置:
static int (*GetInputEvent)(); // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标
在第一步获取报点处使用,输入接口相对于六个界面是独立存在的,所以可以用个独立的循环线程存在
Input{char *name; // 输入模块名称void (*Init)() // 输入设备初始化1. 打开设备,创建线程轮询等待事件上报2. 在线程中,有数据上报就唤醒 GetInputData() 上的睡眠进程void (*GetInputData)() // 获取输入数据等待输入事件并上报
}
当然感觉输入设备不应该只有触摸屏,想以后也可以响应按键,响应网络,响应终端等输入设备,所以这个结构体还需要再改改
需要用链表管理
Input{char *name; // 输入模块名称void (*Init)() // 输入设备初始化1. 打开设备,创建线程轮询等待事件上报2. 在线程中,有数据上报就唤醒 GetInputData() 上的睡眠进程void (*GetInputData)() // 获取输入数据检查是否有事件上报,有则上报,无则睡眠ptNext // 下一个模块
}
也需要修改界面的 GetInputEvent() 函数,以及 Run() 函数因为每个界面响应的按键方式可能不一定
static int (*GetInputEvent)(); // 获得输入数据的函数 1. 获取报点2. 判断点是否有在某个图标位置内部,有返回数组下标3. 获取按键void (*Run)("当前图片路径"); // 页面运行函数Display(); // 显示主界面当前图片路径 = 根目录for(;;)GetInputEvent(); // 获取哪个按钮被点击switch()case: 选择目录PageSelect("选择界面")->Run("当前图片路径");case: 设置间隔PageSelect("连播界面")->Run("当前图片路径");################添加按键等响应处理
调试输出接口
调试输出接口嘛,就是支持各种 log 输出,可以从 文件输出、标准输出、网络输出、串口输出, 屏上输出等等
Debug{char *name; // 输出名称void (*Init)() // 调试模块初始化 1. 打开输出模块2. 创建线程,等待 DebugPrint() 函数输入,再转发输出模块输出void (*DebugPrint)(格式化字符串) // 输出函数唤醒线程,让其通过特定模块输出 logptNext; // 下一个模块
}
优化接口
显示内存管理
显示过程中发现还有获取一块内存的操作,像这种也可以使用缓冲池管理
Display() 流程:1. 获取一块内存2. 填充内存2.1 遍历当前页面 Icon[]2.2 GetPicFmts(Icon):获取对应处理格式模块2.3 PicFmt->Read(显示缓存,Icon ):读入显存中3. 刷新到显示中
可用如下结构体管理:
VideoMem{int count;void (*Init)(内存大小,内存块数) // 建池void (*Get)() // 从缓冲池获取数据void (*Set)() // 释放到缓冲池中
}
后续想法
阅读界面
比如想在浏览界面,支持文本阅读,那要怎么实现呢?
阅读界面逻辑如下:
Page {char *name; // 页面名字 void (*Run)("当前文件路径"); // 页面运行函数Display(); // 显示主界面for(;;)GetInputEvent(); // 获取哪个按钮被点击switch()case: 上一页根据屏大小,及字体大小,更新上一页开始文件中位置Display()case: 下一页根据屏大小,及字体大小,更新下一页开始文件中位置Display()static int (*GetInputEvent)(); // 获得输入数据的函数 1. 获取报点2. 判断点位置,左半屏上翻,右半屏下翻static void (*Display)(); // 页面显示函数1. 获取一块内存2. 填充内存2.1 读取文件2.2 加载到显示内存指定位置3. 刷新到显示中Page *ptNext; // 链表管理Icon[] // 所有包含的图标
}
根据之前学习过程可知道
怎样在 LCD 上显示文件:1. 根据文件获得字符编码 {ASCII, GBK【这一行是大陆用户默认的】UTF-8,UTF16LE,UTF16BE,}2. 根据编码从字体文件中得到 字体数据【包括点阵图】{ASCII字体数组,HZK16,GBK字体文件,freetype,}3. 把 点阵图 在 LCD 上显示出来
字库:主要是将不同编码的字符,转换成对应的点阵图,所以
一个字库可以支持多种编码方式
freetype: 支持 ASCII/GBK/UTF-8/UTF16LE/UTF16BE
HZK16: 支持 ASCII/GBK
ASCII: 支持 ASCII /UTF-16LE/UTF-16BE/UTF-8 # ? 有这么多?参考程序提取
针对浏览界面读取 txt 场景,处理流程大致如下:
static void (*Display)(); // 页面显示函数1. 获取一块内存2. 填充内存2.1 读取文件,判断文本字符编码2.2 根据字符字符获取让对应模块处理,获取其点阵图2.3 将获取的点阵图显示在显示缓存合适位置3. 刷新到显示中
故可以针对上面的字符编码,字库进行抽象如下:
Encoding{char *name; // 编码名称 void (*isSupport)(文件路径) // 是否是某字符文件1. 打开文件,读取到内存中2. 判断是否支持此种编码文件void (*DisplayTxt)(显示缓存,文件路径,文件内部位置,字体大小)1. 打开文件,2. 根据屏幕尺寸及字体大小,更新可从指定位置读取多少字符3. 将读入的字符通过支持此编码的字库的 GetBmpData() 获得位图4. 将获得的位图填充到显示缓存中ptNext // 链表管理 ptNextFont // 会有个绑定支持字符操作,放到这个链表中
}Fonts{char *name; // 字库名称void (*Init)() // 字库初始化void (*GetBmpData)(字符,返回位图) // 根据字符,返回对应的位图ptNext // 指向下一个字库}
更新显示场景逻辑:
static void (*Display)(); // 页面显示函数1. 获取一块内存2. 填充内存2.1 遍历编码表, 调用 isSupport() 判断是否有支持的2.2 对应编码的 DisplayTxt() 显示字符3. 刷新到显示中
这篇关于韦东山数码相框任务需求分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!