安卓TV开发遇到的那些坑
最近公司需要开发一个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开发遇到的那些坑相关推荐
- 安卓TV开发(九) Android模拟事件 遥控器变身成鼠标来操作TV
本文出处:http://blog.csdn.net/sk719887916/article/details/40348853,作者:skay 阅读此文建议先阅读 安卓Tv开发(二)移动智能电 ...
- 安卓TV开发(三) 移动智能设备之实现主流TV电视盒子焦点可控UI
前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验上有很大的区别,本系列博文主 ...
- 安卓TV开发(八) 移动智能终端多媒体爬虫技术 获取加载网页视频源
转载请标明出处:http://blog.csdn.net/sk719887916/article/details/40049137,作者:skay 从上一篇学习中,学习了多媒体技术中的怎么去用josu ...
- 安卓TV开发(五) 移动智能终端UI之实现主流TV焦点可控UI
载请标明出处: http://blog.csdn.net/sk719887916 ,作者: skay 由于其他网站收录,导致你无法查看本系列原创文章请点击此处 安卓TV开发(四)实现主 ...
- 安卓TV开发《2》开发TV应用
本节将会总结下TV开发中的注意点,如何管理TV控制器,如何构建TV布局.如何创建TV导航. 一.处理TV硬件 为啥要处理TV硬件呢?因为TV不像其他安卓设备一样支持触摸屏,照相机.GPS之类的.这些硬 ...
- 安卓TV开发之实现原生播放器
安卓有很多第三方的播放器,比如最强大的ijkPlayer和最全能的Vitamio播放器,如果是手机APP开发,可以直接拿过来用.但是TV APP开发不行,因为电视盒子的高定制性,兼容性很差,比手机上差 ...
- 安卓TV开发(七) 移动智能终端多媒体之在线解析网页视频源
载请标明出处:http://blog.csdn.net/sk719887916/article/details/40049137,作者:skay 结束了所有UI绘制的学习,智能设备常用的应用音视频类, ...
- Android TV开发总结(四)通过RecycleView构建一个TV app列表页(仿腾讯视频TV版)
转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52854131 前言:昨晚看锤子手 ...
- Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑
原文:Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑 版权声明:我已委托"维权骑士"(rightknights.com)为我的文章进行维权行动.转载务必 ...
- android tv 菜单键,Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑
前言:关于<TV Metro界面(仿泰捷视频TV版)源码解析>由于都是相关代码,就不发公众号了,有兴趣的可以看链接:http://blog.csdn.net/hejjunlin/artic ...
最新文章
- 【linux】Valgrind工具集详解(二):入门
- 【使用 DOM】为DOM元素设置样式
- python适合找哪方面工作_学习python后能做哪方面的工作
- Linux学习之系统编程篇:进程回收(wait / waitpid)
- 《机器学习实战》第十五章 MapReduce
- Java死锁、活锁,悲观锁、乐观锁
- html搜索结果 重置,搜索结果和后退按钮/ HTML表格
- Ubuntu 14.04 为 root 帐号开启 SSH 登录
- Spring 的 init-method 和 destory-method
- Node.js开发入门—使用http访问外部世界
- aka名字_中国新说唱:马来王子尤长靖,尤长靖自编AKA名字
- Java中级内容——异常处理(exception handing)
- 制作一个类“全能扫描王”的简易扫描软件(opencv)
- Java语言有哪些特点?
- Ant Design Table columns 参数配置隐藏列方法
- Pandas和Numpy:常见函数参数inplace的作用
- vp230引脚功能_正点原子阿波罗STM32F767
- warning: CRLF will be replaced by LF in 出现的原因和解决办法
- Android 经典蓝牙与 BLE 蓝牙基础
- 计算机主板命名根据,华硕主板命名_技嘉主板命名_微星主板命名_快速看懂-太平洋电脑网...
热门文章
- 自然辩证法与计算机科学与技术,自然辩证法与计算机科学技术的研究.pdf
- 谈谈键盘A键不定期失灵问题
- 思科Cisco BGP 专题(一) BGP基本概念
- kali kda安装 linux_KDA-无损音乐下载
- zblog php的foot模板在那里,zblog主题模板修改教程 zblog模板怎么修改?
- USB3.0:VL817Q7-C0的LAYOUT指南(三)
- 站在Stay老司机肩膀上分析Retrofit
- 《javascript设计模式与开发实践》——第一章(面向对象的javascript)学习记录
- html 灯箱效果,基于 BootStrap 4 的图片灯箱效果 | 智慧宫
- 乾隆年间贪污贿赂成风:皇帝敛财不逊臣子