android获取焦点
A. Android TV 焦点原理源码解析
相信很多刚接触AndroidTV开发的开发者,都会被各种焦点问题给折磨的不行。不管是学技术还是学习其他知识,都要学习和理解其中原理,碰到问题我们才能得心应手。下面就来探一探Android的焦点分发的过程。
Android焦点事件的分发是从ViewRootImpl的processKeyEvent开始的,源码如下:
源码比较长,下面我就慢慢来讲解一下具体的每一个细节。
dispatchKeyEvent方法返回true代表焦点事件被消费了。
ViewGroup的dispatchKeyEvent()方法的源码如下:
(2)ViewGroup的dispatchKeyEvent执行流程
(3)下面再来瞧瞧view的dispatchKeyEvent方法的具体的执行过程
惊奇的发现执行了onKeyListener中的onKey方法,如果onKey方法返回true,那么dispatchKeyEvent方法也会返回true
可以得出结论:如果想要修改ViewGroup焦点事件的分发,可以这么干:
注意:实际开发中,理论上所有焦点问题都可以通过给dispatchKeyEvent方法增加监听来来拦截来控制。
(1)dispatchKeyEvent方法返回false后,先得到按键的方向direction值,这个值是一个int类型参数。这个direction值是后面来进行焦点查找的。
(2)接着会调用DecorView的findFocus()方法一层一层往下查找已经获取焦点的子View。
ViewGroup的findFocus方法如下:
View的findFocus方法
说明:判断view是否获取焦点的isFocused()方法, (mPrivateFlags & PFLAG_FOCUSED) != 0 和view 的isFocused()方法是一致的。
其中isFocused()方法的作用是判断view是否已经获取焦点,如果viewGroup已经获取到了焦点,那么返回本身即可,否则通过mFocused的findFocus()方法来找焦点。mFocused其实就是ViewGroup中获取焦点的子view,如果mView不是ViewGourp的话,findFocus其实就是判断本身是否已经获取焦点,如果已经获取焦点了,返回本身。
(3)回到processKeyEvent方法中,如果findFocus方法返回的mFocused不为空,说明找到了当前获取焦点的view(mFocused),接着focusSearch会把direction(遥控器按键按下的方向)作为参数,找到特定方向下一个将要获取焦点的view,最后如果该view不为空,那么就让该view获取焦点。
(4)focusSearch方法的具体实现。
focusSearch方法的源码如下:
可以看出focusSearch其实是一层一层地网上调用父View的focusSearch方法,直到当前view是根布局(isRootNamespace()方法),通过注释可以知道focusSearch最终会调用DecorView的focusSearch方法。而DecorView的focusSearch方法找到的焦点view是通过FocusFinder来找到的。
(5)FocusFinder是什么?
它其实是一个实现 根据给定的按键方向,通过当前的获取焦点的View,查找下一个获取焦点的view这样算法的类。焦点没有被拦截的情况下,Android框架焦点的查找最终都是通过FocusFinder类来实现的。
(6)FocusFinder是如何通过findNextFocus方法寻找焦点的。
下面就来看看FocusFinder类是如何通过findNextFocus来找焦点的。一层一层往下看,后面会执行findNextUserSpecifiedFocus()方法,这个方法会执行focused(即当前获取焦点的View)的findUserSetNextFocus方法,如果该方法返回的View不为空,且isFocusable = true && isInTouchMode() = true的话,FocusFinder找到的焦点就是findNextUserSpecifiedFocus()返回的View。
(7)findNextFocus会优先根据XML里设置的下一个将获取焦点的View ID值来寻找将要获取焦点的View。
看看View的findUserSetNextFocus方法内部都干了些什么,OMG不就是通过我们xml布局里设置的nextFocusLeft,nextFocusRight的viewId来找焦点吗,如果按下Left键,那么便会通过nextFocusLeft值里的View Id值去找下一个获取焦点的View。
可以得出以下结论:
1. 如果一个View在XML布局中设置了focusable = true && isInTouchMode = true,那么这个View会优先获取焦点。
2. 通过设置nextFocusLeft,nextFocusRight,nextFocusUp,nextFocusDown值可以控制View的下一个焦点。
Android焦点的原理实现就这些。总结一下:
为了方便同志们学习,我这做了张导图,方便大家理解~
B. Android 输入法窗口焦点获取流程(2) ,输入法窗口和应用窗口绑定
基于Android9.x
Window和Session创建成功后,窗口的下一步流程为获取焦点
我们看下焦点获取过程,跟输入法相关的流程
两个Activity切换时,对应的状态变化过程为:
以下是Activity窗口初次获取焦点的流程
当两个activity 切换时,失去焦点的窗口调用过程如下:
对应的,获取焦点的额窗口的调用过程如下:
当B窗口的状态切换到RESUMED时,当窗口的focus可能变化时,会调用updateFocusedWindowLocked
在该方法中,判断,如果还没有执行startInputInner方法,则执行startInputInner方法,否则,直接执行startInputOrWindowGainedFocus方法
主要流程:
1:设置controlFlags的flag为CONTROL_WINDOW_FIRST
2:检查是否已经执行过startInputInner,没有的话执行startInputInner-->startInputOrWindowGainedFocus;否则,直接执行startInputOrWindowGainedFocus
两条路径,携带的startInputReason参数不一样
主要流程:
1:检查要启动和退出的ServedView是否为同一个,如果为同一个,则表示已经执行过startInputInner,则返回false,表示不再执行startInputInner
2:如果获取焦点的是EditorText,会创建跟IMS通信的mServedInputConnectionWrapper对象
主要流程:
1:创建EditorInfo对象tba,这个参数对TextView布局才有意义,它的初始化是在mServedView的onCreateInputConnection完成实例化的
2:根据EditorInfo创建一个InputConnection对象,输入法应用通过该对象,完成输入内容到输入框的传递;ACTIVITY获取焦点场景,该对象
为null,因为没有要输入的对象
startInputOrWindowGainedFocus携带的参数
startInputReason = 1
表示,该流程是窗口获取焦点过程
mClient
应用层创建的IInputMethodClient对象,为服务层提供应用层的各个回调方法
该方法跟应用进程首次创建时Session时,传递到IMMS的对象是同一个对象
windowGainingFocus:
应用层的ViewRootImpl$W对象
controlFlags |= CONTROL_START_INITIAL;
表示window窗口刚开始获取焦点
softInputMode = SOFT_INPUT_ADJUST_RESIZE , 允许调整输入法窗口,避免被其他窗口遮挡
tba , EditorInfo对象
servedContext
null
missingMethodFlags
ic等于null的情况下,为0
当应用层传递的W对象windowToken不为null的时候,则创建windowGainedFocus对象,返回给app
结果返回后,会对IMM的对象进行赋值
如此,进入一个窗口,获取窗口焦点过程,窗口与输入法相关的流程,就结束了。
下一篇:输入法在输入框弹出流程
Android输入法(3),弹出流程
C. android tv常见问题(一)焦点查找规律
https://github.com/Geekholt/TvFocus
Recyclerview聚焦到最后一个Item,继续按下键,焦点保持不变。
Recyclerview聚焦到最后一个Item,继续按下键,焦点会跳出RecyclerView,跳到附近的View上。
那么当Recyclerview滑动到最底部时,按下键,Android系统是如何找到下一个需要被聚焦的view的呢?我们把断点打在ViewGroup的focusSearch方法上,可以看到从ViewRootImp的performFocusNavigation方法开始,依次调用了如下方法。
View并不会直接去找焦点,而是交给它的parent去找。
焦点会逐级的交给父ViewGroup的focusSearch方法去处理,直到最外层的布局,最后实际上是调用了FocusFinder的findNextFocus方法去寻找新的焦点。
但是这里要注意的是,RecyclerView和其他的ViewGroup不一样,它自己重写了focusSearch方法。所以在焦点查找委托到达到DecorView之前,会先执行RecyclerView的focusSearch方法。
那么,RecyclerView和其他ViewGroup在寻找焦点方面有什么不一样呢? 为什么RecyclerView要重写ViewGroup的焦点查找机制呢 ?想知道这些问题的答案,那我们首先要知道ViewGroup的焦点查找机制。
ViewGroup的焦点查找机制的核心其实就是FocusFinder的findNextFocus方法。
主要步骤:
主要注意三点:
在addFocusables之后,找到指定方向上与当前focused距离最近的view。在进行查找之前,会统一坐标系。
总的来说就是根据当前focused的位置以及按键的方向,循环比较focusable集合中哪一个最适合,然后返回最合适的view,焦点查找就算完成了。
用于比较的方法。分别是将 当前聚焦的view , 当前遍历到的focusable 和 目前为止最合适的focusable (i = 0时是优先级最低的rect)进行比较。
判断是否可以做为候选。可以看作是一个初步筛选的方法,但是到底哪个更好还需要看beamBeat方法,这个方法会将通过筛选的focusable和当前最合适的focusable进行比较,选出更合适的一个。
到这里为止ViewGroup的focusSearch方法基本上就讲完了。那么下面来看一下RecyclerView的focusSearch方法是如何实现焦点查找的。
前面讲到了,该方法主要是为了解决 RecyclerView聚焦在按键方向上、当前屏幕区域内可见的最后一个item时,当前不可见的下一个item将无法获得焦点。
这个方法是由LayoutManager来实现的,这就是RecyclerView的针对上面提到的情况的焦点查找方法。这里主要分析LinearLayoutManager中实现的该方法,如果在使用其他的LayoutManager时出现RecyclelerView焦点不符合预期的话,可以查看对于LayoutManager下的onFocusSearchFailed方法。
主要关注方法,通过这个方法的命名我们大致就可以看出来这个方法的作用了。这个方法主要会 根据当前RecyclerVIew的正逆序以及按键方向,找出最近一个部分或完全不可见的View 。
这个方法是RecyclerView内部的方法,和FocusFinder中的isCandidate方法的逻辑可以说几乎是一摸一样的。
到此为止ViewGroup的focusSearch和RecyclerVIew的focusSearch都分析完了。我们已经知道RecyclerView滑动到最底部的时候,发生了哪些焦点行为,那么解决起来就比较简单了。
结合KeyEvent事件的流转,处理焦点的时机,按照优先级(顺序)依次是:
以上任一处都可以指定焦点,一旦消费了就不再往下走。
比如前面说到了RecyclerView就是通过重写focusSearch方法对边界上部分可见或不可见的view的焦点查找进行了特殊处理。
重写RecyclerView的focusSearch方法
D. Android 获取焦点
你好,我举例说明:比如有个输入框,当你点击输入框时,这时它的光标在闪烁,可以输入文字,那么就可以说这个输入框获得焦点了。获取焦点的控件就是当前可以执行操作的控件。
使用view.requestFocus()方法可以手动获取焦点。
以上,希望对你有帮助。
E. Android EditText获取焦点并弹出软键盘
1、首先,在xml文件中通过让edittext获取焦点
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="17dp"
android:textColor="#333333"
android:textColorHint="#999999"
android:hint="请输入课件描述..."
android:gravity="top|left"
android:background="@null"
>
<requestFocus/>
</EditText>
2、在清单文件中给activity添加android:windowSoftInputMode=”stateVisible”属性,这样一进入这个页面的时候光标就自动显示,软键盘也显示出来
<activity
android:name=".wonderfulmoment."
android:windowSoftInputMode="stateVisible"></activity>
F. Android移动应用中的焦点分析
简单一点理解,在移动应用中,焦点就是当前正在处理事件的位置。在手机应用中,最有可能用到焦点的就是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方法。
G. Android TV按键焦点原理浅谈
原文链接: Android TV按键焦点原理浅谈
本篇主要阅读 Android 源码讲解 TV 的按键事件分发原理和焦点查找原理,源码基于 Android9.0 ,首先思考几个问题:
带着这些问题,我们一起来撸 Android 源码吧!了解了系统是如何处理的有便于我们解决 TV 上一些按键和焦点的问题。
首先我们看下按键事件的入口 ViewRootImpl 类中的 ViewPostImeInputStage 内部类:
可以看到注释1,2,3,4分别判断不同事件执行不同方法,本篇主要讨论的TV焦点事件,主要看下 processKeyEvent 方法:
可以看到在该方法中执行了 mView.dispatchKeyEvent 方法,这里的 View 其实是 DecorView ,接着看下该方法:
上面首先判断了如果是第一次按下则处理panel的快捷键,如果处理了则不往下走,否则继续判断当窗口未销毁且回调非空则回调处理,如果处理了则不往下走,否则让 PhoneWindow 对应的 onKeyDown , onKeyUp 方法来处理。
接下来我们按照这个派发顺序依次来看看相关方法的实现,这里先看看 Activity 的 dispatchKeyEvent 实现:
我们看第1点 superDispatchKeyEvent 方法,可以看到该方法为一个抽象方法,而它的实现是实现它的子类 PhoneWindow :
该方法又回调用 DecorView 中的 superDispatchKeyEvent 方法:
此时,再来看下 ViewGroup 的 dispatchKeyEvent 方法:
接着看下 View 的 dispatchKeyEvent 方法:
该方法主要是判断如果有给 View 设置 OnKeyListener 事件且 View 为可用状态,则优先处理监听事件,其次调用 KeyEvent 的 dispatch 方法,接下来我们看下该方法:
该方法主要处理了按下、弹起事件,其中按下如果 mRepeatCount 重复次数大于0判断为长按,则执行长按事件。
我们继续看下 View 的 onKeyDown 方法:
按下事件判断了如果为确认相关的按键才到下一步处理,判断点击或长按条件满足,执行按下 View 正中心坐标,然后执行 checkForLongClick 检查长按方法,看下该方法如下:
我们经常会遇到电视按遥控器时长按会执行一次 onKeyDown 、 onKeyUp ,之后才是一直 onKeyDown ,松开后才执行 onKeyUp ,原因就在于这个检查长按方法是延迟的。 delayOffset 传进来的是0,所以延迟时间为 ViewConfiguration.getLongPressTimeout() ,即该类中定义的 DEFAULT_LONG_PRESS_TIMEOUT 常量。
同样的如果是触摸屏,可以看下 View 类中的 onTouchEvent 方法在按下操作的时候会开启 CheckForTap 线程检查是否是长按,该线程同样是延迟的,时间为 ViewConfiguration.getTapTimeout() ,即该类中的 TAP_TIMEOUT 常量,知道了这个你就知道如果写脚本或插件模拟长按应该间隔多长时间了,是不是一下你的模拟长按插件速度又可以更加准确快速的实现了。
不同版本系统定义的延迟时间有可能不一样,比如Google API 28 的 DEFAULT_LONG_PRESS_TIMEOUT 是500, TAP_TIMEOUT 是100,而 API 30 的 DEFAULT_LONG_PRESS_TIMEOUT 是400, TAP_TIMEOUT 也是100。
接下来再看下 Activity 的 onKeyDown :
回到 Decorview 中的 dispatchKeyEvent 方法看看 PhoneWindow 的 onKeyDown 方法:
onKeyUp 方法也可以自己再看下,以上就是浅谈按键事件的分发流程了。
总结:
上面讲解了按键事件分发流程,当上面分发完所有都没消费的时候,就会继续走 ViewRootImpl 的焦点导航流程,接下来看下 performFocusNavigation 方法:
首先我们看 mView.findFocus() ,该方法实际是调用了 ViewGroup 的 findFocus 方法:
该方法很简单,就是向下递归查找在当前页面已经获取焦点的 View ,继续看 focused.focusSearch(direction) 调用了 View 的 focusSearch 方法:
该方法向上递归查找,调用 ViewGroup 的 focusSearch 方法:
如果是根命名空间,则调用 FocusFinder 的 findNextFocus 方法查找焦点,否则继续往上查找。继续看 FocusFinder 的 findNextFocus 方法:
可以看到该方法首先查找用户指定的下一个获取焦点的 view ,如果找到了直接返回该 view ,如果没找到继续下面先添加 effectiveRoot 下的所有 view 到 focusables 集合中去,然后调用 findNextFocus 方法查找系统可获取下一个焦点的最近 view 。
我们先看下 findNextUserSpecifiedFocus 方法的实现:
通过用户指定焦点方式不是本篇的重点,这里就不贴出内部细节源码了。该方法实际就是调用 View 的 findUserSetNextFocus 方法来查找用户设置的下一个可获取焦点的 view ,然后在 while 循环中判断如果找到的是可以获取焦点并且可见的并且不是 InTouchNode 模式,则返回该焦点,否则继续循环查找直到找了一个循环没有找到可以获取焦点的或者 userSetNextFocus 为 null 跳出循环返回 null 。
再来看下系统就近原则查找的 findNextFocus 方法:
该方法主要通过 在相对方向上找下一个焦点,该方法内部逻辑比较简单,这里就不贴出来了,进去看下就知道其实就是先给 focusables 排序,然后从中找到 focused 在其中的后一个或前一个 view ,如果没找到并且 focusables 不为空则返回 focusables 的第一个。
接下来我们重点看下 方法:
再看下 isBetterCandidate 方法,该方法很关键,内部包含一系列逻辑如何成为最佳候选者:
该方法英文注释很直观,就不中文翻译了,首先看下成为候选人的 isCandidate 方法:
该方法判断了目标Rect如果在源Rect的方向一侧且不在内部的话,则为候选者,如第一个 destRect 左侧应在 srcRect 左侧左边, destRect 右侧应在 srcRect 右侧左边,其他方向同理。
接下来看下 beamBeats 方法:
H. Android高德地图获取不到输入框焦点
1、创建工程,并在工程中选择AddExternelJARs,选定MapApi.jar,点击OK,这样就可以将高德地图AndroidAPI库文件引入。
2、在工程中将引入的库文件MapApi.jar选中,点击OK,这样就可以在程序中使用Android高德地图获取输入框焦点。