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

 

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

 

 

简单的播控页面:
这个播控页面由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很类似,请不要对语言的优劣性有任何微词;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值