浅谈AndroidTV开发与常规APP开发的异同点

不知不觉中,从事AndroidTV开发已将近快有两个月了,从一开始的焦点问题慢慢摸索到最后的KeyDown事件分发,也算是整理了写头绪。还记得最初想要调试程序,是通过AS自带的AndroidTV模拟器,操作起来很不友好,后来在公司架构师的提醒下,才发现其实用GenyMotion的平板镜像也是一样的。只不过是用方向键的上下左右替代了APP的上下左右滑动,还有回车键替代了原先的点击事件。这样一来比常规的AndroidTV模拟器感觉好多了,但总还是感觉差点什么...
其实,TV开发和常规APP开发最大区别在于屏幕,一个屏幕支持触控,另外一个不支持,所以导致其中一个的事件分发名字是onKeyDown()。因为不能有触摸操作。所以在TV端开发全是焦点,可以理解为你手指落下但却没有按下发生点击事件的前奏。
【浅谈AndroidTV开发与常规APP开发的异同点】曾经百度过很多焦点方面的文章,无奈这方面资料实在是太少,很多收藏的东西已经找不到出处,在此所说两种获取焦点方式,我已经忘了我是哪里找来的了……
一、采用Android自带的直接控制焦点上下左右的方法。
这种方法的前提是必须知道每个view的id,因此在进行布局时有必须要通过view.setId(…)指定view的特定ID, 然后通过view.setNextLeftView(…)等四个方法控制该view的上下左右移动后所到达的view。 在XML布局中也可以提前设置这个 android:nextFocusLeft=""。引号内填写下一个焦点所持id。 二、 在一些比较复杂的Layout中,特别是涉及到在View的焦点变化的过程中还要控制view的背景以及字体颜色变化等。 比如:在有多个Layout(假设有Layout1, Layout2, Layout3,每个Layout中都有若干个ImageButton), 当你从Layout1中的某个ImageButton 1.1中移动Layout2中ImageButton 2.1,此时ImageButton 1.1要标识为被选中,但是失去焦点,此时ImageButton 2.1是选中并且获取焦点,再从ImageButton 2.1移到ImageButton 3.1的过程中也是这种情况。 对于这样一种情况,你必须对每个ImageButton 设置焦点捕获实际(setOnFocusChangeListener),在该监听事件中处理:

ImageButton.setOnFocusChangeListener(){ public void onFocus(boolean Focus){ if( Focus ){ // ImageButton 2.1 获焦时, ImageButton 2.1 改变获取焦点背景, ImageButton 1.1也改变失去焦点背景 } else{ //ImageButton 2.1 获焦时,ImageButton 2.1 改变失去焦点背景 ,ImageButton 3.1也改变获取焦点背景 } } }

当 当前焦点移动到ImageButton 3.1上时,你有时需要知道此时Layout1、Layout2上是哪个ImageButton 被选中, 因此你还必须设置三个ImageButton 变量(标识选中哪个布局中的那个ImageButton 对象) 和三个int变量(标识选中哪个布局中的第几个)。 通过这些标识,你就可以很方便的了解到那个聚焦和哪个失去焦点了。 对于进行上下左右的控制,此时就要在OnKeyDown事件中进行捕获处理了。 由于事先已经知道是哪个Layout中的哪ImageButton被选中了,而此时你进行上下左右操作是在你被选中的View上进行操作的, 因此在OnKeyDown中你只需先判断是哪个View被选中,然后根据按键事件来移动View(通过之前设置的int标识进行移动)
(注:即兴所写,代码可能有错,领会意思最重要)
假设Layout1中被选中的ImageButton为mFirstImgBtn, 序号为mFirstIndx; Layout3中被选中的ImageButton为mThirdImgBtn, 序号为mThirdIndx; 每个Layout里面的ImageButton均在一个数组中, 假设分别为:ImageButton mImgBtnArray1[], mImgBtnArray2[], mImgBtnArray3[],当前被选中的view为mSenondImgBtn
public void OnKeyDown(int keyCode, KeyEvent event){ if( event.KEYCODE_DROP_UP== keyCode ){//如果按下的是上键 mImgBtnArray1[ThirdIndx ].requestFocus; } if( event.KEYCODE_DROP_DOWN == keyCode ){//如果按下的是下键 mImgBtnArray3[ ThirdIndx ].requestFocus; } if( event.KEYCODE_DROP_LEFT == keyCode ){//如果按下的是左键 mImgBtnArray1[ ThirdIndx-1 ].requestFocus; } if( event.KEYCODE_DROP_RIGHT == keyCode ){//如果按下的是右键 mImgBtnArray1[ ThirdIndx+1 ].requestFocus; } }

而具体的获焦事件处理则在每个View的 OnFocusChangeListener 事件中处理。
在这里介绍一种方法: 在当前的焦点view下面,重写focusSearch这个函数,然后再用一个回调.
public View focusSearch(View focused, int direction) { if (mFocusSearchLister != null) { View view = mFocusSearchLister.onFocusSearch(focused, direction); if (view != null) return view; } return super.focusSearch(focused, direction); } FocusSearchLister mFocusSearchLister; class FocusSearchLister { public View onFocusSearch(View focused, int direction) { return null; } }


还有一种焦点控制,是关于事件分发的。
@Override public boolean onKey(View v, int keyCode, KeyEvent event) { int action = event.getAction(); if (action == KeyEvent.ACTION_DOWN) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: removeMenu(v); return true; case KeyEvent.KEYCODE_DPAD_LEFT:// 防止菜单往左边跑到其它地方. // 如果为listview,左边.就消失. if ((v instanceof ListView)) { removeMenu(v); return true; } case KeyEvent.KEYCODE_DPAD_RIGHT: // 防止菜单往右边跑到其它地方. case KeyEvent.KEYCODE_DPAD_UP: // 防止菜单往上面跑到其它地方. case KeyEvent.KEYCODE_DPAD_DOWN: // 防止菜单往下面跑到其它地方. v.onKeyDown(keyCode, event); return true; default: break; } } return false; }

可以理解为 将事件返回给自己,然后 return true,消耗掉了事件,就不会往下乱跑了。
其实,在智能电视,盒子,投影仪,TV开发中,总要遇到焦点,或者各种莫名其妙的问题,让我头疼不已,不过在摸爬滚打中,总算总结了一些方法。在开发中,想要有焦点的控件进行放大或者有其它操作,在布局XML尽量去设置 android:focusable="true", 不然是无效的.在 GridView, ListView中的Item,也要设置 android:focusable="true"。

该如何去监听控件焦点的事件呢,可以这样,如果界面上有很多控件,有一些是我不想操作,可以使用这个监听 view.setOnFocusChangeListener,如果全部都要操作,可以使用全局监听,使整个界面都会监听到,使用这个函数 view.getViewTreeObserver().addOnGlobalFocusChangeListener(... ... 。

好了,说了半天我也就想吐槽下,如果你也是AndroidTV开发者你会懂我意思,如果你正准备转这个方向,给你点建议……还是别转了吧。如果你喜欢新鲜事,喜欢尝试不同的东西,那么,祝你好运~Good Luck~!

    推荐阅读