最近公司需要开发一个TV的luancher,就是那种纯物理按键的遥控,没有触摸屏,现在说说我踩得那些坑。(其实布局和代码逻辑和正常的安卓应用差不多)1.焦点   焦点   焦点,重要的事情说三遍,安卓TV由于没有触摸屏所以需要手动设置可以获取焦点的控件。2.设置获取到状态也就是常用的select。3.各种事件冲突。4.按键事件 通过重写onKeyDown(),onKeyUp()方法中监听keyCode的值,来判断用户按下的是哪个键,比如OK键  其他特殊的键值,上下左右键的话,系统会自动处理的。
其实遇到最烦的也就是焦点了,下面列举一下遇到的几件坑爹事情。

1.viewpager嵌套fragment中嵌套gridview,onkeyDown事件中的OK键的键值回调不出来,经过一系列的排查发现是系统的gridview中把以下四个键值给处理掉了。

case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_SPACE:
case KeyEvent.KEYCODE_NUMPAD_ENTER:

跟着源码查看:GridView.java中查看onKeyDown()方法:

@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {return commonKey(keyCode, 1, event);}

继续往下跟commonKey()方法:

    private boolean commonKey(int keyCode, int count, KeyEvent event) {if (mAdapter == null) {return false;}if (mDataChanged) {layoutChildren();}boolean handled = false;int action = event.getAction();if (KeyEvent.isConfirmKey(keyCode)&& event.hasNoModifiers() && action != KeyEvent.ACTION_UP) {handled = resurrectSelectionIfNeeded();if (!handled && event.getRepeatCount() == 0 && getChildCount() > 0) {keyPressed();handled = true;}}if (!handled && action != KeyEvent.ACTION_UP) {switch (keyCode) {case KeyEvent.KEYCODE_DPAD_LEFT:if (event.hasNoModifiers()) {handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_LEFT);}break;case KeyEvent.KEYCODE_DPAD_RIGHT:if (event.hasNoModifiers()) {handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_RIGHT);}break;case KeyEvent.KEYCODE_DPAD_UP:if (event.hasNoModifiers()) {handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_UP);} else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);}break;case KeyEvent.KEYCODE_DPAD_DOWN:if (event.hasNoModifiers()) {handled = resurrectSelectionIfNeeded() || arrowScroll(FOCUS_DOWN);} else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);}break;case KeyEvent.KEYCODE_PAGE_UP:if (event.hasNoModifiers()) {handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_UP);} else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);}break;case KeyEvent.KEYCODE_PAGE_DOWN:if (event.hasNoModifiers()) {handled = resurrectSelectionIfNeeded() || pageScroll(FOCUS_DOWN);} else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);}break;case KeyEvent.KEYCODE_MOVE_HOME:if (event.hasNoModifiers()) {handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_UP);}break;case KeyEvent.KEYCODE_MOVE_END:if (event.hasNoModifiers()) {handled = resurrectSelectionIfNeeded() || fullScroll(FOCUS_DOWN);}break;case KeyEvent.KEYCODE_TAB:// TODO: Sometimes it is useful to be able to TAB through the items in//     a GridView sequentially.  Unfortunately this can create an//     asymmetry in TAB navigation order unless the list selection//     always reverts to the top or bottom when receiving TAB focus from//     another widget.if (event.hasNoModifiers()) {handled = resurrectSelectionIfNeeded()|| sequenceScroll(FOCUS_FORWARD);} else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {handled = resurrectSelectionIfNeeded()|| sequenceScroll(FOCUS_BACKWARD);}break;}}if (handled) {return true;}if (sendToTextFilter(keyCode, count, event)) {return true;}switch (action) {case KeyEvent.ACTION_DOWN:return super.onKeyDown(keyCode, event);case KeyEvent.ACTION_UP:return super.onKeyUp(keyCode, event);case KeyEvent.ACTION_MULTIPLE:return super.onKeyMultiple(keyCode, count, event);default:return false;}}

往下跟可以看到KeyEvent.isConfirmKey()方法:

    @UnsupportedAppUsagepublic static final boolean isConfirmKey(int keyCode) {switch (keyCode) {case KeyEvent.KEYCODE_DPAD_CENTER:case KeyEvent.KEYCODE_ENTER:case KeyEvent.KEYCODE_SPACE:case KeyEvent.KEYCODE_NUMPAD_ENTER:return true;default:return false;}}

这里把以上4个键值给消费掉了

所以就会出现之前说的那种情况。

解决方案,重写自己的GridView继承自系统的GridView,然后重写对应方法:

public class CustomGridView extends GridView {public CustomGridView(Context context) {super(context);}public CustomGridView(Context context, AttributeSet attrs) {super(context, attrs);}public CustomGridView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {switch (keyCode) {case KeyEvent.KEYCODE_DPAD_CENTER:case KeyEvent.KEYCODE_ENTER:case KeyEvent.KEYCODE_SPACE:case KeyEvent.KEYCODE_NUMPAD_ENTER:return false;}return super.onKeyDown(keyCode, event);}@Overridepublic boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {switch (keyCode) {case KeyEvent.KEYCODE_DPAD_CENTER:case KeyEvent.KEYCODE_ENTER:case KeyEvent.KEYCODE_SPACE:case KeyEvent.KEYCODE_NUMPAD_ENTER:return false;}return super.onKeyMultiple(keyCode, repeatCount, event);}
}

使用以上自定义的GridView就能解决gridview中获取不到OK键的监听。

2.ScrollView中的子view获取不到焦点。因为不是触摸屏,所以需要获取焦点的view在xml中设置android:focusable="true"属性。还有个很重要的属性,也是平时触摸屏基本上用不到的属性android:descendantFocusability="afterDescendants"百度一下这个属性的3种值代表什么意思。

<ScrollViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:descendantFocusability="afterDescendants"android:scrollbars="none"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><LinearLayoutandroid:id="@+id/ll_ly"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/selector1"android:focusable="true"android:onClick="@{activity::onClick}"android:orientation="horizontal"><ImageViewandroid:layout_width="@dimen/x19"android:layout_height="@dimen/x19"android:layout_gravity="center_vertical"android:layout_marginLeft="@dimen/x4"android:src="@mipmap/icon_ly" /><TextViewandroid:layout_width="match_parent"android:layout_height="@dimen/x24"android:layout_marginLeft="@dimen/x3"android:gravity="center_vertical"android:text="蓝牙"android:textColor="#FFFFFF"android:textSize="@dimen/x11" /></LinearLayout><LinearLayoutandroid:id="@+id/ll_pmxm"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/selector1"android:focusable="true"android:onClick="@{activity::onClick}"android:orientation="horizontal"><ImageViewandroid:layout_width="@dimen/x19"android:layout_height="@dimen/x19"android:layout_gravity="center_vertical"android:layout_marginLeft="@dimen/x4"android:src="@mipmap/icon_pmxm" /><TextViewandroid:layout_width="match_parent"android:layout_height="@dimen/x24"android:layout_marginLeft="@dimen/x3"android:gravity="center_vertical"android:text="屏幕休眠"android:textColor="#FFFFFF"android:textSize="@dimen/x11" /></LinearLayout><LinearLayoutandroid:id="@+id/ll_ld"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/selector1"android:focusable="true"android:onClick="@{activity::onClick}"android:orientation="horizontal"><ImageViewandroid:layout_width="@dimen/x19"android:layout_height="@dimen/x19"android:layout_gravity="center_vertical"android:layout_marginLeft="@dimen/x4"android:src="@mipmap/icon_ld" /><TextViewandroid:layout_width="match_parent"android:layout_height="@dimen/x24"android:layout_marginLeft="@dimen/x3"android:gravity="center_vertical"android:text="亮度"android:textColor="#FFFFFF"android:textSize="@dimen/x11" /></LinearLayout><LinearLayoutandroid:id="@+id/ll_cckj"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/selector1"android:focusable="true"android:onClick="@{activity::onClick}"android:orientation="horizontal"><ImageViewandroid:layout_width="@dimen/x19"android:layout_height="@dimen/x19"android:layout_gravity="center_vertical"android:layout_marginLeft="@dimen/x4"android:src="@mipmap/icon_cckj" /><TextViewandroid:layout_width="match_parent"android:layout_height="@dimen/x24"android:layout_marginLeft="@dimen/x3"android:gravity="center_vertical"android:text="存储空间"android:textColor="#FFFFFF"android:textSize="@dimen/x11" /></LinearLayout><LinearLayoutandroid:id="@+id/ll_rq"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/selector1"android:focusable="true"android:onClick="@{activity::onClick}"android:orientation="horizontal"><ImageViewandroid:layout_width="@dimen/x19"android:layout_height="@dimen/x19"android:layout_gravity="center_vertical"android:layout_marginLeft="@dimen/x4"android:src="@mipmap/icon_rq" /><TextViewandroid:layout_width="match_parent"android:layout_height="@dimen/x24"android:layout_marginLeft="@dimen/x3"android:gravity="center_vertical"android:text="日期"android:textColor="#FFFFFF"android:textSize="@dimen/x11" /></LinearLayout><LinearLayoutandroid:id="@+id/ll_gybj"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/selector1"android:focusable="true"android:onClick="@{activity::onClick}"android:orientation="horizontal"><ImageViewandroid:layout_width="@dimen/x19"android:layout_height="@dimen/x19"android:layout_gravity="center_vertical"android:layout_marginLeft="@dimen/x4"android:src="@mipmap/icon_gybj" /><TextViewandroid:layout_width="match_parent"android:layout_height="@dimen/x24"android:layout_marginLeft="@dimen/x3"android:gravity="center_vertical"android:text="关于本机"android:textColor="#FFFFFF"android:textSize="@dimen/x11" /></LinearLayout></LinearLayout></ScrollView>

3.获取到焦点和没获取到焦点时显示的背景色不一样,这个功能在触摸屏中也经常用到的select就出来了。

<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:drawable="@color/black" android:state_focused="false" /><item android:drawable="@color/white1" android:state_focused="true" />
</selector>

4.RecyclerView中的item获取不到焦点的问题,或者当RecyclerView滚动时,子item的焦点就丢失了。

4.1先重写LinearLayoutManager:

public class FocusLinearLayoutManager extends LinearLayoutManager {public FocusLinearLayoutManager(Context context) {super(context);}public FocusLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {super(context, orientation, reverseLayout);}public FocusLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}@Overridepublic View onInterceptFocusSearch(View focused, int direction) {int count = getItemCount();//获取item的总数int fromPos = getPosition(focused);//当前焦点的位置int lastVisibleItemPos = findLastVisibleItemPosition();//最新的已显示的Item的位置switch (direction) {//根据按键逻辑控制positioncase View.FOCUS_UP:fromPos--;break;case View.FOCUS_DOWN:fromPos++;break;}Log.i("dz", "onInterceptFocusSearch , fromPos = " + fromPos + " , count = " + count+" , lastVisibleItemPos = "+lastVisibleItemPos);if(fromPos < 0 || fromPos >= count ) {//如果下一个位置<0,或者超出item的总数,则返回当前的View,即焦点不动if (fromPos < 0) {return super.onInterceptFocusSearch(focused, direction);}return focused;} else {//如果下一个位置大于最新的已显示的item,即下一个位置的View没有显示,则滑动到那个位置,让他显示,就可以获取焦点了if (fromPos > lastVisibleItemPos) {scrollToPosition(fromPos);}}return super.onInterceptFocusSearch(focused, direction);}
}

4.2 Item的根布局中添加android:focusable="true"属性和android:background="@drawable/selector1"
4.3 使用FocusLinearLayoutManager :

RecyclerView.setLayoutManager(new FocusLinearLayoutManager(this, FocusLinearLayoutManager.VERTICAL, false));

这样就解决的之前说的问题。遇到以上的坑 记录分享一下,避免再次踩坑。(总而言之,没有想象中的那么难,系统会处理很多按键的事件,就是焦点和特殊键值需要处理一下)

安卓TV开发遇到的那些坑相关推荐

  1. 安卓TV开发(九) Android模拟事件 遥控器变身成鼠标来操作TV

    本文出处:http://blog.csdn.net/sk719887916/article/details/40348853,作者:skay      阅读此文建议先阅读 安卓Tv开发(二)移动智能电 ...

  2. 安卓TV开发(三) 移动智能设备之实现主流TV电视盒子焦点可控UI

    前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验上有很大的区别,本系列博文主 ...

  3. 安卓TV开发(八) 移动智能终端多媒体爬虫技术 获取加载网页视频源

    转载请标明出处:http://blog.csdn.net/sk719887916/article/details/40049137,作者:skay 从上一篇学习中,学习了多媒体技术中的怎么去用josu ...

  4. 安卓TV开发(五) 移动智能终端UI之实现主流TV焦点可控UI

       载请标明出处: http://blog.csdn.net/sk719887916 ,作者: skay    由于其他网站收录,导致你无法查看本系列原创文章请点击此处 安卓TV开发(四)实现主 ...

  5. 安卓TV开发《2》开发TV应用

    本节将会总结下TV开发中的注意点,如何管理TV控制器,如何构建TV布局.如何创建TV导航. 一.处理TV硬件 为啥要处理TV硬件呢?因为TV不像其他安卓设备一样支持触摸屏,照相机.GPS之类的.这些硬 ...

  6. 安卓TV开发之实现原生播放器

    安卓有很多第三方的播放器,比如最强大的ijkPlayer和最全能的Vitamio播放器,如果是手机APP开发,可以直接拿过来用.但是TV APP开发不行,因为电视盒子的高定制性,兼容性很差,比手机上差 ...

  7. 安卓TV开发(七) 移动智能终端多媒体之在线解析网页视频源

    载请标明出处:http://blog.csdn.net/sk719887916/article/details/40049137,作者:skay 结束了所有UI绘制的学习,智能设备常用的应用音视频类, ...

  8. Android TV开发总结(四)通过RecycleView构建一个TV app列表页(仿腾讯视频TV版)

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52854131 前言:昨晚看锤子手 ...

  9. Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑

    原文:Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑 版权声明:我已委托"维权骑士"(rightknights.com)为我的文章进行维权行动.转载务必 ...

  10. android tv 菜单键,Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑

    前言:关于<TV Metro界面(仿泰捷视频TV版)源码解析>由于都是相关代码,就不发公众号了,有兴趣的可以看链接:http://blog.csdn.net/hejjunlin/artic ...

最新文章

  1. 【linux】Valgrind工具集详解(二):入门
  2. 【使用 DOM】为DOM元素设置样式
  3. python适合找哪方面工作_学习python后能做哪方面的工作
  4. Linux学习之系统编程篇:进程回收(wait / waitpid)
  5. 《机器学习实战》第十五章 MapReduce
  6. Java死锁、活锁,悲观锁、乐观锁
  7. html搜索结果 重置,搜索结果和后退按钮/ HTML表格
  8. Ubuntu 14.04 为 root 帐号开启 SSH 登录
  9. Spring 的 init-method 和 destory-method
  10. Node.js开发入门—使用http访问外部世界
  11. aka名字_中国新说唱:马来王子尤长靖,尤长靖自编AKA名字
  12. Java中级内容——异常处理(exception handing)
  13. 制作一个类“全能扫描王”的简易扫描软件(opencv)
  14. Java语言有哪些特点?
  15. Ant Design Table columns 参数配置隐藏列方法
  16. Pandas和Numpy:常见函数参数inplace的作用
  17. vp230引脚功能_正点原子阿波罗STM32F767
  18. warning: CRLF will be replaced by LF in 出现的原因和解决办法
  19. Android 经典蓝牙与 BLE 蓝牙基础
  20. 计算机主板命名根据,华硕主板命名_技嘉主板命名_微星主板命名_快速看懂-太平洋电脑网...

热门文章

  1. 自然辩证法与计算机科学与技术,自然辩证法与计算机科学技术的研究.pdf
  2. 谈谈键盘A键不定期失灵问题
  3. 思科Cisco BGP 专题(一) BGP基本概念
  4. kali kda安装 linux_KDA-无损音乐下载
  5. zblog php的foot模板在那里,zblog主题模板修改教程 zblog模板怎么修改?
  6. USB3.0:VL817Q7-C0的LAYOUT指南(三)
  7. 站在Stay老司机肩膀上分析Retrofit
  8. 《javascript设计模式与开发实践》——第一章(面向对象的javascript)学习记录
  9. html 灯箱效果,基于 BootStrap 4 的图片灯箱效果 | 智慧宫
  10. 乾隆年间贪污贿赂成风:皇帝敛财不逊臣子