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

Android TV开发焦点移动源码分析,第1张

点可以理解为选中态,在Android TV上起很重要的作用。一个视图控件只有在获得焦点的状态下,才能响应按键的Click事件

相对于手机上用手指点击屏幕产生的Click事件, 在TV中通过点击遥控器的方向键来控制焦点的移动。当焦点移动到目标控件上之后,按下遥控器的确定键,才会触发一个Click事件,进而去做下一步的处理

在处理焦点的时候,有一些基础的用法需要知道。

首先,一个控件isFocusable()需要为true才有资格可以获取到焦点。如果想要在触摸模式下获取焦点,需要通过setFocusableInTouchMode(boolean)来设置。也可以直接在xml布局文件中指定:

keyEvent 分发过程:

而当按下遥控器的按键时,会产生一个按键事件,就是KeyEvent,包含“上”,“下”,“左”,“右”,“返回”,“确定”等指令。焦点的处理就在KeyEvent的分发当中完成。

首先,KeyEvent会流转到ViewRootImpl中开始进行处理,具体方法是内部类 ViewPostImeInputStage 中的 processKeyEvent :

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

这里也是可以做焦点控制,最好是在 eventgetAction() == KeyEventACTION_DOWN 进行

因为android 的 ViewRootlmpl 的 processKeyEvent 焦点搜索与请求的地方 进行了判断if (eventgetAction() == KeyEventACTION_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(ViewGroupFOCUS_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中查找焦点。

FocusFinderfindNextFocus 查找焦点

DecorView →PhoneWindow →Activity→ViewGroup→view

下面我们根据按键事件的分发流程,抽丝剥茧,逐一分析。

private int processKeyEvent(QueuedInputEvent q)

1、DecorViewjava

2、Activityjava

3、ViewGroupjava

4、Viewjava

通过该方法,接收器receiver的onKeyDown、onKeyUp、onKeyLongPress、onKeyMultiple等方法将被回调。

在上述按键事件的入口中提到的ViewRootImpl中

如果mViewdispatchKeyEvent(event)返回true,则结束事件分发;

如果返回false,则调用如下方法

继续执行后续的焦点导航流程。

焦点导航的总体流程就是:

1、View focused = mViewfindFocus();//从视图树的顶层,即DecorView一层一层的递归查找当前获得焦点的view

2、View v = focusedfocusSearch(direction);根据导航的方向查找下一个可获取焦点的view

3、vrequestFocus(direction, mTempRect)请求获取焦点

4、vrequestFocus(direction,mTempRect)内部,调用mParentrequestChildFocus(this, focused)逐层递归向上级通知

ViewRootImpljava

mView即DecorView,从DecorView开始,一层一层的向下递归查找当前获得焦点的view

找到了当前获得焦点的focused,调用该焦点view的focusSearch(direction)方法查找direction方向上下一个将要获取焦点的view。

focusedfocusSearch(direction)实际上会调用mParentfocusSearch(this, direction)方法,层层递归,直到调用到DecorView的focusSearch(this, direction)方法。

而DecorView继承ViewGroup,实际上最终会调用到FocusFindergetInstance()findNextFocus(this, focused, direction),this 就是DecorView对象。

最终会调用到DecorView父类ViewGroup中的FocusFindergetInstance()findNextFocus(this, focused, direction);

ViewGroupjava

FocusFinderjava

搜索到下一个获取焦点的view后,调用该viewrequestFocus(direction, mTempRect)方法

注意:调用requestFocus(direction, mTempRect)需要区分调用者。

如果是ViewGroup,则会更加焦点获取策略,实现父View和子View之间获取焦点的优先级。

如下是ViewGroupjava 和Viewjava 中requestFocus方法是实现:

ViewGroupjava

Viewjava

View获取到焦点后,会调用mParentrequestChildFocus(this, focused)逐层递归向上级通知

ViewGroupjava

onclick 是鼠标点击事件。。

focus 是获得焦点事件件。。

拿input框来举例

只有你用鼠标点击文本框时,它才会触发onclick事件

只要你的光标在文本框中,他就会触发focus事件。比如用tab键可以移动光标线到文本框中,也触发了focus事件。。,但是并会触发onclick,onclick只在你点击它时才触发。

你点击文本框后,光标那个竖线自然在文本框中了,所以他触发onclick的同时,也会触发focus事件。

<head>

<meta charset="UTF-8">

<title>无缝滚动</title>

<style type="text/css">

body,ul,li{margin:0;padding:0}

ul{list-style:none;}

slide{

width:500px;

height:100px;

border:1px solid #ddd;

margin:20px auto 0;

position:relative;

overflow:hidden;

}

slide ul{

position:absolute;/相对于slide进行绝对定位/

width:1000px;/比slide宽度大一倍,做这种连续滚动效果的时候,要在后面把内容复制一份/

height:100px;

left:0;/可以改变该值让其动起来/

top:0;

}

slide ul li{

width:90px;

height:90px;

margin:5px;

background-color:#ccc;

line-height:90px;

text-align: center;

font-size:30px;

float:left;

}

btns{

width:500px;

height:50px;

margin:10px auto 0;

}

</style>

<script type="text/javascript" src="/js/jquery-1124minjs"></script>

<script type="text/javascript">

$(function(){

var $ul = $('#slide ul');

var left = 0;

var deraction = 2;//每次滚动的距离

//内容为两套li

$ulhtml($ulhtml() + $ulhtml());

//反复循环定时器,30ms动一下可以看起来比较平滑

var timer = setInterval(move, 30);

function move(){

left -= deraction;

//当第2套li完全显示出来的时候,整个移回原点重新移动,实现向左连续滚动

if(left < -500){

left = 0;

}

//瞬间跳回,实现向右连续滚动

if(left > 0){

left = -500;

}

$ulcss({left: left});

}

$('#btn1')click(function() {

deraction = 2;

});

$('#btn2')click(function() {

deraction = -2;

});

$('#slide')mouseover(function() {

clearInterval(timer);

});

$('#slide')mouseout(function() {

timer = setInterval(move,30);

});

})

</script>

</head>

<body>

<div class="btns">

<input type="button" name="" value="向左" id="btn1">

<input type="button" name="" value="向右" id="btn2">

</div>

<div class="slide" id="slide">

<ul>

<li>1</li>

<li>2</li>

<li>3</li>

<li>4</li>

<li>5</li>

</ul>

</div>

</body>

鼠标移入要做的事情mouseover

鼠标移出要做的事情mouseout

<head>

    <meta charset="UTF-8">

    <title>鼠标移入移出</title>

    <style type="text/css">

        box{

            width: 200px;

            height: 200px;

            background-color: gold;

            margin: 100px auto 0;

        }

        son{

                width: 100px;

                height: 100px;

                background-color: green;

        }

    </style>

    <script type="text/javascript" src="js/jquery-1124minjs"></script>

    <script type="text/javascript">

         $(function(){

        /进入子元素也触发/

        /$('#div1')mouseover(function() {

        $(this)animate({marginTop: 50});//stop()

        });

        $('#div1')mouseout(function() {

        $(this)animate({marginTop: 100});//stop()

    });/

        /进入子元素不触发/

        /$('#div1')mouseenter(function() {

        $(this)stop()animate({marginTop: 50});//

        });

        $('#div1')mouseleave(function() {

        $(this)stop()animate({marginTop: 100});//

        });/

        /通过hover(mouseenter+mouseleave)实现简写/

        $('#div1')hover(function() {

        $(this)stop()animate({marginTop: 50});

        }, function() {

        $(this)stop()animate({marginTop: 100});

            });

        })

    </script>

</head>

<body>

        <div id="div1" class="box">

                <div class="son"></div>

        </div>

</body>

创建一个子元素 促使鼠标移入到子元素才能移动

是由于时间mouse影响

则要使用mouseenter和mouseleave进行改变

也可以运用hover简化

autofocus

表单元素的值发生变化

<head>

                <meta charset="UTF-8">

                <title>input框事件</title>

                <style type="text/css">

                </style>

                <script type="text/javascript" src="js/jquery-1124minjs"></script>

                <script type="text/javascript">

                     $(function(){

                    // //一开始就获取焦点,相当于设置了autofocus自动获取焦点了(HTML5 新增表单控件属性)

                    // $('#txt01')focus();

                    // //文本框获取焦点的时候(有光标闪烁的时候)

                    // $('#txt01')focus(function() {

                    // alert('focus');

                    // });

                    // //失去焦点的时候(光标离开的时候)

                    // $('#txt01')blur(function() {

                    // alert('blur');

                    // });

                    // //输入框内容发生变化的时候,失去焦点后触发,可用于注册时验证用户名是否已存在

                    // $('#txt01')change(function() {

                    // alert('change');

                    // });

                    //松开键盘按键就触发

                    $('#txt01')keyup(function() {

                    alert('keyup');

                    });

                })

                </script>

</head>

<body>

        <input type="text" id="txt01">

</body>

<head>

            <meta charset="UTF-8">

            <title>jQuery其他事件</title>

            <style type="text/css">

            </style>

            <script type="text/javascript" src="js/jquery-1124minjs"></script>

            <script type="text/javascript">

            // // JS原生写法

            // windowonload = function(){

            // }

            // / /jQuery写法,等同于上面写法

            // $(window)load(function(){

            // })

            // //ready的写法

            // $(document)ready(function(){

            // })

            // //ready的简写

            // $(function(){

            // })

            // 窗口改变尺寸的时候,会高频触发

            $(window)resize(function() {

            consolelog('3');

            });

            </script>

</head>

<body>

            <div id="div1"></div>

</body>

鼠标移入要做的事情mouseover

鼠标移出要做的事情mouseout

title: '深入理解android2-WMS,控件-图床版'

date: 2020-03-08 16:22:42

tags:

typora-root-url: /深入理解android2-WMS-控件

typora-copy-images-to: /深入理解android2-WMS-控件

WMS主要负责两个功能, 一是负责窗口的管理,如窗口的增加删除,层级二是负责全局事件的派发如触摸点击事件

先简单介绍几个重要的类

IWindowSession 进程唯一的是一个匿名binder通过他向WMS请求窗口 *** 作

surface 绘画时,canvas会把内容绘制到surface里surface是有surfaceFlinger提供给客户端的

WindowManagerLayoutParams 集成自ViewGroupLayoutParams用来指明client端的窗口的一些属性最重要的是type 根据这属性来对多个窗口进程ZOrder的排序

windowToken向WMS添加的窗口令牌每个窗口都要有一个令牌

IWindow 是client提供给WMS的继承自binderWMS通过IWindow对象来主动发起client端的事件

窗口的本周就是进行绘制所使用的surface,客户端向WMS添加窗口的过程,就是WMS为客户端分配surface的过程

ui框架层就是使用surface上绘制ui元素及响应输入事件

WMS负责surface的分配窗口的层级顺序

surfaceFlinger负责将多个Surface混合并输出

WMS有SystemServer 进程启动他和AMS其实是运行于一个进程中的只是分别有各自的线程

上边传入了两个handler这里就使用windowManager的handler来创建WMS也就是在一个handerThread线程中创建

用来管理每个窗口的事件输入也就是把输入事件转发到正确的窗口

能获取显示系统的同步信号用来驱动动画的渲染

所有窗口动画的总管,在mChoreographer的驱动下渲染所有动画

只有PhoneWindowManager一个实现定义了很多窗口相关的策略是最重要的成员,比如负责窗口的zorder顺序

zorder就是各个窗口在z轴的值越大越在屏幕上层窗口就是根据zorder值一层一层堆在一起

可以绘制的屏幕列表默认是只有1个

管理所以窗口的显示令牌token,每个窗口都要属于一个token这里的IBinder 是

表示所有Activity的token AppWindowToken是WindowToken的子类,这个list的顺序和AMS中对mHistory列表中activity的顺序是一样的 反应了系统中activity的叠加顺序也就是说所有窗口都有WindowToken而Activity对应的窗口则多了AppWindowToken

每个窗口都对应一个WindowState存储改窗口的状态信息(这就和AMS中对每个activity抽象成ActivityRecord一样)

这里的iBinder 是IWIndow类

Session 是WMS提供给客户端来与WMS进行交互的,这是匿名binder为了减轻WMS的负担客户端通过IWindowManageropenSession 拿到他的代理然后通过代理与WMS交互每个进程唯一

客户端通过IWindowSessionadd 来添加窗口 iWindowSession 是同aidl形成的最终到了WMSaddWindow

这里总的来说就是确立了客户窗口的WindowTokenWindowState和DisplayContent 并都保存了起来同时根据layoutparamstype进行了些窗口等级的判断

WindowToken将同一个应用组件的窗口安排在一起一个应用组件可以是Activity,InputMethod

WindowToken使应用组件在变更窗口时必须与自己的WindowToken匹配

这里主要是为了处理窗口的层级关系而设立的

只要是一个binder对象都可以作为token向wms声明wms会把这个binder对应起一个WindowToken其实就是把客户端的binder和wms里的一个WindowToken对象进行了绑定

因为Activity比较复杂,因此WMS为Activity实现了WindowToken的子类 appwindowtoken同时在AMS启动Activity的ActivityStackstartActivityLocked里声明token

然后在activityStackrealStartActivityLocked里在发给用户进程,然后用户在通过这个binder和WMS交互时带过来

activity则在 activityStack 线程的handleResumeActivity 里把Activity 对应的窗口,加入到wMS中

取消token 也是在AMS中 ,也就是说, AMS负责avtivity的token向WMS的添加和删除

当然Activity的 rappToken 是 IApplicationTokenStub ,他里边有一系列的窗口相关的通知回调

这里总结下 AMS在创建Activity的ActivityRecord时,创建了他的appToken,有把appToken传送给WMSWMS对应匹配为APPWindowToken,最后还把这个appToken发送给activity因此AMS就通过ActivityRecord就可有直接 *** 作WMS对该窗口的绘制如图

每个window在WMS里都抽象成了WindowState他包含一个窗口的所有属性WindowState在客户端对应的则是iWidowstub类iWidowstub有很多窗口通知的回调

WindowState被保存在mWindowMap里这是整个系统所有窗口的一个全集

HashMap<IBinder, WindowToken> mTokenMap 这里是 IApplicationToken(客户端)和WindowToken的映射

HashMap<IBinder, WindowState> mWindowMap 这里是IWidow(客户端)和WindowState的映射,并且WMS通过这个IWindow 来回调客户端的方法

上图可以看出每个activity 只有一个ActivityRecord也只有一个AppToken,也就只有一个WindowToken而一个acitvity可能有多个窗口每个窗口对应一个WindowState

WindowToken用来和AMS交换 而WindowState对应的iWindow则是WMS来与客户端交互的

窗口显示次序就是窗口在Z轴的排了因为窗口是叠加在一起的因此就需要知道哪些显示在上边,哪些在下边这个由WindowState构造时确定

可见分配规则是由WindowManagerPolicy mPolicy来决定的产生 mBaseLayer和mSubLayer mBaseLayer决定该窗口和他的子窗口在所有窗口的显示位置 mSubLayer决定子窗口在同级的兄弟窗口的显示位置值越高显示约靠上

WindowState 产生了他自己这个窗口的layer值后在添加窗口的时候就会把所有窗口按layer排序插入mWindows列表中,在通过 adjustWallpaperWindowsLocked();进行层级调整

当客户端通过IWindowsessionadd后,客户端还没有获得Surface只有在执行IWindowsessionrelayout后客户端才获得了一块Surface IWindowsessionrelayout根据客户端提供的参数,为客户端提供surface具体实现是WMSrelayoutWindow

总的来说就是根据用户传入的参数,更新WindowState然后遍历所有窗口布局在设置合适的Surface尺寸,在返回给用户端

performLayoutAndPlaceSurfacesLocked 会循环调用6次里边的逻辑大概如下

这里主要下,因为之前加了锁requestTraversalLocked他又会重复执行performLayoutAndPlaceSurfacesLocked();因此会重复循环执行布局

布局这部分就记个原理吧

布局完成后客户端的尺寸和surface都得到了就可以绘制 了WMS会通知客户端布局发送变化

总结,WMS 负责管理所有的窗口包括系统窗口和APP窗口,而窗口必须有一个WindowToken所为标识符同时WMS为每个窗口创建一个WindowState类,这是窗口在服务端的抽象WindowState则绑定了一个客户端的IWindow类,WMS通过这个IWindow 向APP发送消息

AMS在启动Activity的时候把ActivityRecordtoken 通过wmsaddtoken 注册到WMS又把这个token发送到APP端因此三方可以通过这个token正确找到对应的数据

WMS负责给所以窗口按ZOrder排序,确定窗口的尺寸,提供绘画用的surface

Activity的窗口是先wmsaddtoken 建立windowToken关系 wmsaddWindow 添加串口, WMSrelayout获取surface 完成

一个windowToken对应一个Activity 但是可能对应多个windowSatate也就是对应多个窗口

是view树的根实现类是viewRootImpl但是他不是view他是用来和WMS进行交流的管理者viewRootImpl内部有个IWindowSession,是WMS提供的匿名binder,同时还有个iWindow子类,用来让WMS给viewr发消息 view通过ViewRoot向WMS发消息WMS在通过IWIndow 向APP发消息 每个View树只有一个ViewRoot,每个Activity也只有一个ViewRoot UI绘制,事件传递都是通过ViewRoot

实现类是PhoneWindow Activity和View的沟通就是通过WindowActivity实现window的各种回调一个Activity也对应一个PhoneWindow也对应一个View树

Docerview 就是View树的根这是一个View 他由PhoneWindow管理 下文的WindowManager也由phoneWindow管理

他还管理window的属性 WindowManagerlayoutparams

他是一个代理类他集成自ViewManager他的实现是WindowManagerImpl这是每个Activity都有一个但是他只是把工作委托给了 WindowManagerGlobal来实现 他负责添加删除窗口,更新窗口并控制窗口的补件属性WindowManagerLayoutparams

是进程唯一的负责这个进程的窗口管理他里边有三个集合保存这个进程所有窗口的数据这里的每个数据根据index得到的是同一个Activity属性所有的WindowManager的 *** 作都转到他这里来

private final ArrayList<View> mViews 每个view是个跟节点

private final ArrayList<ViewRootImpl> mRoots view对应的viewRoot

private final ArrayList<WindowManagerLayoutParams> mParams 窗口的layoutparams属性每个窗口一个

对于一个acitivity对象永远对应一个PhoneWindow,一个WindowManagerImpl,一个WMS端的APPWindowToken,一个AMS里的ActivityRecord(但是如果一个activity在栈里有多个对象,就有多个ActivityRecord和AppWindowToken),acitvity 的默认窗口的view树是DocerView

一个窗口 对应一个ViewRoot,一个View树一个WindowManagerLayoutParams,一IWindow(WMS回调app)一个WSM端的WindowSatate

但是一个Activity可以有多个窗口,因此对应WMS里可能有多个WindowSatate这些WindowState都对应一个AppWindowToken

一个Activity可能被加载多次因此在AMS中可能有多个ActivityRecord对应这个activit的多个对象

但是一个进程则对应一个WindowManagerGlobal一个ActivityThread(主线程)一个ApplicationThread(AMS调用app)一个iWindowSession(viewroot向WMS发消息)

这里的区别就是 app与AMS 的交互是以进程之间进行通信而App与WMS的交互则是以窗口作为通信基础

当Activity由AMS启动时ActivityThread 通过handleResumeActivity执行resume相关的 *** 作这个函数首先是执行activityresume, 此时activity 对应的view树已经建立完成(oncreate中建立,PhoneWindow也创建了)需要把activity的窗口添加到WMS中去管理

这里的wm是WindowManager是每个activity一个他内部会调用WindowManagerGlobaladdView

WindowManagerGlobaladdView

这里会为窗口创建ViewRootImpl 并把viewViewRootImplWindowMaLayoutParams都保存在WindowManagerGlobal中, 并通过ViewRootImpl向WMS添加窗口

如果这个窗口是子窗口(wparamstype >= WindowManagerLayoutParamsFIRST_SUB_WINDOW &&

wparamstype <= WindowManagerLayoutParamsLAST_SUB_WINDOW)

就把子窗口的token设为父窗口的token否则就是所属activity的token

在来个图

在这里我们看到我们通过mWindowManager = (WindowManager) mContextgetSystemService(ContextWINDOW_SERVICE); 拿到的并不是远程的WMS而是本地的WindowManagerImpl 他又把请求转发给WindowManagerGlobal ,而WindowManagerGlobal作为进程单实例又是吧请求转给对应窗口的ViewRootImplViewRootImpl通过WMS的IWindowSession 把数据发给WMS

ViewRootImpl用来沟通View和WMS并接受WMS的消息这是双向的binder通信作为整个空间树的根部,控件的测量,布局,绘制,输入时间的派发都由ViewRootImpl来触发

ViewRootImpl由WindowManagerGlobal创建,是在activityThreadhandleResumeActivity时,先执行activityresume在调用wmaddView 就会执行WindowManagerGlobaladdView里创建ViewRootImpl,此时是在ui线程中

ViewRootImpl里的mView属性host属性,就是view树

添加窗口时通过requestLayout();向ui线程发送消息最后回调到ViewRootImplperformTraversals他是整个ui控件树,measurelayoutdraw的集合

这里分为五个阶段

预测量阶段进行第一次测量,获得viewgetMeasuredWitdh/Height,此时是控件树期望的尺寸会执行View的onMeasure

布局阶段,根据预测量的结果,通过IWindowSessionrelayout向WMS请求调整窗口的尺寸这会使WMS对窗口重新布局,并把结果返回给ViewRootImpl

最终测量阶段, 预测量的结果是view树期望的结果WMS可能会进行调整,在这里WMS已经把结果通知了ViewRootImpl因此这里会窗口实际尺寸performTraversals进行布局view及子类的onMeasure会被回调

布局阶段 测量完成后获得空间的尺寸,布局要确定控件的位置,View及子类的onLayout会被回调

绘制阶段,使用WMS提供的surface进行绘制,View及子类的onDraw会被回调

通常我们看到的都是 先measure,在layout在draw 这里看到其实measure先得到期望值,在和WMS沟通WMS在调整后,返回确定值,在根据确定值进行mesure

measureHierarchy里会通过三次协商执行performMeasure 来确认合适的尺寸

performMeasure 会调用view 的measure 优会调用onMeasure 我们可重写onMeasure来实现测量而measure 方法是final的onMeasure 的结果通过setMeasuredDimension方法保存

对于view onMeasure比较容易 对于ViewGroup则还要遍历调用他所以子view的measure 并且需要考虑padding和子view 的margin padding是控件外内边距 margin 是控件外边距

ViewGroup需要先测量完子view在根据子view的测量值得到自己的宽高举例,如果只有一个子view那么ViewGroup的宽= 子view的宽+子view的margin+viewg的padding 至少是这个值

继续回到performTraversals

这里就是提前测量了一下得到控件树希望的尺寸大小,

通过relayoutWindow来布局窗口 ViewRootImpl 通过IWindowSession 来通知WMS进行窗口布局

这里主要下 调用WMS后WMS会调整窗口的尺寸 同时会生成surface返回给ViewRootImpl 因此后续的绘画就有了画布了可以看到最后的参数是mSurface这是本地的surface 这里会和wms的进行绑定

接下来继续performTraversals,绑定WMS返回的surface然后更新尺寸

最后进行最终测量 上边过程太乱了 了解下就行还是看常见的控件绘制流程

绘制由viewRootImplperformTraversals触发, 抽取出来后,就是这样

就是直接调用view树的根的measure方法 传入到View

该方法是final 意味着无法重写这里又会调用onMeasure

因此对于view在onMeasure中调整好高度,通过setMeasuredDimension设置好自己的测量宽高就可以了

对应ViewGroup则在onMeasure中,先要遍历子view调用他们的measure(注意一定是调用子类的measure,measure又会调用onMeasure), 子view宽高都知道后,在根据子view的宽高来设置自己也就是ViewGroup的宽高受子view影响

可以看到view的measure又调用了onMeasure, 如果是view 则可以直接重新onMeasure来设定大小而对于ViewGroup, 则需要重写onMeasure来先遍历子view设定大小然后再设定viewGroup的大小 ViewGroup并没有重写onMeasure因为每个ViewGroup要实现的效果不同,需要自己完成但ViewGroup提供了几个方法供ViewGroup的继承类来遍历子view

view的宽高由自己的layoutParams和父view提供的 widthMeasureSpec|heightMeasureSpec共同决定

View 自己的宽高,是保存在LayoutParams中对,以宽举例 LayoutParamswidth 有三种情况,精确值(就是指定大小),MATCH_PARENT WRAP_CONTENT,模式则有fuview提供有 unspecified,exactly,at_most三种

匹配如下

其实这个很好理解 如果子view自己指定了宽高就用他的值就可以如果子view是match_parent那就使用父view提供的宽高 如果子view是wrap_content,那就不能超过父view的值

看下ViewGroup为子view绘制而提供的方法,可以看到ViewGroup会减去padding和margin,来提供子view的宽高

上步measure过程未完成后,整个view书的 测量宽高都得到了也就是viewgetMeasuredWidth()和getMeasuredHeight()

performLayout中会调用mViewlayout 这样就把事件从ViewRootImpl传递到了view而layout中又会调用onLayoutViewGroup需要重写onLayout为子view进行布局,遍历调用子view的layout因此就完成整个view树的laylut过程

竖向的实现, 竖向的就行把view从上到下一次排开

这里注意区分measure过程是先得到子view的测量值,在设定父ViewGroup的值而layout过程则是先传入父view的左上右下值,来计算子view的左上右下的位置值这里应该具有普遍性但不知道是否绝对

performDraw 中的调用draw又调用mViewdraw然后就进入view树的绘制了

view的draw 又会调用onDraw ,viewGroup又调用dispatchDraw()把draw分发到子view里 绘制的画布就是canvas 这是从surfacelockCanvas中获得的一个区域

而在ViewGroupdispatchDraw中重要的一点是getChildDrawingOrder 表示子view的绘制顺序默认是与ziview的添加顺序一样我们也可以改变他最后绘制的会显示在最上边,而这也影响view的事件传递顺序

viewdraw 就是一层一层的画内容先画北京,在onDraw在画装饰什么的

canvastranslate(100,300)通过平移坐标系使之后的内容可以直接在新坐标系中绘制

这就是ViewGroup在向子view传递canvas的时候方便多了 会之前先对其ziview的左上角那么子view就可以直接从自己坐标轴的(0,0)开始绘制, 绘制完成后ViewGroup在还原原有坐标系

canvassave canvasrestore 用来保存还原坐标系

viewinvalidate

当某个view发送变化需要重绘时,通过viewinvalidate向上通知到ViewRootImpl从这个view到ViewRootImpl的节点都标记为藏区域dirty area ViewRootimpl再次从上到下重绘时,只绘制这些脏区域效率高

本来安卓兼容使用键盘,也支持,触摸二者的输入事件派发不一样使用键盘时会有个控件处于获得焦点状态处于触摸模式则由用户决定 因此控件分为两类任何情况下都能获得焦点如输入文本框只有在键盘 *** 作时才能获得焦点如菜单,按钮

安卓里有触摸模式当发送任意触摸时进入触摸模式当发送方向键和键盘或者执行ViewrequestRocusFromTouch时,退出触摸模式

获取焦点 viewrequest

先检查是否能获取焦点,

然后设置获取简单的标记,

向上传递到ViewRootimpl保证只能有一个控件获取焦点

通知焦点变化的监听者

更新view的drawable状态,

requestChildFocus会把焦点事件层层上报取消原来有焦点的控件最后的效果就是从viewrootimpl中到最终有焦点的view构成一条 mFoucued 标识的链条来个图就明白了每个view的mFocused总是指向他的直接下级

获取focus的传递是从底层view到顶层的ViewRootImpl而取消focus测试从顶层的ViewRootimpl到底层原来那个获得焦点的view

而如果是ViewGroup请求获取焦点,会根据FLAG_MASK_FOCUSABILITY特性来做不同方式,分别有先让自己获取焦点,或者安卓view的索引递增或者递减来匹配view

ViewRootImpl 中的WindowInputEventReceiver接受输入事件他会把事件包装成一个QueuedInputEvent然后追加到一个单链表的末尾接着重头到尾的处理输入事件,并通过deliverInputEvent完成分发这里会把单链表所有事件都处理完

deliverInput中又会把触摸事件执行到通过 ViewPreImeInputStageprocessKeyEvent 转入mViewdispatchPointerEvent(event)这里又进入 dispatchTouchEvent

MotionEvent是触摸事件的封装getAction可以拿到动作的类型和触控点索引号

getX(),getY()拿到动作的位置信息通过getPointID拿到触控点的id 动作以down 开头跟多个move最后是up

,当事件返回true表示事件被消费掉了

在JavaScript中,focus()虽然比较罕见,但还是比较有用的,

主要是用于获取焦点,说白了,就是自动把光标放到此组件上面,无须用户再次 *** 作。

直接用一个例子说明问题:

上述代码,设置在打开网页5秒之后,无须用户 *** 作,focus()方法会自动将光标放到id为text_2这个文本框。

在IE中,windowselffocus();还会起作用。5秒后,窗口还会自动获得焦点,切换到其他窗口,或者其他程序,等5秒,IE浏览器还会自动返回到前台,成为活动窗口,而在野狐禅等其他浏览器中不适用。火狐、谷歌等为了安全性,设置浏览器无法实现控制用户行为。在其他程序应用时,让浏览器获取窗口焦点这个是无法做到的。最少在浏览器内部js无法做到。

不过对于chrome或者firefox,safari之类的浏览器可以用过Notification这个类来做一个d出通知。然而notification必须要支持html5,太麻烦。目前要求窗口获取焦点,基本上用alert去实现。

由于IE的windowselffocus();是其作用的,不被禁止的,配合onblur()获取焦点事件,还能构造一个死循环,让IE崩溃,如下的代码:

关于Java

资料来源:百度百科:Java

1、你写的autofocus是让输入框默认获得焦点,并不是一直获得焦点。

2、你描述的需求是本页面只有这个输入框可以输入?如果是这个意思,那就把输入框以外的所有控件都disabled就可以了,这样整个页面就只有这个输入框可以获得焦点了。

3、如果你是为了在某些时候特意控制不许点输入框以外的控件(比如验证码必须输入后才能干干别的),那么你需要在其他所有控件的focus事件里写上这个逻辑。比如,if(输入框内容为空){输入框获得焦点}

简单一点理解,在移动应用中,焦点就是当前正在处理事件的位置。在手机应用中,最有可能用到焦点的就是EditText,如果同一个界面中有多个EditText,通常情况下同一时间只有一个能够输入内容,此时,这个EditText就获取了焦点。

在Android中,对焦点的设置分为两种情况,TouchMode和非TouchMode。现在的手机基本都是触摸屏,我们用手指触摸屏幕来 *** 作Android应用时,处于TouchMode。除了TouchMode之外,还有非TouchMode,利用外接设备来 *** 作应用。比如键盘。使用Genymotion模拟器的时候,一个界面上有多个控件时,可以用电脑tab键来进行移动,被选中的控件会高亮显示,这时候就是非TouchMode,被选中的控件获得了焦点。

在手机应用中,用到焦点的时候并不多,但是TV应用中,需要用遥控器来 *** 作选中控件,这时候就需要对焦点进行处理了。关于焦点,常用方法如下:

在View类中, isFocusable() 和 isFocusableInTouchMode() 获取到的结果都是false,也就是说,直接继承自View的控件是不能获取焦点的。我们常用控件中对这两个方法进行了改写,比如EditText,这两个方法都是true,而Button则只有 isFocusable() 返回true。这也就是为什么我们用tab键选取Button的时候能够高亮显示,而鼠标点击(模拟触控)的时候不能高亮显示的原因了。如果想在点击的时候也能高亮显示Button,需要手动设置 setFocusableInTouchMode(true) ,就可以了。

如果想对控件的焦点状态进行监听,需要设置 setOnFocusChangeListener() ,只要控件的焦点状态发生变化(获得或者失去焦点),都会调用 onFocusChange 方法

关于焦点的移动,默认的算法会寻找指定方向上最近的可以获取焦点的元素(非TouchMode)。另外在创建控件的时候,也可以指定寻找焦点的方向,设置nextFocusDown、nextFocusLeft、nextFocusRight 和 nextFocusUp的值为指定元素就可以了。看以下例子:

这里指定了上面的button向上寻找焦点时,下一个元素是id为bottom的元素,也就是说,上面的Button在获取了焦点之后,继续按向上键,系统会将焦点移动到id为bottom的元素上,而不是继续向上。

在开发手机应用的过程中,对焦点的处理并不多,它与事件是两个不同的体系,通常情况下焦点和事件是相互独立并不冲突。但是在Button的点击事件中会有一点问题。如果我们队一个button设置了 setFocusableInTouchMode(true) ,使他可以获取焦点,那么我们点击这个button的时候,第一次点击并不会执行 onClick() 方法,而是执行 onFocusChange() 。第二次点击的时候才会执行 onClick() 方法。看起来好像 onFocusChange() 消耗了点击事件,实际上并不是的。

这个问题我们看一下源码就清楚了:

onClick() 方法是在onTouchEvent的ACTION_UP里调用的,看一下View的onTouchEvent方法:

可以看到,只有当focusTaken为false的时候才会执行onClick,focusTaken的值默认是false的,但是在 isFocusable() && isFocusableInTouchMode() && !isFocused() 为true的时候,会去 requestFocus 获取焦点,并将值赋给focusTaken。

关键在于 isFocused() ,如果当前Button没有获取焦点, isFocused() 返回false, !isFocused() 值为ture,Button就会去获取焦点,从而导致 focusTaken 为true, onClick 方法就不会执行了,只有Button已经获取了焦点的时候才会执行onClick方法。

以上就是关于Android TV开发焦点移动源码分析全部的内容,包括:Android TV开发焦点移动源码分析、Android TV 按键焦点事件分发流程详解、文本框 也有onClick事件等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9562340.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-29
下一篇 2023-04-29

发表评论

登录后才能评论

评论列表(0条)

保存