「Efficient Android Threading 笔记」- C1 Android Components and the Need for Multiprocessing Android RecyclerView Android Socket Programming Supporting Multiple Screens Make a Reusable UI in Android App Development 如何在 Android Studio 中包含 *.so library,并使用库中定义的方法? 使用 SpannableString 格式化字符串,实现前景色、下划线、超链接、图文混排等 如何使用 bound service 完成进程间通信? 创建自定义视图 Creating custom views 通过 Android Theme & Style 定制应用的样式 「译」Android ViewPropertyAnimator 介绍 Android Animation Interpolator - Android 动画插值器源码笔记 「译」Android Animation in Honeycomb by Chet Haase(Android 3.0系统中的动画机制) 从 Android Sample ApiDemos 中学习 android.animation API 的用法 如何学习 Android Animation? 如何实现 Android ListView「上拉加载更多」? 「译」向Big Nerd Ranch提问:为什么Fragment在Android App开发中非常重要? 分类整理我在 SegmentFault 上针对某些问题作的回答 Android Servcie 后台服务总结笔记 如何在Android设备旋转时暂存数据以保护当前的交互状态? Android Message Handler 消息处理机制总结笔记 如何获取FragmentTabHost中指定标签页的Fragment? Fragment子类必须包含一个public无参构造器 如何更新及替换ViewPager中的Fragment? 如何使用Android UI Fragment开发“列表-详情”界面? 一个Android音频文本同步的英文有声读物App的开发过程 「Android编程权威指南笔记」Android应用本地化 通过jfeinstein10/SlidingMenu实现Android侧滑菜单 为Ubuntu14.04部署Android App的Eclipse开发环境 「Android编程权威指南笔记」使用ListFragment显示列表 「Android编程权威指南笔记」SDK版本与兼容 「Android编程权威指南笔记」Android布局和组件 「Android编程权威指南笔记」UI Fragment 「Android编程权威指南笔记」Activity 第一次开发iOS App和Android的对比总结笔记 「App Training笔记」创建第一个应用 「App Training笔记」开发入门训练大纲 Android APP - 从远程FTP服务器下载文件到本地

基于二叉树的多层的液晶菜单界面设计

2011年09月21日

说明:这是一个菜单界面,运行在240*128点阵的液晶上,有上、下、确定、返回4个按键。最初于2011年9月21日发表于阿莫电子论坛,后整理到自己的博客。

功能分析

1.菜单需要完成的功能是:
实时显示环境A中的若干传感器值(传感器数量大于液晶可以显示的最大值时,还需要翻页功能),实时显示环境B中的若干传感器值,时间显示和设置,系统信息和帮助信息。

2.菜单界面的布局
目前分为3种:
(1) 主菜单停留在屏幕上,下级菜单被选中后显示;
(2) 下级菜单显示时,上级菜单消失;
(3) 图形文字结合显示;
由于240128点阵的液晶,行最多显示15个1616汉字,列最多显示8个汉字,液晶可显示8行15列,采用布局(1):
液晶界面基本布局图

3.从功能需求上看,这是一个简单的界面,配有简单的按键操作:
(1) “上”、“下”键在“环境A传感器值”,“环境B传感器值”,“时间设置”,“系统信息”4个菜单之间切换;
(2) “确定键”进入下级(子级)菜单,这里是显示信息,设置时间等;
(3) “返回键”返回上级(父级)菜单;
(4) “确定键”进入“时间设置”菜单后,“上下键”设置时间,“确定键”在年月日时分秒间切换,“返回键”完成时间设置;
基本功能完成后,进一步完善:
(5) 对于“系统信息”和“传感器值”这种类型的菜单,对应的子菜单只是显示某些信息,没有第二级需要继续选择的菜单项,故,当“当前菜单”为此类型时,不通过“确定键”直接显示相应的信息;
(6) 当“环境A”的传感器个数>8时,需要分2页显示,此时,按“确定键”翻页;

这份界面经历了3个版本,每次都有主要参考的代码资料。(说借鉴或着抄都可以,但也有我自己的构思,尤其是最后一个版本)

参考版本

第1个版本,参考的是《一个占用内存极少的菜单系统的实现》 http://goo.gl/hKBsK
作者公布了代码,但更令人钦佩的是很有耐心的写了份pdf教程,很有借鉴意义。但这个方案,菜单结构复杂增删不便,可维护性差,可移植性差。论坛上坛兄 a730598 《借阿莫的宝地开源个原创的LCD菜单内核》的帖子http://goo.gl/XhWao 分析的很细致。

第2个版本,同事给的,是一个多叉树的菜单设计。
关键在于菜单结构的设计,每个菜单都是一个节点,为了在父子兄菜单间切换,这个节点信息必须包含父、子、左兄弟、右兄弟节点链接,而且必须手动建立链接关系。但由于每个菜单的下级菜单个数不同,难以用统一的数据结构描述这种关系。
多叉树菜单与二叉树菜单

例如:a,b是兄弟菜单;a的左右兄弟都是b,b的左右兄弟都是a,但若a,b中间增加了c,变成了3兄弟a,c,b,上述关系必须更改:a的左兄弟是b,右兄弟是C,……,这些链接关系必须手动建立,一旦有增删,必须更新。
建立上述关系后,菜单间的切换很统一很方便:“确定键”当前菜单项->子菜单,若无子,则“当前菜单->功能函数”;“返回键”当前菜单->父菜单;“上”当前菜单->左兄弟;“下”当前菜单->右兄弟。但缺点也很明显。

第3个版本,参考 彭良清《基于节点编号的通用树状菜单设计方法与实现》http://goo.gl/hKBsK
这是我在下面会要重点说的,作者给出的方法是真好呀。我先copy下作者论文中的一些话:
界面设计的流程要把按键和菜单显示作为一个整体设计协同处理;
树形菜单以2层为限,选择级数超过3级,操作不便;
菜单布局,同一系统中应保持界面的一致性;菜单子项使用频率大,置前;
一个菜单(menu)是包含多个固定条目内容(菜单项menu item),并同时在屏幕上显示或消失的矩形窗口。

我的实现

菜单类型不同,对按键的响应不同。比如,某些菜单“确定键”进入子菜单,其它则执行某种功能(保存啦,退出啦,时间设置啦,输入参数啦,显示某些信息啦)。尤其是当按键少的时候,按键必须具有复用功能,问题是,怎么让程序知道此时此地“确定”是进入子菜单,它时它地“确定”是执行某个特殊的功能函数。(当按键多的时候,不通过菜单选择,通过按键,直接执行某种功能函数,这个本程序没有涉及到)

为了设计一个结构、流程清晰的界面,首先分析菜单的行为,对菜单进行分类:
1st. 选择型菜单:当然就是菜单间的切换了
2nd. 功能型菜单:
i. 功能1型,执行某个操作
ii. 功能2型,允许用户输入信息或设置(如时间设置)
iii. 功能3型,单向不受控的显示窗口\菜单(系统信息、帮助信息等)
然后建立菜单信息的结构:

typedef struct menu
{
    uint8 ID;               /*menu的唯一标识码*/
    MenuInfo item[10];      /*结构体数组,本级menu包含的menuitem信息:显示位置,名称*/
    uint8 items_num;        /*本级menu的menuitem个数*/
    /*和其它menu的关系,由程序完成,初始化NULL即可*/
    struct menu *father,    /*父辈menu*/
    *son,                   /*长子menu*/
    *obrother,              /*兄menu elderbrother*/
    *ybrother;              /*弟menu youngerbrother*/
    uint8 item_sn_cur;      /*当前menuitem序号(serial number),0~(itemnum-1)*/
    uint8 menu_type;        /*menu类型*/
} Menu;

虽然这个二叉树的菜单设计,看上去和第2个版本多叉树类似,仍需要建立菜单节点之间的父子兄弟的关系。但这只是貌似相同,这2个有很大的区别。上一张图形象地显示了2者间的区别,更详细的见论文《基于节点编号的通用树状菜单设计方法与实现》中的图6,7。
多叉树菜单间的切换在第2个版本中有讲,它需要手动建立所有父子兄弟间的链接关系。
二叉树也需要建立这种关系,但这是通过一种ID编号规则(详见论文《基于节点》3(6))建立的,程序根据确立的ID编号规则初始化节点链接关系。显而易见的好处是,当我们需要增删节点或者改变节点位置时,只需要重新分配这个惟一的ID号即可。程序根据当前节点(菜单)的ID号,就可以寻找 到它的父子兄弟菜单,具体方法是,所有的菜单存在数组内的,通过for循环匹配ID,匹配即为当前菜单,当前菜单->父菜单,就找到了它的父菜单。

因此,本菜单的关键是,根据我们自己定义的ID编号规则,建立这样一个二叉树的菜单结构;然后,根据菜单类型,确立对按键响应的整个程序流程。
《基于节点》提出的程序流程是:

While (1) {
    键值 =  获取按键值的函数( );
    当前菜单 = 菜单选择函数(当前菜单,键值);
    if (当前菜单 == 选择型菜单)
        continue;
    switch (当前菜单->ID) {      /* 功能型菜单代码调用 */
    case ID_1:
        功能函数 ID_1();
        break;    
    case ID_2:
    ……
    }
}

菜单选择函数(当前菜单, 键值) {
    switch (键值) {
    case     上:当前菜单 = 当前菜单->兄; break;
    case     下:当前菜单 = 当前菜单->弟; break;
    case 确定:当前菜单 = 当前菜单->父; break;
    case 返回:当前菜单 = 当前菜单->子; break;
    }
    return  (当前菜单);
}

清晰,明了,易维护,易移植。

但是,我需要设置时间,我只有4个按键,因此,上下键必须复用,上述流程会有冲突。
我的改进是:

While (1) {
    键值 =  获取按键值的函数( );
    当前菜单 = 菜单界面执行函数(当前菜单,键值);
    键值 = 空; /* 避免无键按下时,程序重复执行上次操作 */
}

菜单界面执行函数(当前菜单,键值) {
    switch (菜单类型) {
    case 选择型菜单:    
        当前菜单 = 菜单选择函数(当前菜单,键值);
        break;    /* 此函数和上一个流程的函数一样 */
    case 功能1(执行某个操作):
        当前菜单 = 功能型菜单代码调用;
        break;
    case 功能2(允许用户输入信息或设置(如时间设置)):
        当前菜单 = 对应的功能函数;
        break;
    case 功能3(单向不受控的显示窗口\菜单, 系统信息、帮助信息等):
        当前菜单 = 对应的功能函数;
        break;        
    } 
    return (当前菜单);
}

整个框架搭好之后,需要考虑细节,这些细节很容易在上述框架中实现:

  1. 屏幕刷新,有键按下时刷新界面,避免屏幕的闪烁;数据的更新通过不断写入的方式而非清屏刷新;
  2. 当前菜单的维护,分析所有可能改变当前菜单的函数,通过函数参数传递和返回值的方式,尽量避免使用全局变量;
  3. 通过ID编号规则初始化节点间的链接关系,必然有垃圾链接,比如,主菜单无父节点,最底层菜单无子节点,如何避免在这些地方跑飞;
  4. 对于多级菜单,父子菜单切换时高亮或反显的维护问题;
  5. 如何实现一.3.(5)(6)的扩展功能。

总结

上述提到的所有资料我放在了我的共享空间 http://goo.gl/4N1EQ
包括

  1. 主题为“多层菜单界面设计”的小论文和资料
  2. 我用到的液晶驱动芯片t6963C的论文和资料
  3. 这个液晶多层菜单界面的代码

这是对我着手写界面(或者说开始编程生涯)5个月的一点儿总结,当然也不是每天都在写,还有其它好多的工作呢。我还是有点进步哒。
不过硬伤是明显的,编程经验少、编写代码少、数据结构和算法知识缺陷、操作系统知识匮乏到简直不懂……
这个界面只能用在简单的系统上,对于复杂的系统则难以为继。
我真心希望有人能阅读我写的代码,即使风格很糟糕,然后说点什么,这对我很有帮助。
我的共享空间里可以下载到源码。
由于深刻和直接地体会到阅读没有注释的代码的痛苦和难耐,我在自己的代码里写了挺多注释,并且努力保持注释和代码的一致性;同样的原因,我也没有直接贴上代码了事,而是分析了我写代码的思路、程序的流程,留给自己,也留给需要的人;真心认为程序的流程远比代码本身更重要。


by weiyi.li http://li2.me SH FengXian 2011年9月21日2时0分


知识共享许可协议
li2的博客WeiYi.Li 创作,采用 知识共享 署名-非商业性使用 4.0 国际 许可协议进行许可。
© 2011-2017. All rights reserved by WeiYi.Li. Powerd by Jekyll & LinAnYa's Theme