以一个简单的播控页面(机顶盒上的播控页面)为例来探讨页面的按键管理方式(没有鼠标,不考虑组合键)。
简单的播控页面:
这个播控页面由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和“播放页面”的同等级页面;
可以解决所有的情况;
interface org.ideamarker.as2.keyhandle.example.state.IKeyCodeHandle {
function onUp():Void;
function onDown():Void;
function onChanUp():Void;
function onChanDown():Void;
function onOk():Void;
function onVolUp():Void;
}
class org.ideamarker.as2.keyhandle.example.state.LiveContext {
private var m_currFocus:IKeyCodeHandle;
private var m_livePlayUI:LivePlayUI;
private var m_liveMiniEpgUI:LiveMiniEpgUI;
public function getCurrFocus():IKeyCodeHandle {
return m_currFocus;
}
public function getLivePlayUI():IKeyCodeHandle {
return m_livePlayUI;
}
public function getLiveMiniEpgUI():IKeyCodeHandle {
return m_liveMiniEpgUI;
}
public function setCurrFocus(_focusUI:IKeyCodeHandle):Void {
m_currFocus = _focusUI;
}
public function onOk():Void {
m_currFocus.onOk();
}
public function onUp():Void {
m_currFocus.onUp();
}
public function onDown():Void {
m_currFocus.onDown();
}
public function onChanUp():Void {
// TODO:“‘按键责任分配总结’中的第1种情况”;
m_livePlayUI.onChanUp();
m_liveMiniEpgUI.onChanUp();
// 焦点移动到了liveMiniEpgUI上;
m_currFocus = m_liveMiniEpgUI;
}
public function onChanDown():Void {
// TODO:“‘按键责任分配总结’中的第1种情况”;
m_livePlayUI.onChanDown();
m_liveMiniEpgUI.onChanDown();
// 焦点移动到了liveMiniEpgUI上;
m_currFocus = m_liveMiniEpgUI;
}
public function onVolUp():Void {
m_currFocus.onVolUp();
// 或者直接在此处调用m_livePlayUI的onVolUp();
// m_livePlayUI.onVolUp();
}
}
class org.ideamarker.as2.keyhandle.example.state.LivePlayUI implements IKeyCodeHandle {
private var m_context:LiveContext;
private var m_currPlayChannel:String;
public function LivePlayUI(_context:LiveContext) {
m_context = _context;
}
public function onUp():Void {
// 调出MINIEPG,在其中显示下一个频道的信息;
// TODO:“‘按键责任分配总结’的第4种的第1种情况”,并且当前焦点被移动到LiveMiniEpgUI上
m_context.setCurrFocus(m_context.getLiveMiniEpgUI());
m_context.getCurrFocus().onUp();
}
public function onDown():Void {
// 调出MINIEPG,在其中显示上一个频道的信息;
// TODO:“‘按键责任分配总结’的第4种的第1种情况”,并且当前焦点被移动到LiveMiniEpgUI上
m_context.setCurrFocus(m_context.getLiveMiniEpgUI());
m_context.getCurrFocus().onDown();
}
public function onChanUp():Void {
// 切台,播放下一个频道;
m_currPlayChannel = "play the next channel";
}
public function onChanDown():Void {
// 切台,播放上一个频道;
m_currPlayChannel = "play the pervious channel";
}
public function onOk():Void {
// 显示miniepg
// TODO:“‘按键责任分配总结’中的第4种的第1种情况”;
m_context.setCurrFocus(m_context.getLiveMiniEpgUI());
}
public function onInfo():Void {
// 显示miniepg, 同onOk()
// TODO:“‘按键责任分配总结’中的第4种的第1种情况”;
m_context.setCurrFocus(m_context.getLiveMiniEpgUI());
}
public function onVolUp():Void {
// 音量加
}
}
class org.ideamarker.as2.keyhandle.example.state.LiveMiniEpgUI implements IKeyCodeHandle {
private var m_context:LiveContext;
private var m_info:String;
public function LiveMiniEpgUI(_context:LiveContext) {
m_context = _context;
}
public function onUp():Void {
// 在其中显示下一个频道的信息;
m_info = "the next channel info";
}
public function onDown():Void {
// 在其中显示上一个频道的信息;
m_info = "the previous channel info";
}
public function onChanUp():Void {
// 展示下一个播放的频道和节目单的信息;
m_info = "the next channel info";
}
public function onChanDown():Void {
// 展示上一个播放的频道和节目单的信息;
m_info = "the next channel info";
}
public function onOk():Void {
// ·OK/Info/back:隐藏MINIEPG,焦点到了“播放页面”
// TODO:“‘按键责任分配总结’中的第2种情况”;
m_context.setCurrFocus(m_context.getLivePlayUI());
}
public function onVolUp():Void {
// 音量加,按键责任要传递给LivePlayUI
// TODO:“‘按键责任分配总结’中的第4种的第2种情况”;
// 其实在这种情况,完全无需要抽象出接口方法,只需要直接在LiveContext中调用LivePlayUI的onVolUp()方法即可
m_context.getLivePlayUI().onVolUp();
}
}
优点:
·状态模式可以解决“按键责任分配总结”中的所有情况。但是入门有门槛,没有明显的固定的套路,在情况较为复杂的情况必须要coder有一定的分析能力;
缺点:
·所有具有对相同按键有响应的方法都需要抽象成相同的接口名称;
·如果可以将状态模式再进一步抽象,总结出一个工具类来,那就更加好了----这是我一直在思考的问题;
//====责任链模式按键管理方式的代码演示
页面层次关系:MINIEPG是“播放页面”的子页面;
interface org.ideamarker.as2.keyhandle.example.cor.IKeyControlItem {
/**
* 该受控单元的按键处理函数;
*/
public function keyHandle(_keyCode:String):Void;
/**
* 获取该受控单元需要处理的按键列表;
*/
public function getInterestingKeys():Array;
}
class org.ideamarker.as2.keyhandle.example.cor.KeyControlManager {
private var itemList:Array;
public function KeyControlManager() {
itemList = new Array();
}
/**
* 向按键状态管理器里面添加一个受控单元;
* 如果一个受控单元被重复添加,则其优先级以最后一次添加的为准;
* @param item 要添加的按键受控单元;
*/
public function regItem(_item:IKeyControlItem):Void {
unRegItem(_item);
itemList.unshift(_item);
}
/**
* 从按键状态管理器里面移除一个受控单元;
* @param item 要移除的按键受控单元;
*/
public function unRegItem(_item:IKeyControlItem):Void {
for(var i:Number = 0;i < itemList.length;i++) {
if(itemList[i] == _item) {
itemList.splice(i, 1);
return ;
}
}
}
/**
* 按键按下的事件;
*/
public function onKeyCodeHandle(_keyCode:String):Void {
for(var i:Number = 0;i < itemList.length;i++) {
var item:IKeyControlItem = IKeyControlItem(itemList[i]);
var interestKeys:Array = item.getInterestingKeys();
for(var j:Number = 0;j < interestKeys.length;j++) {
if(_keyCode == interestKeys[j]) {
item.keyHandle(_keyCode);
return ; // 因为这个return,让这个例子是责任链模式,而不是观察者模式
}
}
}
}
}
class org.ideamarker.as2.keyhandle.example.cor.LivePlayUI implements IKeyControlItem {
// 焦点管理工具
private var m_keyControlManager:KeyControlManager;
// keyCode组的集合
private var m_keyCodeGroup:Array;
// 当前key组的index
private var m_nowGroupIndex:Number;
// 子页面 miniepg
private var m_liveMiniEpgUI:LiveMiniEpgUI;
// private var m_currChannel:Object;
public function LivePlayUI() {
m_keyControlManager = new KeyControlManager();
this.regKeyItem();
this.regKeyCodeGroup();
m_liveMiniEpgUI.setKeyControlManager(m_keyControlManager);
}
/**
* 注册本页面
*/
private function regKeyItem():Void {
m_keyControlManager.regItem(this);
}
/**
* 注册本页面感兴趣的键值
*/
private function regKeyCodeGroup():Void {
// 注册键值
m_keyCodeGroup.push([Const.UP, Const.DOWN, Const.CHAN_UP, Const.CHAN_DOWN
, Const.OK, Const.BACK, Const.VOL_UP, Const.VOL_DOWN]);
// 当此例子改成观察者模式的时候放开注释
// m_keyCodeGroup.push([Const.CHAN_UP, Const.CHAN_DOWN, Const.VOL_UP, Const.VOL_DOWN]);
m_nowGroupIndex = 0;
}
public function keyHandle(_keyCode:String):Void {
switch (_keyCode) {
case (Const.UP):
this.onUp();
break;
case (Const.DOWN):
this.onDown();
break;
case (Const.CHAN_UP):
this.onChanUp();
break;
case (Const.CHAN_DOWN):
this.onChanDown();
break;
case (Const.OK):
this.onOk();
break;
case (Const.VOL_UP):
this.onVolUp();
break;
}
}
public function getInterestingKeys():Array {
return m_keyCodeGroup[m_nowGroupIndex];
}
private function onUp():Void {
// 1.获取下一个频道信息,在miniepg上展示
// 2.展示miniepg
m_liveMiniEpgUI.show("the next channel info");
}
private function onDown():Void {
// 1.获取上一个频道信息,在miniepg上展示
// 2.展示miniepg
m_liveMiniEpgUI.show("the previous channel info");
}
private function onChanUp():Void {
// 1.播放下一个频道
// 2.展示miniepg
m_liveMiniEpgUI.show("the next channel info");
}
private function onChanDown():Void {
// 1.播放上一个频道
// 2.展示miniepg
m_liveMiniEpgUI.show("the previous channel info");
}
private function onOk():Void {
// 1.展示miniepg
m_liveMiniEpgUI.show("the current channel info");
}
private function onVolUp():Void {
//
}
}
class org.ideamarker.as2.keyhandle.example.cor.LiveMiniEpgUI implements IKeyControlItem {
private var m_keyControlManager:KeyControlManager;
// keyCode组的集合
private var m_keyCodeGroup:Array;
// 当前key组的index
private var m_nowGroupIndex:Number;
public function LiveMiniEpgUI() {
}
/**
* 注册本页面
*/
private function regKeyItem():Void {
m_keyControlManager.regItem(this);
}
/**
* 注册本页面感兴趣的键值
*/
private function regKeyCodeGroup():Void {
// 注册键值
m_keyCodeGroup.push([Const.UP, Const.DOWN, Const.OK]);
// 当此例子改成观察者模式的时候放开注释
// m_keyCodeGroup.push([Const.UP, Const.DOWN]);
m_nowGroupIndex = 0;
}
public function setKeyControlManager(_keyControlManager:KeyControlManager):Void {
m_keyControlManager = _keyControlManager;
regKeyItem();
regKeyCodeGroup();
}
public function show(_chanInfo:String):Void {
}
public function keyHandle(_keyCode:String):Void {
switch(_keyCode) {
case (Const.UP):
this.onUp();
break;
case (Const.DOWN):
this.onDown();
break;
case (Const.OK):
this.onOk();
break;
}
}
public function getInterestingKeys():Array {
return m_keyCodeGroup[m_nowGroupIndex];
}
private function onUp():Void {
// do sth.
}
private function onDown():Void {
// do sth.
}
private function onOk():Void {
// 隐藏miniepg
m_keyControlManager.unRegItem(this);
}
}
优点:
·抽象出了一个工具类,方便使用,易于理解;
缺点:
·无法实现“按键责任分配总结”中的第2种情况;
//====观察者模式按键管理方式的代码演示
因为多了一个return,上个例子就是责任链模式;
如果将这个return去掉,上个例子就可以变成观察者模式;但是,需要做点修改:
1)需要添加更多的可能情况的key组;
2)m_nowGroupIndex需要随着页面的跳转而改变;
疑惑:
1、总是觉得现在这样的状态模式还是不够抽象,能够抽象出像责任链模式例子中的KeyControlManager类就更加好了;
2、总是觉得这个责任链模式例子中的代码,不是那么和谐,尤其是keyHandle()方法中的内容(虽然可以使用map来关联keyCode和Function);
有没有更加好的改进方法?
声明:
1、本文属于原创文章,历时一周思考得出的结论;
2、由于工作原因,本文代码由Actionscript2实现,语法和Java很类似,请不要对语言的优劣性有任何微词;
探讨机顶盒播控页面的按键管理方案,包括状态模式、责任链模式及观察者模式的应用与优缺点分析。
1243

被折叠的 条评论
为什么被折叠?



