Android TV开发焦点移动源码分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android TV开发焦点移动源码分析相关的知识,希望对你有一定的参考价值。

参考技术A 点可以理解为选中态,在android TV上起很重要的作用。一个视图控件只有在获得焦点的状态下,才能响应按键的Click事件。
相对于手机上用手指点击屏幕产生的Click事件, 在TV中通过点击遥控器的方向键来控制焦点的移动。当焦点移动到目标控件上之后,按下遥控器的确定键,才会触发一个Click事件,进而去做下一步的处理
在处理焦点的时候,有一些基础的用法需要知道。
首先,一个控件isFocusable()需要为true才有资格可以获取到焦点。如果想要在触摸模式下获取焦点,需要通过setFocusableInTouchMode(boolean)来设置。也可以直接在xml布局文件中指定:

keyEvent 分发过程:

而当按下遥控器的按键时,会产生一个按键事件,就是KeyEvent,包含“上”,“下”,“左”,“右”,“返回”,“确定”等指令。焦点的处理就在KeyEvent的分发当中完成。
首先,KeyEvent会流转到ViewRootImpl中开始进行处理,具体方法是内部类 ViewPostImeInputStage 中的 processKeyEvent :

接下来先看一下KeyEvent在view框架中的分发:

这里也是可以做焦点控制,最好是在 event.getAction() == KeyEvent.ACTION_DOWN 进行.
因为android 的 ViewRootlmpl 的 processKeyEvent 焦点搜索与请求的地方 进行了判断if (event.getAction() == KeyEvent.ACTION_DOWN)

• 首先ViewGroup会一层一层往上执行父类的dispatchKeyEvent方法,如果返回true那么父类的dispatchKeyEvent方法就会返回true,也就代表父类消费了该焦点事件,那么焦点事件自然就不会往下进行分发。
• 然后ViewGroup会判断mFocused这个view是否为空,如果为空就会return false,焦点继续往下传递;如果不为空,那就会return mFocused的dispatchKeyEvent方法返回的结果。这个mFocused其实是ViewGroup中当前获取焦点的子View

发现执行了onKeyListener中的onKey方法,如果onKey方法返回true,那么dispatchKeyEvent方法也会返回true
如果想要修改ViewGroup焦点事件的分发
• 重写view的dispatchKeyEvent方法
• 给某个子view设置onKeyListener监听

下面再来看一下如果一个页面第一次进入,系统是如何确定焦点是定位在哪个view上的

由于DecorView继承自FrameLayout,这里调用的是ViewGroup的requestFocus

descendantFocusability:
• FOCUS_AFTER_DESCENDANTS:先分发给Child View进行处理,如果所有的Child View都没有处理,则自己再处理
• FOCUS_BEFORE_DESCENDANTS:ViewGroup先对焦点进行处理,如果没有处理则分发给child View进行处理
• FOCUS_BLOCK_DESCENDANTS:ViewGroup本身进行处理,不管是否处理成功,都不会分发给ChildView进行处理
因为 PhoneWindow 给 DecoreView 初始化时设置 了 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS),所以这里默认是FOCUS_AFTER_DESCENDANTS

到此第一次请求焦点的过程基本告一个段落

焦点移动的时候,默认的情况下,会按照一种算法去找在指定移动方向上最近的邻居。在一些情况下,焦点的移动可能跟开发者的意图不符,这时开发者可以在布局文件中使用下面这些XML属性来指定下一个焦点对象:

在KeyEvent分发中已经知道如果分发过程中event没有被消耗,就会根据方向搜索以及请求焦点View

流程一:查找用户指定的下一个焦点

流程二:获取搜索方向上所有可以获取焦点的view,使用算法查找下一个view
addFocusables() 获取搜索方向上可获得焦点的view

descendantFocusability属性决定了ViewGroup和其子view的聚焦优先级
• FOCUS_BLOCK_DESCENDANTS:viewgroup会覆盖子类控件而直接获得焦点
• FOCUS_BEFORE_DESCENDANTS:viewgroup会覆盖子类控件而直接获得焦点
• FOCUS_AFTER_DESCENDANTS:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
addFocusables 的第一个参数views是由root决定的。在ViewGroup的focusSearch方法中传进来的root是DecorView,也可以主动调用FocusFinder的findNextFocus方法,在指定的ViewGroup中查找焦点。
FocusFinder.findNextFocus 查找焦点

安卓Tv开发移动智能电视之焦点控制(按键事件)

      原文:http://blog.csdn.net/sk719887916/article/details/44781475 skay

     TV项目源码:https://github.com/Tamicer/TvResource-Android

前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验上有很大的区别,本系列博文主要用移动智能电视(TV)上实现视频播放器的去了解下在智能设备上的开发的相关技术。本系列将实现遥控器焦点控制,模拟鼠标点击,视频在线直播,和手机当遥控器等功能,带给你不一样的开发体验。

上篇文章中说道了触控事件,(安卓Tv开发(一)焦点控制(触控事件))但是只对MotionEvent做了细说,很多东西还是不懂怎么用触控事件,现在就做对上篇的补充吧   本文出处:http://blog.csdn.net/sk719887916

在view重写onTouchEvent方法,通过event.getAction()对不同的enent就可以处理了,代码如下

  1. public boolean onTouchEvent(MotionEvent event)  
  2.       
  3.         int events[] = MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE,  
  4.                 MotionEvent.ACTION_UP, MotionEvent.ACTION_MOVE, MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_OUTSIDE,  
  5.                 MotionEvent.ACTION_POINTER_DOWN,MotionEvent.ACTION_POINTER_UP,  
  6.                 MotionEvent.EDGE_TOP,MotionEvent.EDGE_BOTTOM,MotionEvent.EDGE_LEFT,MotionEvent.EDGE_RIGHT;  
  7.           
  8.         String szEvents[]="ACTION_DOWN""ACTION_MOVE",  
  9.         "ACTION_UP""ACTION_MOVE""ACTION_CANCEL""ACTION_OUTSIDE",  
  10.         "ACTION_POINTER_DOWN","ACTION_POINTER_UP",  
  11.         "EDGE_TOP","EDGE_BOTTOM","EDGE_LEFT","EDGE_RIGHT";  
  12.         for(int i=0; i < events.length; i++)  
  13.           
  14.             if(events[i] == event.getAction())  
  15.               
  16.                 if(oldevent != event.getAction())  
  17.                   
  18.                     DisplayEventType(szEvents[i]);  
  19.                     oldevent = event.getAction();  
  20.                   
  21.                 break;  
  22.               
  23.           
  24.         return super.onTouchEvent(event);  
  25.       
好了 ,今天言归正传,接着说键盘事件:keyevent.

KeyEvent事件


一  keyEvent:

     源码位于android.view下,包装管理所有按键有关输入的事件体系,KeyEvent和MotionEvent的分发流程一样,都是InputEvent的子类,都是从Activity开始的,KeyEvent主要有以下事件类型:



   KeyEvent.KEYCODE_DPAD_UP; 上
   KeyEvent.KEYCODE_DPAD_DOWN; 下
   KeyEvent.KEYCODE_DPAD_LEFT;左
   KeyEvent.KEYCODE_DPAD_RIGHT;右
   KeyEvent.KEYCODE_DPAD_CENTER;确定键
   KeyEvent.KEYCODE_DPAD_RIGHT; 右
   KeyEvent.KEYCODE_XXX:数字键 (xx表示你按了数字几)
   KeyEvent.KEYCODE_BACK; 返回键
   KeyEvent.KEYCODE_HOME;房子键

   KeyEvent.KEYCODE_A: A-Z,26个字母

   KeyEvent.KEYCODE_MENU菜单键。

   谷歌提供了260种keyEvent,其他类型可以去源码查看,一个最基本的构造函数:

       
public KeyEvent(long downTime, long eventTime, int action,
    int code, int repeat, int metaState,
    int deviceId, int scancode, int flags, int source) 
    mDownTime = downTime;
    mEventTime = eventTime;
    mAction = action;
    mKeyCode = code;
    mRepeatCount = repeat;
    mMetaState = metaState;
    mDeviceId = deviceId;
    mScanCode = scancode;
    mFlags = flags;
    mSource = source;
    



   仔细观察,发现一个按键事件,会包含以上很多属性, 按键Id,设备ID, 按键坐标,按键资源,按键标记位,按键action(Up,down),响应次数,按下的时间等。


   一般我们处理按键事件可以这样做:
       public boolean onKeyDown(int keyCode, KeyEvent event) 
        switch (keyCode) 
            case KeyEvent.KEYCODE_DPAD_CENTER:
                Toast("你按下中间键");
                break;

            case KeyEvent.KEYCODE_DPAD_DOWN:
                Toast("你按下下方向键");
            break;

            case KeyEvent.KEYCODE_DPAD_LEFT:
                Toast("你按下左方向键");
                break;

            case KeyEvent.KEYCODE_DPAD_RIGHT:
                Toast("你按下右方向键");
                break;

            case KeyEvent.KEYCODE_DPAD_UP:
                Toast("你按下上方向键");
                break;
        
        return super.onKeyDown(keyCode, event);
    

      
   复写onKeyDown()或者onKeyUp();

   不管是触控和按键事件,我们在Java代码中凭空创建,API提供了obtain()函数,来让开发者模拟事件,让系统处理。

KeyEvent事件分发

   上一篇文中介绍了触控事件的机制,其实按键事件和他很相似,基本也传递和分发(拦截不存在,你无法阻止实际物理键的按下去),主要有dispatchKeyEvent(KeyEvent event),onKeyUp和onKeyDown,

**dispatchKeyEvent()**

  主要处理按键的分发。avtivity和view都拥有此方法,两种有所区别的,实际都是交给DecorView来处理。

  
  public boolean dispatchKeyEvent(KeyEvent event) 
        onUserInteraction();

        // Let action bars open menus in response to the menu key prioritized over
        // the window handling it
        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
                mActionBar != null && mActionBar.onMenuKeyEvent(event)) 
            return true;
        

        Window win = getWindow();
        if (win.superDispatchKeyEvent(event)) 
            return true;
        
        View decor = mDecor;
        if (decor == null) decor = win.getDecorView();
        return event.dispatch(this, decor != null
                ? decor.getKeyDispatcherState() : null, this);
    


 上层拦截可以复写就可以了,

   
 @Override    
     public boolean dispatchKeyEvent(KeyEvent event) 
            Log.i("dispatchKeyEvent",
                    "dispatchKeyEvent(), action=" + event.getAction() + " keycode="
                            + event.getKeyCode());
            
            return super.dispatchKeyEvent(event);
            
        


  
   
 如果我们想消费某个按键事件我们可以复写onKeyDown()或者onKeyUp();

    
@Override
    public boolean onKeyUp(int keyCode, KeyEvent event) 
        return super.onKeyUp(keyCode, event);
        
        // // TODO: 2014-10-30  
    



**总结下整个流程:**

首先触发dispatchKeyEvent()
再次onKeyDown 如果按下紧接着松开,则是俩步

紧跟着触发dispatchKeyEvent 
然后触发onUserInteraction 
再次onKeyUp


本文出 http://blog.csdn.net/sk719887916处:


Focus


 requestFocus():强制设置一个焦点到指定的view或它的一个子类,(前提是android:focusable为true能够获得焦点)
 
-    android:focusable:设置一个控件能否获得焦点
-    android:background:设置在作为背景的drawable
-    android:nextFocusDown:定义下一个获得焦点的控件当按下键时
-    android:nextFocusUp:定义下一个获得焦点的控件当按上键时
-    android:nextFocusLeft:定义下一个获得焦点的控件当按左键时
-    android:nextFocusRight:定义下一个获得焦点的控件当按右键时


可以通过焦点事件变化操作view,    View.setFocusable(true);   设置控件是否可以获得焦点,同时会触发 setOnFocusChangeListener() 

 view.setOnFocusChangeListener()   
          public void onFocus(boolean Focus)   
                   if( Focus )  
                    //获得焦点
                     else  
                       //失去焦点
                         



三 instrumentation


   Instrumentation和Activity有点类似,只不过Activity是需要一个界面的,而Instrumentation并不是这样的,我们可以将它理解为一种没有图形界面的,具有启动能力的,用于监控其他类(用Target Package声明)的工具类。Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作,代替认为操作,主要用于自动测试框架。

        instrumentation发送键盘鼠标事件:Instrumentation提供了丰富的以send开头的函数接口来实现模拟键盘和鼠标,如下所述:

    sendCharacterSync(int keyCode)            //用于发送指定KeyCode的按键

   sendKeyDownUpSync(int key)                //用于发送指定KeyCode的按键

   sendPointerSync(MotionEvent event)     //用于模拟Touch

   sendStringSync(String text)                   //用于发送字符串

 

   

                     Instrumentation inst=new Instrumentation();
                     inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(),SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 10, 10, 0));
                     inst.sendPointerSync(MotionEvent.obtain(SystemClock.uptimeMillis(),SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, 10, 10, 0));
  本文出 处:http://blog.csdn.net/sk719887916/article/details/44781475

但是TV的遥控器模拟鼠标并非需要此类,也没这么复杂,具体可以拦截事件,发送模拟事件即可,欢迎阅读。

 安卓实现遥控器模拟鼠标请阅读:《安卓TV开发(九) Android之模拟事件点击并实现遥控器模模拟鼠标操作

如果可以 鼓励下作者:



以上是关于Android TV开发焦点移动源码分析的主要内容,如果未能解决你的问题,请参考以下文章

安卓Tv开发移动智能电视之焦点控制(按键事件)

Android TV 焦点与按键事件分析

Android TV 焦点与按键事件分析

Android TV按键焦点原理浅谈

Android焦点事件分发与传递机制

parameter must be a descendant of this view 报错解决方案及Android 获取View焦点源码分析