android焦点控制
⑴ Android TV开发焦点移动源码分析
点可以理解为选中态,在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 查找焦点
⑵ 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 方法:
⑶ 怎么让android 页面失去焦点
在网上找了好久,有点监听软键盘事件,有点调用 clearFouse()方法,但是测试了都没有!xml中也找不到相应的属性可以关闭这个默认行为
1 解决之道:在EditText的父级控件中找一个,设置成
Android:focusable="true"
android:focusableInTouchMode="true"
这样,就把EditText默认的行为截断了!
<LinearLayout
style="@style/FillWrapWidgetStyle"
android:orientation="vertical"
android:background="@color/black"
android:gravity="center_horizontal"
android:focusable="true"
android:focusableInTouchMode="true"
>
<ImageView
android:id="@+id/logo"
style="@style/WrapContentWidgetStyle"
android:background="@drawable/dream_dictionary_logo"
/>
<RelativeLayout
style="@style/FillWrapWidgetStyle"
android:background="@drawable/searchbar_bg"
android:gravity="center_vertical"
>
<EditText
android:id="@+id/searchEditText"
style="@style/WrapContentWidgetStyle"
android:background="@null"
android:hint="Search"
android:layout_marginLeft="40dp"
android:singleLine="true"
/>
</RelativeLayout>
</LinearLayout>
2 还有一个方法也可以非常简单的实现这个功能:
EditText对象的clearFocus();
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(editMsgView.getWindowToken(), 0);(关闭软键盘。。。)
3更多问题解决办法请参考android学习手册,例子、源码、文档全部搞定,采用androidstudo的目录结构,360手机助手中下载。下面是截图。
⑷ 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方法。
⑸ Android音频焦点处理方法
前两宏陆天在项目上做了对音频焦点使用方法的一个总结,蔽答顷记录在下面。
在Android设备上,默认可以有多个应用同时播放音频,但是,这种处理带来的用户体验并不好,为了解决这个问题,Android引入了音频焦点机制,一次只能有一个App持有音频焦点。
一般情况下,当一个App失去音频焦点时,为了有较好的用户体验,它应该主动暂停播放,从而使新获得音频焦点的App可以清晰的播放音频,避免混音的情况。
以下是官方建议的处理音频焦点应该遵循的一些规则:
处理音频焦点都是通过AudioManager这个类,如下是获得该类实例的方法:
AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
下面介绍音频焦点处理相关的一些方法(不同的Android版本处理音频焦点的方式略有差别,以下内容基于Android 4.4)。
下面进行详细介绍。
2. abandonAudioFocus(OnAudioFocusChangeListener l)
参数同上。
返回值同上。
3. AudioManager.OnAudioFocusChangeListener
当音频焦点发生变化时,可以在OnAudioFocusChangeListener的onAudioFocusChange(int focusChange)方法中监听到,下面详细说明该方法。
onAudioFocusChange(int focusChange)
参数:focusChange可以表明当前音频焦点发生的是何种变化,需要根据该参数状态做出正确的响应。
分为获得和举镇丢失两种情况:
下面是一个申请长音频焦点,播放音乐的例子:
以上
⑹ android 中如何设置焦点的位置。
设置焦点需要以下几步:
1,打开手机相机,进入设置,打开焦点功能。
2,进入拍照页面,双击屏幕的一个地方,相机就会自动锁定焦点。
3,焦点会带有两个锁定框,一个锁定,一个可以拖动。
4,锁定在屏幕上的锁定框就是焦点,可移动的是进行焦距调节。
多数相机的焦距处理并不明显,直接移动焦点效果会好些。
⑺ 如何控制listview的焦点
1.将ListView的Item Layout的子控件focusable属性设置为false
2.对Item Layout的根控件android:descendantFocusability="blocksDescendant"
例如:
<RelativeLayout
xmlns:android=""
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dip"
android:background="#ffffffff"
android:descendantFocusability="blocksDescendant >
<LinearLayout
<RatingBar
android:id="@+id/rb_bookRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:rating="2.0"
style="@style/RatingBar"
android:isIndicator="true"
/>
</LinearLayout>
<Button
android:id="@+id/btn_schele"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:focusable="false"
android:text="影讯"
style="@style/Button"
/>
</RelativeLayout>
⑻ Android 获取焦点
你好,我举例说明:比如有个输入框,当你点击输入框时,这时它的光标在闪烁,可以输入文字,那么就可以说这个输入框获得焦点了。获取焦点的控件就是当前可以执行操作的控件。
使用view.requestFocus()方法可以手动获取焦点。
以上,希望对你有帮助。
⑼ android某一app设为焦点app
在做电视机顶盒应用的时候,每个控件都是有焦点变化的显示的册启,如果当前焦点被一个自己不知道的控件获取到,则其他控件就不会获取到焦点,我们必须要找到当前获取焦点的控件,将其 android:focusable="false" ,不让它获取到焦点。
通过findFocus()方法来获取到当前的控件。
在Activity的onCreate方法中,启动线毁困程。
new Thread(run2).start();
run2的代码如下所示:
Runnable run2 = new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
View rootview = DetailActivity.this.getWindow().getDecorView();
View aaa = rootview.findFocus();
Log.i("tag", aaa.toString());
}
}
};
这样每隔5秒会获取一纤姿念次当前有焦点的控件,打印出log就可以了。
打印出的log如下所示:
最后就是控件的id了,这样你就找到了当前获取焦点的控件了。
结束。
链接:https://www.waimaiguai.com/technology/article/1327504
来源:外卖怪
⑽ 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焦点的原理实现就这些。总结一下:
为了方便同志们学习,我这做了张导图,方便大家理解~