不知不觉中,从事AndroidTV开发已将近快有两个月了,从一开始的焦点问题慢慢摸索到最后的KeyDown事件分发,也算是整理了写头绪。还记得最初想要调试程序,是通过AS自带的AndroidTV模拟器,操作起来很不友好,后来在公司架构师的提醒下,才发现其实用GenyMotion的平板镜像也是一样的。只不过是用方向键的上下左右替代了APP的上下左右滑动,还有回车键替代了原先的点击事件。这样一来比常规的AndroidTV模拟器感觉好多了,但总还是感觉差点什么...

其实,TV开发和常规APP开发最大区别在于屏幕,一个屏幕支持触控,另外一个不支持,所以导致其中一个的事件分发名字是onKeyDown()。因为不能有触摸操作。所以在TV端开发全是焦点,可以理解为你手指落下但却没有按下发生点击事件的前奏。

曾经百度过很多焦点方面的文章,无奈这方面资料实在是太少,很多收藏的东西已经找不到出处,在此所说两种获取焦点方式,我已经忘了我是哪里找来的了……

一、采用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~!

Logo

智屏生态联盟致力于大屏生态发展,利用大屏快应用技术降低开发者开发、发布大屏应用门槛

更多推荐