前言

相信很多刚接触AndroidTV开发的开发者,都会被各种焦点问题给折磨的不行。不管是学技术还是学习其他知识,都要学习和理解其中原理,碰到问题我们才能得心应手。下面就来探一探Android的焦点分发的过程。

Android焦点分发,拦截过程的实现

Android焦点事件的分发是从ViewRootImpl的processKeyEvent开始的,源码如下:

private int processKeyEvent(QueuedInputEvent q) {

final KeyEvent event = (KeyEvent)q.mEvent;

// Deliver the key to the view hierarchy.

if (mView.dispatchKeyEvent(event)) {

return FINISH_HANDLED;

}

if (shouldDropInputEvent(q)) {

return FINISH_NOT_HANDLED;

}

// If the Control modifier is held, try to interpret the key as a shortcut.

if (event.getAction() == KeyEvent.ACTION_DOWN

&& event.isCtrlPressed()

&& event.getRepeatCount() == 0

&& !KeyEvent.isModifierKey(event.getKeyCode())) {

if (mView.dispatchKeyShortcutEvent(event)) {

return FINISH_HANDLED;

}

if (shouldDropInputEvent(q)) {

return FINISH_NOT_HANDLED;

}

}

// Apply the fallback event policy.

if (mFallbackEventHandler.dispatchKeyEvent(event)) {

return FINISH_HANDLED;

}

if (shouldDropInputEvent(q)) {

return FINISH_NOT_HANDLED;

}

// Handle automatic focus changes.

if (event.getAction() == KeyEvent.ACTION_DOWN) {

int direction = 0;

switch (event.getKeyCode()) {

case KeyEvent.KEYCODE_DPAD_LEFT:

if (event.hasNoModifiers()) {

direction = View.FOCUS_LEFT;

}

break;

case KeyEvent.KEYCODE_DPAD_RIGHT:

if (event.hasNoModifiers()) {

direction = View.FOCUS_RIGHT;

}

break;

case KeyEvent.KEYCODE_DPAD_UP:

if (event.hasNoModifiers()) {

direction = View.FOCUS_UP;

}

break;

case KeyEvent.KEYCODE_DPAD_DOWN:

if (event.hasNoModifiers()) {

direction = View.FOCUS_DOWN;

}

break;

case KeyEvent.KEYCODE_TAB:

if (event.hasNoModifiers()) {

direction = View.FOCUS_FORWARD;

} else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {

direction = View.FOCUS_BACKWARD;

}

break;

}

if (direction != 0) {

View focused = mView.findFocus();

if (focused != null) {

View v = focused.focusSearch(direction);

if (v != null && v != focused) {

// do the math the get the interesting rect

// of previous focused into the coord system of

// newly focused view

focused.getFocusedRect(mTempRect);

if (mView instanceof ViewGroup) {

((ViewGroup) mView).offsetDescendantRectToMyCoords(

focused, mTempRect);

((ViewGroup) mView).offsetRectIntoDescendantCoords(

v, mTempRect);

}

if (v.requestFocus(direction, mTempRect)) {

playSoundEffect(SoundEffectConstants

.getContantForFocusDirection(direction));

return FINISH_HANDLED;

}

}

// Give the focused view a last chance to handle the dpad key.

if (mView.dispatchUnhandledMove(focused, direction)) {

return FINISH_HANDLED;

}

} else {

// find the best view to give focus to in this non-touch-mode with no-focus

View v = focusSearch(null, direction);

if (v != null && v.requestFocus(direction)) {

return FINISH_HANDLED;

}

}

}

}

return FORWARD;

}

源码比较长,下面我就慢慢来讲解一下具体的每一个细节。

* (1) 首先由dispatchKeyEvent进行焦点的分发,如果dispatchKeyEvent方法返回true,那么下面的焦点查找步骤就不会继续了。

dispatchKeyEvent方法返回true代表焦点事件被消费了。

// Deliver the key to the view hierarchy.

if (mView.dispatchKeyEvent(event)) {

return FINISH_HANDLED;

}

首先会执行mView的dispatchKeyEvent方法,估计大家会好奇这个mView是个什么鬼?其实它就是Activity的顶层容器DecorView,它是一FrameLayout。所以这里的dispatchKeyEvent方法应该执行的是ViewGroup的dispatchKeyEvent()方法,而不是View的dispatchKeyEvent方法。

ViewGroup的dispatchKeyEvent()方法的源码如下:

@Override

public boolean dispatchKeyEvent(KeyEvent event) {

if (mInputEventConsistencyVerifier != null) {

mInputEventConsistencyVerifier.onKeyEvent(event, 1);

}

if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))

== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {

if (super.dispatchKeyEvent(event)) {

return true;

}

} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)

== PFLAG_HAS_BOUNDS) {

if (mFocused.dispatchKeyEvent(event)) {

return true;

}

}

if (mInputEventConsistencyVerifier != null) {

mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);

}

return false;

}

(2)ViewGroup的dispatchKeyEvent执行流程

首先ViewGroup会一层一层往上执行父类的dispatchKeyEvent方法,如果返回true那么父类的dispatchKeyEvent方法就会返回true,也就代表父类消费了该焦点事件,那么焦点事件自然就不会往下进行分发。

然后ViewGroup会判断mFocused这个view是否为空,如果为空就会return false,焦点继续往下传递;如果不为空,那就会return mFocused的dispatchKeyEvent方法返回的结果。这个mFocused是什么呢?其实

是ViewGroup中当前获取焦点的子View,这个可以从requestChildFocus方法中得到答案。requestChildFocus()的源码如下:

@Override

public void requestChildFocus(View child, View focused) {

if (DBG) {

System.out.println(this + " requestChildFocus()");

}

if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {

return;

}

// Unfocus us, if necessary

super.unFocus(focused);

// We had a previous notion of who had focus. Clear it.

if (mFocused != child) {

if (mFocused != null) {

mFocused.unFocus(focused);

}

mFocused = child;

}

if (mParent != null) {

mParent.requestChildFocus(this, focused);

}

}

(3)下面再来瞧瞧view的dispatchKeyEvent方法的具体的执行过程

public boolean dispatchKeyEvent(KeyEvent event) {

if (mInputEventConsistencyVerifier != null) {

mInputEventConsistencyVerifier.onKeyEvent(event, 0);

}

// Give any attached key listener a first crack at the event.

//noinspection SimplifiableIfStatement

ListenerInfo li = mListenerInfo;

if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED

&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {

return true;

}

if (event.dispatch(this, mAttachInfo != null

? mAttachInfo.mKeyDispatchState : null, this)) {

return true;

}

if (mInputEventConsistencyVerifier != null) {

mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);

}

return false;

}

惊奇的发现执行了onKeyListener中的onKey方法,如果onKey方法返回true,那么dispatchKeyEvent方法也会返回true

可以得出结论:如果想要修改ViewGroup焦点事件的分发,可以这么干:

重写view的dispatchKeyEvent方法

给某个子view设置onKeyListener监听

注意:实际开发中,理论上所有焦点问题都可以通过给dispatchKeyEvent方法增加监听来来拦截来控制。

焦点没有被dispatchKeyEvent拦截的情况下的处理过程

(1)dispatchKeyEvent方法返回false后,先得到按键的方向direction值,这个值是一个int类型参数。这个direction值是后面来进行焦点查找的。

// Handle automatic focus changes.

if (event.getAction() == KeyEvent.ACTION_DOWN) {

int direction = 0;

switch (event.getKeyCode()) {

case KeyEvent.KEYCODE_DPAD_LEFT:

if (event.hasNoModifiers()) {

direction = View.FOCUS_LEFT;

}

break;

case KeyEvent.KEYCODE_DPAD_RIGHT:

if (event.hasNoModifiers()) {

direction = View.FOCUS_RIGHT;

}

break;

case KeyEvent.KEYCODE_DPAD_UP:

if (event.hasNoModifiers()) {

direction = View.FOCUS_UP;

}

break;

case KeyEvent.KEYCODE_DPAD_DOWN:

if (event.hasNoModifiers()) {

direction = View.FOCUS_DOWN;

}

break;

case KeyEvent.KEYCODE_TAB:

if (event.hasNoModifiers()) {

direction = View.FOCUS_FORWARD;

} else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {

direction = View.FOCUS_BACKWARD;

}

break;

}

(2)接着会调用DecorView的findFocus()方法一层一层往下查找已经获取焦点的子View。

ViewGroup的findFocus方法如下:

@Override

public View findFocus() {

if (DBG) {

System.out.println("Find focus in " + this + ": flags="

+ isFocused() + ", child=" + mFocused);

}

if (isFocused()) {

return this;

}

if (mFocused != null) {

return mFocused.findFocus();

}

return null;

}

View的findFocus方法

public View findFocus() {

return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;

}

说明:判断view是否获取焦点的isFocused()方法, (mPrivateFlags & PFLAG_FOCUSED) != 0 和view 的isFocused()方法是一致的。

@ViewDebug.ExportedProperty(category = "focus")

public boolean isFocused() {

return (mPrivateFlags & PFLAG_FOCUSED) != 0;

}

其中isFocused()方法的作用是判断view是否已经获取焦点,如果viewGroup已经获取到了焦点,那么返回本身即可,否则通过mFocused的findFocus()方法来找焦点。mFocused其实就是ViewGroup中获取焦点的子view,如果mView不是ViewGourp的话,findFocus其实就是判断本身是否已经获取焦点,如果已经获取焦点了,返回本身。

(3)回到processKeyEvent方法中,如果findFocus方法返回的mFocused不为空,说明找到了当前获取焦点的view(mFocused),接着focusSearch会把direction(遥控器按键按下的方向)作为参数,找到特定方向下一个将要获取焦点的view,最后如果该view不为空,那么就让该view获取焦点。

if (direction != 0) {

View focused = mView.findFocus();

if (focused != null) {

View v = focused.focusSearch(direction);

if (v != null && v != focused) {

// do the math the get the interesting rect

// of previous focused into the coord system of

// newly focused view

focused.getFocusedRect(mTempRect);

if (mView instanceof ViewGroup) {

((ViewGroup) mView).offsetDescendantRectToMyCoords(

focused, mTempRect);

((ViewGroup) mView).offsetRectIntoDescendantCoords(

v, mTempRect);

}

if (v.requestFocus(direction, mTempRect)) {

playSoundEffect(SoundEffectConstants

.getContantForFocusDirection(direction));

return FINISH_HANDLED;

}

}

// Give the focused view a last chance to handle the dpad key.

if (mView.dispatchUnhandledMove(focused, direction)) {

return FINISH_HANDLED;

}

} else {

// find the best view to give focus to in this non-touch-mode with no-focus

View v = focusSearch(null, direction);

if (v != null && v.requestFocus(direction)) {

return FINISH_HANDLED;

}

}

}

(4)focusSearch方法的具体实现。

focusSearch方法的源码如下:

@Override

public View focusSearch(View focused, int direction) {

if (isRootNamespace()) {

// root namespace means we should consider ourselves the top of the

// tree for focus searching; otherwise we could be focus searching

// into other tabs. see LocalActivityManager and TabHost for more info

return FocusFinder.getInstance().findNextFocus(this, focused, direction);

} else if (mParent != null) {

return mParent.focusSearch(focused, direction);

}

return null;

}

可以看出focusSearch其实是一层一层地网上调用父View的focusSearch方法,直到当前view是根布局(isRootNamespace()方法),通过注释可以知道focusSearch最终会调用DecorView的focusSearch方法。而DecorView的focusSearch方法找到的焦点view是通过FocusFinder来找到的。

@Override

public View focusSearch(View focused, int direction) {

if (isRootNamespace()) {

// root namespace means we should consider ourselves the top of the

// tree for focus searching; otherwise we could be focus searching

// into other tabs. see LocalActivityManager and TabHost for more info

return FocusFinder.getInstance().findNextFocus(this, focused, direction);

} else if (mParent != null) {

return mParent.focusSearch(focused, direction);

}

return null;

}

(5)FocusFinder是什么?

它其实是一个实现 根据给定的按键方向,通过当前的获取焦点的View,查找下一个获取焦点的view这样算法的类。焦点没有被拦截的情况下,Android框架焦点的查找最终都是通过FocusFinder类来实现的。

(6)FocusFinder是如何通过findNextFocus方法寻找焦点的。

下面就来看看FocusFinder类是如何通过findNextFocus来找焦点的。一层一层往下看,后面会执行findNextUserSpecifiedFocus()方法,这个方法会执行focused(即当前获取焦点的View)的findUserSetNextFocus方法,如果该方法返回的View不为空,且isFocusable = true && isInTouchMode() = true的话,FocusFinder找到的焦点就是findNextUserSpecifiedFocus()返回的View。

private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {

// check for user specified next focus

View userSetNextFocus = focused.findUserSetNextFocus(root, direction);

if (userSetNextFocus != null && userSetNextFocus.isFocusable()

&& (!userSetNextFocus.isInTouchMode()

|| userSetNextFocus.isFocusableInTouchMode())) {

return userSetNextFocus;

}

return null;

}

(7)findNextFocus会优先根据XML里设置的下一个将获取焦点的View ID值来寻找将要获取焦点的View。

看看View的findUserSetNextFocus方法内部都干了些什么,OMG不就是通过我们xml布局里设置的nextFocusLeft,nextFocusRight的viewId来找焦点吗,如果按下Left键,那么便会通过nextFocusLeft值里的View Id值去找下一个获取焦点的View。

View findUserSetNextFocus(View root, @FocusDirection int direction) {

switch (direction) {

case FOCUS_LEFT:

if (mNextFocusLeftId == View.NO_ID) return null;

return findViewInsideOutShouldExist(root, mNextFocusLeftId);

case FOCUS_RIGHT:

if (mNextFocusRightId == View.NO_ID) return null;

return findViewInsideOutShouldExist(root, mNextFocusRightId);

case FOCUS_UP:

if (mNextFocusUpId == View.NO_ID) return null;

return findViewInsideOutShouldExist(root, mNextFocusUpId);

case FOCUS_DOWN:

if (mNextFocusDownId == View.NO_ID) return null;

return findViewInsideOutShouldExist(root, mNextFocusDownId);

case FOCUS_FORWARD:

if (mNextFocusForwardId == View.NO_ID) return null;

return findViewInsideOutShouldExist(root, mNextFocusForwardId);

case FOCUS_BACKWARD: {

if (mID == View.NO_ID) return null;

final int id = mID;

return root.findViewByPredicateInsideOut(this, new Predicate() {

@Override

public boolean apply(View t) {

return t.mNextFocusForwardId == id;

}

});

}

}

return null;

}

可以得出以下结论:

1. 如果一个View在XML布局中设置了focusable = true && isInTouchMode = true,那么这个View会优先获取焦点。

2. 通过设置nextFocusLeft,nextFocusRight,nextFocusUp,nextFocusDown值可以控制View的下一个焦点。

Android焦点的原理实现就这些。总结一下:

首先DecorView会调用dispatchKey一层一层进行焦点的分发,如果dispatchKeyEvent方法返回true的话,那么焦点就不会往下分发了。

中途可以给某个子View设置OnKeyListener进行焦点的拦截。

如果焦点没有被拦截的话,那么焦点就会交给系统来处理

Android底层先会记录按键的方向,后面DecorView会一层一层往下调用findFocus方法找到当前获取焦点的View

后面系统又会根据按键的方向,执行focusSearch方法来寻找下一个将要获取焦点的View

focusSearch内部其实是通过FocusFinder来查找焦点的。FocusFinder会优先通过View在XML布局设置的下一个焦点的ID来查找焦点。

最终如果找到将要获取焦点的View,就让其requestFocus。

android 焦点分发,Android TV 焦点分发原理解析相关推荐

  1. android黑科技系列——微信抢红包插件原理解析和开发实现

    一.前言 自从几年前微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导 ...

  2. android传感器原理,Android移动设备中光电传感器的工作原理解析

    在移动技术中,传感器是被测量信号输入的首要技术,也是传感器系统中的元件组成部分,它包括载体和电路连接的敏感元件和转换元件,但是传感器系统却是组合某种信息处理能力元件的传感器. Android平台应用的 ...

  3. android ibeacon室内定位,iBeacon室内定位原理解析

    原标题:iBeacon室内定位原理解析 目前,技术发展持续火热,因着iBeacon的定位精度和造价都比较符合国内室内定位的市场需求,下面我们来聊一聊iBeacon室内定位原理. iBeacon定位原理 ...

  4. android 手机内存uri_Android消息机制Handler原理解析

    关注[搜狐技术产品]公众号,第一时间获取技术干货 导读 在Android中,Handler一直是一个热点,在开发过程中,它的使用频率很高,而且在Android源码中Handler都是常客.那么Hand ...

  5. Android 原生 多屏显示 (分屏) 原理 解析

    概述 在 Android 7.0 及更高版本中,用户或开发者可以借助平台原生功能,在设备屏幕上同时显示多个应用,即为:多窗口模式(Multi Window Supports),目前支持多窗口模式以下三 ...

  6. Android进阶:七、Retrofit2.0原理解析之最简流程【上】

    retrofit 已经流行很久了,它是Square开源的一款优秀的网络框架,这个框架对okhttp进行了封装,让我们使用okhttp做网路请求更加简单.但是光学会使用只是让我们多了一个技能,学习其源码 ...

  7. Android逆向之路---脱壳360加固原理解析

    前言 众所周知,上次说到了如何脱壳360加固,大致意思就是安装一个xposed插件,然后自动就会脱壳了,那么这个插件是如何工作的呢,本次重点说说这个. 上次说道了dumpDex脱壳360加固,其实先说 ...

  8. android 屏幕录制方案,ShareREC for Android全系统录屏原理解析

    本文是Mob开发者平台技术副总监余勋杰基于MediaProjection实现Android全系统录屏功能的原理解析,包括了结合MediaRecorder和MediaCodec两套方案. 文 / 余勋杰 ...

  9. Android TV 焦点分发原理解析

    前言 相信很多刚接触AndroidTV开发的开发者,都会被各种焦点问题给折磨的不行.不管是学技术还是学习其他知识,都要学习和理解其中原理,碰到问题我们才能得心应手.下面就来探一探Android的焦点分 ...

最新文章

  1. Chrome的console
  2. Ubuntu 20.04 LTS 开发周期的重要任务:移除 Python 2
  3. Leetcode每日一题:724.Find Pivot Index(寻找中心索引)
  4. python中for语句只有一种写法for in_pythonforin语句
  5. cfb为什么不需要填充_高压输电线路中为什么只有相线不需要零线
  6. JS每日一题: Vue中mixin怎么理解?
  7. 某医院病房计算机管理数据库,毕业论文_数据库某医院病房计算机系统.doc
  8. Pspice仿真模型创建
  9. 【游戏开发实战】下载原神模型,PMX转FBX,导入到Unity中,卡通渲染,绑定人形动画(附Demo工程)
  10. 拯救者Y9000P+因特尔11代+3060Ubuntu驱动安装
  11. 扬帆际海:如何成为一个合格的跨境电商运营?
  12. matlab运用函数随机点名,matlab随机点名程序
  13. UnrealEd3视图导航及常用快捷键
  14. linux net子系统-系统调用层
  15. 木马免杀原理及方法(超全)
  16. 2021年安全员-A证报名考试及安全员-A证考试资料
  17. 2022年 IoT 物联网平台发展趋势:私有化部署
  18. Codeforces Round #731 (Div. 3)(ABCDEFG)
  19. 高级计算机应用软件,商务软件高级应用微课教程(高等职业院校技能应用型教材)/计算机应用系列...
  20. 区块链编程_区块链编程的10个最佳工具包

热门文章

  1. * Dijkstra 堆优化
  2. 【数据库基础知识】oracle client安装及配置
  3. SparkStreaming找不到reduceByKey的解决方法
  4. php 手机屏幕,90hz和60hz手机屏幕差别大吗
  5. python中session的使用方法_python中requests库session对象的妙用详解
  6. python画图显示中文乱码_解决Python pandas plot输出图形中显示中文乱码问题
  7. codesys 串口通讯实例_CODESYS线上直播,解读控制器开发那些事儿(二)
  8. 跟熊浩学沟通30讲读后感_怎样提高自己的沟通表达能力
  9. python实现两个接口的依赖关系
  10. 导致网站服务器负担过重,利用httpd.ini实现图片和文件的防盗链