探讨页面按键管理的各种方式

 以一个简单的播控页面(机顶盒上的播控页面)为例来探讨页面的按键管理方式(没有鼠标,不考虑组合键)。

 

 

简单的播控页面:
这个播控页面由2个子页面组成:
1)播放页面
2)信息面板MINIEPG

这2个页面的按键责任如下:
1)播放页面:
·up:调出MINIEPG,在其中显示下一个频道的信息;
·down:调出MINIEPG,在其中显示上一个频道的信息;
·CH+:切台,播放下一个频道,并显示当前频道当前节目单的MINIEPG
·CH-:切台,播放上一个频道,并显示当前频道当前节目单的MINIEPG
·OK:显示miniepg
·vol+/-:音量加减

2)MINIEPG:
·up:在其中显示下一个频道的信息;
·down:在其中显示上一个频道的信息;
·CH+:切台,播放下一个频道,并展示当前播放的频道和节目单的信息
·CH-:切台,播放上一个频道,并展示当前播放的频道和节目单的信息
·OK:隐藏MINIEPG,焦点到了“播放页面”
·vol+/-:音量加减

//====页面层次关系
可以有两种看法:
·MINIEPG是“播放页面”的子页面;
·MINIEPG和“播放页面”的同等级页面;


//====按键责任分配
情景介绍完了,下面就是对每个页面实际的按键分工进行分析了:

1)“MINIEPG”上的up/down、CH+/-、vol+/-其实是“播放页面”在实际起作用,也就是说,这些按键是传递给“播放页面”来执行的;
//同样的道理:“SUBMENU”上的up、down、CH+/-、left/right、vol+/-也是最终传递给“播放页面”来执行的;


当然,也可以换一个角度来分析。
up/down、CH+/-实际是“MINIEPG”页面在起作用:“播放页面”实际只是调出了“MINIEPG”,然后由“MINIEPG”来真正执行;

2)“MINIEPG”上的OK等按键却将“播放页面”的OK给屏蔽了;
同理如“SUBMENU”对“MINIEPG”;

3)在执行CH+/-的时候,“播放页面”需要播放新频道,同一时刻,“MINIEPG”需要展示新频道的信息;


//====按键责任分配总结
到目前为止就可以总结出以下几种页面按键责任分配的情况:
1、焦点在某个页面上,并响应某个按键;
2、两个页面要同时响应某个按键;
3、两个页面都有需要对某个按键有响应,但是同一时刻却只能在当前页面有响应;
4、某一个按键只有某个页面有响应,但是当前焦点在别的页面上;
又有两种情况:
1)响应了这个按键之后,焦点会移动到这个响应焦点的页面
2)响应了这个按键之后,焦点不变

这其中,比较困难的是后面3种情况;


//====按键管理方式
我在实际中总结出以下几种按键管理的方式:
1、状态模式;----按键独占方式
2、责任链模式;----按键捕获和透传方式
3、观察者模式;----按键广播方式


//====状态模式按键管理方式的代码演示

页面层次关系:MINIEPG和“播放页面”的同等级页面;

 

可以解决所有的情况;

Java代码 
  1. interface org.ideamarker.as2.keyhandle.example.state.IKeyCodeHandle {  
  2.       
  3.     function onUp():Void;  
  4.       
  5.     function onDown():Void;  
  6.       
  7.     function onChanUp():Void;  
  8.       
  9.     function onChanDown():Void;  
  10.       
  11.     function onOk():Void;  
  12.       
  13.     function onVolUp():Void;  
  14.       
  15. }  

 

As2代码 
  1. class org.ideamarker.as2.keyhandle.example.state.LiveContext {  
  2.       
  3.     private var m_currFocus:IKeyCodeHandle;  
  4.       
  5.     private var m_livePlayUI:LivePlayUI;  
  6.       
  7.     private var m_liveMiniEpgUI:LiveMiniEpgUI;  
  8.   
  9.     public function getCurrFocus():IKeyCodeHandle {  
  10.         return m_currFocus;  
  11.     }  
  12.       
  13.     public function getLivePlayUI():IKeyCodeHandle {  
  14.         return m_livePlayUI;  
  15.     }  
  16.       
  17.     public function getLiveMiniEpgUI():IKeyCodeHandle {  
  18.         return m_liveMiniEpgUI;  
  19.     }  
  20.   
  21.     public function setCurrFocus(_focusUI:IKeyCodeHandle):Void {  
  22.         m_currFocus = _focusUI;  
  23.     }  
  24.       
  25.     public function onOk():Void {  
  26.         m_currFocus.onOk();  
  27.     }  
  28.       
  29.     public function onUp():Void {  
  30.         m_currFocus.onUp();  
  31.     }  
  32.       
  33.     public function onDown():Void {  
  34.         m_currFocus.onDown();  
  35.     }  
  36.       
  37.     public function onChanUp():Void {  
  38.         //  TODO:“‘按键责任分配总结’中的第1种情况”;  
  39.         m_livePlayUI.onChanUp();  
  40.         m_liveMiniEpgUI.onChanUp();  
  41.           
  42.         // 焦点移动到了liveMiniEpgUI上;  
  43.         m_currFocus = m_liveMiniEpgUI;  
  44.     }  
  45.   
  46.     public function onChanDown():Void {  
  47.         //  TODO:“‘按键责任分配总结’中的第1种情况”;  
  48.         m_livePlayUI.onChanDown();  
  49.         m_liveMiniEpgUI.onChanDown();  
  50.           
  51.         // 焦点移动到了liveMiniEpgUI上;  
  52.         m_currFocus = m_liveMiniEpgUI;  
  53.     }  
  54.   
  55.     public function onVolUp():Void {  
  56.         m_currFocus.onVolUp();  
  57.           
  58.         // 或者直接在此处调用m_livePlayUI的onVolUp();  
  59.         // m_livePlayUI.onVolUp();  
  60.     }  
  61. }  

 class org.ideamarker.as2.keyhandle.example.state.LivePlayUI implements IKeyCodeHandle {

As2代码 
  1.     private var m_context:LiveContext;  
  2.       
  3.     private var m_currPlayChannel:String;  
  4.       
  5.     public function LivePlayUI(_context:LiveContext) {  
  6.         m_context = _context;  
  7.     }  
  8.   
  9.     public function onUp():Void {  
  10.         // 调出MINIEPG,在其中显示下一个频道的信息;  
  11.           
  12.         // TODO:“‘按键责任分配总结’的第4种的第1种情况”,并且当前焦点被移动到LiveMiniEpgUI上  
  13.         m_context.setCurrFocus(m_context.getLiveMiniEpgUI());  
  14.         m_context.getCurrFocus().onUp();  
  15.     }  
  16.       
  17.     public function onDown():Void {  
  18.         // 调出MINIEPG,在其中显示上一个频道的信息;  
  19.           
  20.         //  TODO:“‘按键责任分配总结’的第4种的第1种情况”,并且当前焦点被移动到LiveMiniEpgUI上  
  21.         m_context.setCurrFocus(m_context.getLiveMiniEpgUI());  
  22.         m_context.getCurrFocus().onDown();  
  23.     }  
  24.       
  25.     public function onChanUp():Void {  
  26.         // 切台,播放下一个频道;  
  27.         m_currPlayChannel = "play the next channel";  
  28.     }  
  29.       
  30.     public function onChanDown():Void {  
  31.         // 切台,播放上一个频道;  
  32.         m_currPlayChannel = "play the pervious channel";  
  33.     }  
  34.       
  35.     public function onOk():Void {  
  36.         // 显示miniepg  
  37.           
  38.         // TODO:“‘按键责任分配总结’中的第4种的第1种情况”;  
  39.         m_context.setCurrFocus(m_context.getLiveMiniEpgUI());  
  40.     }  
  41.       
  42.     public function onInfo():Void {  
  43.         // 显示miniepg, 同onOk()  
  44.           
  45.         // TODO:“‘按键责任分配总结’中的第4种的第1种情况”;  
  46.         m_context.setCurrFocus(m_context.getLiveMiniEpgUI());  
  47.     }  
  48.       
  49.     public function onVolUp():Void {  
  50.         // 音量加  
  51.     }  
  52. }  

 class org.ideamarker.as2.keyhandle.example.state.LiveMiniEpgUI implements IKeyCodeHandle {

As2代码 
  1.     private var m_context:LiveContext;  
  2.       
  3.     private var m_info:String;  
  4.       
  5.     public function LiveMiniEpgUI(_context:LiveContext) {  
  6.         m_context = _context;  
  7.     }  
  8.   
  9.     public function onUp():Void {  
  10.         // 在其中显示下一个频道的信息;  
  11.         m_info = "the next channel info";  
  12.     }  
  13.       
  14.     public function onDown():Void {  
  15.         // 在其中显示上一个频道的信息;  
  16.         m_info = "the previous channel info";  
  17.     }  
  18.       
  19.     public function onChanUp():Void {  
  20.         // 展示下一个播放的频道和节目单的信息;  
  21.         m_info = "the next channel info";  
  22.     }  
  23.       
  24.     public function onChanDown():Void {  
  25.         // 展示上一个播放的频道和节目单的信息;  
  26.         m_info = "the next channel info";  
  27.     }  
  28.       
  29.     public function onOk():Void {  
  30.         // ·OK/Info/back:隐藏MINIEPG,焦点到了“播放页面”  
  31.           
  32.         //  TODO:“‘按键责任分配总结’中的第2种情况”;  
  33.         m_context.setCurrFocus(m_context.getLivePlayUI());  
  34.     }  
  35.       
  36.     public function onVolUp():Void {  
  37.         // 音量加,按键责任要传递给LivePlayUI  
  38.           
  39.         // TODO:“‘按键责任分配总结’中的第4种的第2种情况”;  
  40.         // 其实在这种情况,完全无需要抽象出接口方法,只需要直接在LiveContext中调用LivePlayUI的onVolUp()方法即可  
  41.         m_context.getLivePlayUI().onVolUp();  
  42.     }  
  43. }  

 优点:
·状态模式可以解决“按键责任分配总结”中的所有情况。但是入门有门槛,没有明显的固定的套路,在情况较为复杂的情况必须要coder有一定的分析能力;
缺点:
·所有具有对相同按键有响应的方法都需要抽象成相同的接口名称;
·如果可以将状态模式再进一步抽象,总结出一个工具类来,那就更加好了----这是我一直在思考的问题;

 

 

 


//====责任链模式按键管理方式的代码演示
页面层次关系:MINIEPG是“播放页面”的子页面;

 

As2代码 
  1. interface org.ideamarker.as2.keyhandle.example.cor.IKeyControlItem {  
  2.   
  3.     /**  
  4.      * 该受控单元的按键处理函数;  
  5.      */  
  6.     public function keyHandle(_keyCode:String):Void;  
  7.       
  8.     /**  
  9.      * 获取该受控单元需要处理的按键列表;  
  10.      */  
  11.     public function getInterestingKeys():Array;  
  12.       
  13. }  

 class org.ideamarker.as2.keyhandle.example.cor.KeyControlManager {

As2代码 
  1.     private var itemList:Array;  
  2.   
  3.     public function KeyControlManager() {  
  4.         itemList = new Array();  
  5.     }  
  6.   
  7.     /**  
  8.      * 向按键状态管理器里面添加一个受控单元;  
  9.      * 如果一个受控单元被重复添加,则其优先级以最后一次添加的为准;  
  10.      * @param item 要添加的按键受控单元;  
  11.      */  
  12.     public function regItem(_item:IKeyControlItem):Void {  
  13.         unRegItem(_item);  
  14.         itemList.unshift(_item);  
  15.     }  
  16.   
  17.     /**  
  18.      * 从按键状态管理器里面移除一个受控单元;  
  19.      * @param item 要移除的按键受控单元;  
  20.      */  
  21.     public function unRegItem(_item:IKeyControlItem):Void {  
  22.         for(var i:Number = 0;i < itemList.length;i++) {  
  23.             if(itemList[i] == _item) {  
  24.                 itemList.splice(i, 1);  
  25.                   
  26.                 return ;  
  27.             }  
  28.         }  
  29.     }  
  30.   
  31.     /**  
  32.      * 按键按下的事件;  
  33.      */  
  34.     public function onKeyCodeHandle(_keyCode:String):Void {  
  35.         for(var i:Number = 0;i < itemList.length;i++) {  
  36.               
  37.             var item:IKeyControlItem = IKeyControlItem(itemList[i]);  
  38.             var interestKeys:Array = item.getInterestingKeys();  
  39.             for(var j:Number = 0;j < interestKeys.length;j++) {  
  40.                   
  41.                 if(_keyCode == interestKeys[j]) {  
  42.                     item.keyHandle(_keyCode);  
  43.                     return ; // 因为这个return,让这个例子是责任链模式,而不是观察者模式  
  44.                 }  
  45.             }  
  46.         }  
  47.     }  
  48. }  
 

 

As2代码 
  1. class org.ideamarker.as2.keyhandle.example.cor.LivePlayUI implements IKeyControlItem {  
  2.     // 焦点管理工具  
  3.     private var m_keyControlManager:KeyControlManager;  
  4.     // keyCode组的集合  
  5.     private var m_keyCodeGroup:Array;  
  6.     // 当前key组的index  
  7.     private var m_nowGroupIndex:Number;  
  8.     // 子页面 miniepg  
  9.     private var m_liveMiniEpgUI:LiveMiniEpgUI;  
  10.   
  11.     // private var m_currChannel:Object;  
  12.   
  13.     public function LivePlayUI() {  
  14.         m_keyControlManager = new KeyControlManager();  
  15.           
  16.         this.regKeyItem();  
  17.         this.regKeyCodeGroup();  
  18.           
  19.         m_liveMiniEpgUI.setKeyControlManager(m_keyControlManager);  
  20.     }  
  21.           
  22.     /**  
  23.      * 注册本页面  
  24.      */  
  25.     private function regKeyItem():Void {  
  26.         m_keyControlManager.regItem(this);  
  27.     }  
  28.       
  29.     /**  
  30.      * 注册本页面感兴趣的键值  
  31.      */  
  32.     private function regKeyCodeGroup():Void {  
  33.         // 注册键值  
  34.         m_keyCodeGroup.push([Const.UP, Const.DOWN, Const.CHAN_UP, Const.CHAN_DOWN  
  35.             , Const.OK, Const.BACK, Const.VOL_UP, Const.VOL_DOWN]);  
  36.           
  37.         // 当此例子改成观察者模式的时候放开注释  
  38.         // m_keyCodeGroup.push([Const.CHAN_UP, Const.CHAN_DOWN, Const.VOL_UP, Const.VOL_DOWN]);  
  39.           
  40.         m_nowGroupIndex = 0;  
  41.     }  
  42.       
  43.   
  44.     public function keyHandle(_keyCode:String):Void {  
  45.         switch (_keyCode) {  
  46.             case (Const.UP):  
  47.                 this.onUp();  
  48.             break;  
  49.             case (Const.DOWN):  
  50.                 this.onDown();  
  51.             break;  
  52.             case (Const.CHAN_UP):  
  53.                 this.onChanUp();  
  54.             break;  
  55.             case (Const.CHAN_DOWN):  
  56.                 this.onChanDown();  
  57.             break;  
  58.             case (Const.OK):  
  59.                 this.onOk();  
  60.             break;  
  61.             case (Const.VOL_UP):  
  62.                 this.onVolUp();  
  63.             break;  
  64.         }  
  65.     }  
  66.       
  67.     public function getInterestingKeys():Array {  
  68.         return m_keyCodeGroup[m_nowGroupIndex];  
  69.     }  
  70.       
  71.     private function onUp():Void {  
  72.         // 1.获取下一个频道信息,在miniepg上展示  
  73.         // 2.展示miniepg  
  74.         m_liveMiniEpgUI.show("the next channel info");  
  75.     }  
  76.       
  77.     private function onDown():Void {  
  78.         // 1.获取上一个频道信息,在miniepg上展示  
  79.         // 2.展示miniepg  
  80.         m_liveMiniEpgUI.show("the previous channel info");  
  81.     }  
  82.       
  83.     private function onChanUp():Void {  
  84.         // 1.播放下一个频道  
  85.         // 2.展示miniepg  
  86.         m_liveMiniEpgUI.show("the next channel info");  
  87.     }  
  88.       
  89.     private function onChanDown():Void {  
  90.         // 1.播放上一个频道  
  91.         // 2.展示miniepg  
  92.         m_liveMiniEpgUI.show("the previous channel info");  
  93.     }  
  94.       
  95.     private function onOk():Void {  
  96.         // 1.展示miniepg  
  97.         m_liveMiniEpgUI.show("the current channel info");  
  98.     }  
  99.       
  100.     private function onVolUp():Void {  
  101.         //   
  102.     }  
  103. }  
 

 

As2代码 
  1. class org.ideamarker.as2.keyhandle.example.cor.LiveMiniEpgUI implements IKeyControlItem {  
  2.   
  3.     private var m_keyControlManager:KeyControlManager;  
  4.     // keyCode组的集合  
  5.     private var m_keyCodeGroup:Array;  
  6.     // 当前key组的index  
  7.     private var m_nowGroupIndex:Number;  
  8.   
  9.     public function LiveMiniEpgUI() {  
  10.     }  
  11.       
  12.     /**  
  13.      * 注册本页面  
  14.      */  
  15.     private function regKeyItem():Void {  
  16.         m_keyControlManager.regItem(this);  
  17.     }  
  18.       
  19.     /**  
  20.      * 注册本页面感兴趣的键值  
  21.      */  
  22.     private function regKeyCodeGroup():Void {  
  23.         // 注册键值  
  24.         m_keyCodeGroup.push([Const.UP, Const.DOWN, Const.OK]);  
  25.           
  26.         // 当此例子改成观察者模式的时候放开注释  
  27.         // m_keyCodeGroup.push([Const.UP, Const.DOWN]);  
  28.           
  29.         m_nowGroupIndex = 0;  
  30.     }  
  31.       
  32.     public function setKeyControlManager(_keyControlManager:KeyControlManager):Void {  
  33.         m_keyControlManager = _keyControlManager;  
  34.           
  35.         regKeyItem();  
  36.         regKeyCodeGroup();  
  37.     }  
  38.       
  39.     public function show(_chanInfo:String):Void {  
  40.           
  41.     }  
  42.       
  43.     public function keyHandle(_keyCode:String):Void {  
  44.         switch(_keyCode) {  
  45.             case (Const.UP):  
  46.                 this.onUp();  
  47.             break;  
  48.             case (Const.DOWN):  
  49.                 this.onDown();  
  50.             break;  
  51.             case (Const.OK):  
  52.                 this.onOk();  
  53.             break;  
  54.         }  
  55.     }  
  56.       
  57.     public function getInterestingKeys():Array {  
  58.         return m_keyCodeGroup[m_nowGroupIndex];  
  59.     }  
  60.       
  61.     private function onUp():Void {  
  62.         // do sth.  
  63.     }  
  64.       
  65.     private function onDown():Void {  
  66.         // do sth.  
  67.     }  
  68.       
  69.     private function onOk():Void {  
  70.         // 隐藏miniepg  
  71.         m_keyControlManager.unRegItem(this);  
  72.     }  
  73. }  
 


优点:
·抽象出了一个工具类,方便使用,易于理解;
缺点:
·无法实现“按键责任分配总结”中的第2种情况;


 


//====观察者模式按键管理方式的代码演示
因为多了一个return,上个例子就是责任链模式;
如果将这个return去掉,上个例子就可以变成观察者模式;但是,需要做点修改:
1)需要添加更多的可能情况的key组;
2)m_nowGroupIndex需要随着页面的跳转而改变;

 


疑惑:
1、总是觉得现在这样的状态模式还是不够抽象,能够抽象出像责任链模式例子中的KeyControlManager类就更加好了;
2、总是觉得这个责任链模式例子中的代码,不是那么和谐,尤其是keyHandle()方法中的内容(虽然可以使用map来关联keyCode和Function);


有没有更加好的改进方法?

 

 

 

声明:

1、本文属于原创文章,历时一周思考得出的结论;

2、由于工作原因,本文代码由Actionscript2实现,语法和Java很类似,请不要对语言的优劣性有任何微词;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值