焦点处理相关记录

以下所涉及的焦点部分,只是按键移动部分,不明确包含Touch Focus部分

需解决问题

控件的下一个焦点是哪?

分析思路

当用户通过按键(遥控器等)触发焦点切换时,事件指令会通过底层进行一系列处理。
在ViewRootImpl.java中有一个方法,deliverKeyEventPostIme(...),因为涉及到底层代码,所以没有详细的跟踪分析此方法的调用逻辑,根据网上的资料,按键相关的处理会经过此方法。

    private void deliverKeyEventPostIme(QueuedInputEvent q) {
...
// 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;
...
}
if (direction != 0) {
View focused = mView.findFocus();
if (focused != null) {
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
.....
if (v.requestFocus(direction, mTempRect)) {
...finishInputEvent(q, true);
return;
}
}
...
}
}

由此方法可以看出,最主要的两个核心过程:

    View v = focused.focusSearch(direction);
v.requestFocus(direction, mTempRect)

接下来详细的分析下,看看过程中进行了什么操作

具体分析

在具体分析前,首先我们先明确下相关变量的定义

View mView : 主体View[DecorView]

        //一般把主View“DecorView”添加到WindowManagerImpl中(通过addView)
//WindowManagerImpl.java
private void addView(View view...) {
ViewRootImpl root;
root = new ViewRootImpl(view.getContext());
...
root.setView(view, wparams, panelParentView);
...
}
//ViewRootImpl.java
public void setView(View view....) {
synchronized (this) {
if (mView == null) {
mView = view;
...
}
...
}
}

所以mView是一个DecorView类型的变量.

View focused :

        View focused = mView.findFocus();
//PhoneWindow.java
private final class DecorView extends FrameLayout implements RootVie.... {
...
}
//FrameLayout.java
public class FrameLayout extends ViewGroup {
...
}
//ViewGroup.java
//mFocused记录的是当前被焦点选中的view
@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;
}

所以最终得到的focused为当前页面中得到焦点的view.

在明确的相关变量后,我们开始View v = focused.focusSearch(direction)的具体分析.

   //View.java
public View focusSearch(int direction) {
//如果存在父控件,则执行父控件的focusSearch方法
if (mParent != null) {
return mParent.focusSearch(this, direction);
} else {
return null;
}
}
//ViewGroup.java
public View focusSearch(View focused, int direction) {
//判断是否为顶层布局,若是则执行对应方法,若不是则继续向上寻找,说明会从内到外的一层层进行判断,直到最外层的布局为止
if (isRootNamespace()) {
return FocusFinder.getInstance().findNextFocus(this, focused, direction);
} else if (mParent != null) {
return mParent.focusSearch(focused, direction);
}
return null;
}

说明在这个过程中,其实是从里层开始一直遍历到最外层布局,然后在最外层布局将处理交给了FocusFinder中的方法.

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

那我们来看看此方法具体做了什么操作

    //FocusFinder.java
public final View findNextFocus(ViewGroup root, View focused, int direction) {
return findNextFocus(root, focused, null, direction);
}
    //FocusFinder.java
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
View next = null;
if (focused != null) {
next = findNextUserSpecifiedFocus(root, focused, direction);
}
if (next != null) {
return next;
}
ArrayList<View> focusables = mTempList;
try {
focusables.clear();
root.addFocusables(focusables, direction);
if (!focusables.isEmpty()) {
next = findNextFocus(root, focused, focusedRect, direction, focusables);
}
} finally {
focusables.clear();
}
return next;
}

发现在findNextFocus的执行过程的开始,先执行了findNextUserSpecifiedFocus(...)方法,由代码可以看出,此方法先去判断特定Id值是否存在,若存在则查询出Id对应的view.其实这些Id就是xml里通过android:nextFocusUp="..."等或者代码特别指定的焦点顺序.所以在此过程先判断,若存在,说明下个焦点已经找到,直接返回.

    //FocusFinder.java
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;
}
//View.java
View findUserSetNextFocus(View root, 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<View>() {
@Override
public boolean apply(View t) {
return t.mNextFocusForwardId == id;
}
});
}
}
return null;
}

如果上面过程没有查询到,则会执行到findNextFocus(...)方法.在这个方法中,先通过offsetDescendantRectToMyCoords(...)方法获得焦点控件的位置矩阵.然后通过比较得到下一个焦点的控件。具体的比较规则可以查看findNextFocusInRelativeDirection(...)方法与findNextFocusInAbsoluteDirection(...)方法.

    //FocusFinder.java
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
int direction, ArrayList<View> focusables) {
if (focused != null) {
if (focusedRect == null) {
focusedRect = mFocusedRect;
}
// fill in interesting rect from focused
focused.getFocusedRect(focusedRect);
root.offsetDescendantRectToMyCoords(focused, focusedRect);
} else {
if (focusedRect == null) {
focusedRect = mFocusedRect;
// make up a rect at top left or bottom right of root
switch (direction) {
case View.FOCUS_RIGHT:
case View.FOCUS_DOWN:
setFocusTopLeft(root, focusedRect);
break;
case View.FOCUS_FORWARD:
if (root.isLayoutRtl()) {
setFocusBottomRight(root, focusedRect);
} else {
setFocusTopLeft(root, focusedRect);
}
break;
case View.FOCUS_LEFT:
case View.FOCUS_UP:
setFocusBottomRight(root, focusedRect);
break;
case View.FOCUS_BACKWARD:
if (root.isLayoutRtl()) {
setFocusTopLeft(root, focusedRect);
} else {
setFocusBottomRight(root, focusedRect);
break;
}
}
}
}
switch (direction) {
case View.FOCUS_FORWARD:
case View.FOCUS_BACKWARD:
return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
direction);
case View.FOCUS_UP:
case View.FOCUS_DOWN:
case View.FOCUS_LEFT:
case View.FOCUS_RIGHT:
return findNextFocusInAbsoluteDirection(focusables, root, focused,
focusedRect, direction);
default:
throw new IllegalArgumentException("Unknown direction: " + direction);
}
}

结论

查找焦点的过程,主要是从View的focusSearch(...)方法开始,从当前焦点开始逐层往外,最终在最外层布局执行FocusFinder中的核心方法来获得下个焦点所在的视图view.

如果需要指定跳转,可以在逐层focusSearch(...)的时候,返回特定的view

[Android学习笔记]理解焦点处理原理的相关记录的更多相关文章

  1. Android学习笔记View的工作原理

    自定义View,也可以称为自定义控件,通过自定义View可以使得控件实现各种定制的效果. 实现自定义View,需要掌握View的底层工作原理,比如View的测量过程.布局流程以及绘制流程,除此之外,还 ...

  2. Android学习笔记之Activity详解

    1 理解Activity Activity就是一个包含应用程序界面的窗口,是Android四大组件之一.一个应用程序可以包含零个或多个Activity.一个Activity的生命周期是指从屏幕上显示那 ...

  3. Android 学习笔记之Volley(七)实现Json数据加载和解析...

    学习内容: 1.使用Volley实现异步加载Json数据...   Volley的第二大请求就是通过发送请求异步实现Json数据信息的加载,加载Json数据有两种方式,一种是通过获取Json对象,然后 ...

  4. udacity android 学习笔记: lesson 4 part b

    udacity android 学习笔记: lesson 4 part b 作者:干货店打杂的 /titer1 /Archimedes 出处:https://code.csdn.net/titer1 ...

  5. 【转】 Pro Android学习笔记(七八):服务(3):远程服务:AIDL文件

    目录(?)[-] 在AIDL中定义服务接口 根据AIDL文件自动生成接口代码 文章转载只能用于非商业性质,且不能带有虚拟货币.积分.注册等附加条件.转载须注明出处:http://blog.csdn.n ...

  6. 【转】 Pro Android学习笔记(七七):服务(2):Local Service

    目录(?)[-] Local service代码 调用Local ServiceLocal Service client代码 AndroidManifestxml定义Serviceacitivty的l ...

  7. 【转】Pro Android学习笔记(十四):用户界面和控制(2):Text类控制

    目录(?)[-] TextView 例子1在XML中设置autoLink属性 例子2在代码中设置autoLink属性 EditText AutoCompleteTextView MultiAutoCo ...

  8. 【转】Pro Android学习笔记(二):开发环境:基础概念、连接真实设备、生命周期

    在Android学习笔记(二):安装环境中已经有相应的内容.看看何为新.这是在source网站上的Android架构图,和标准图没有区别,只是这张图颜色好看多了,录之.本笔记主要讲述Android开发 ...

  9. udacity android 学习笔记: lesson 4 part a

    udacity android 学习笔记: lesson 4 part a 作者:干货店打杂的 /titer1 /Archimedes 出处:https://code.csdn.net/titer1 ...

随机推荐

  1. 关于C#中泛型类型参数约束(where T : class)

    .NET支持的类型参数约束有以下五种:where T : struct                               | T必须是一个结构类型where T : class       ...

  2. 【原】UI随设备旋转从iOS6到iOS8的适配策略

    - (void)statusBarOrientationChange:(NSNotification *)notification { WClassAndFunctionName; UIInterfa ...

  3. JDBC demo

    package com.zhangbz.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql ...

  4. 多线程基础(二)pthread的了解

    IOS中多线程的实现方案   了解NSOperation(代码) 所有的方法都是pthread开头的   然后再搞一条线程 pthread_create方法有返回值,作用:判断线程创建是否成功?   ...

  5. mysql高可用之DRBD + HEARTBEAT + MYSQL

    1. 架构 Mysql: master<=slave 10.24.6.4:3306<=10.24.6.6:3306 VIP: 10.24.6.20 必须使得VIP和mysql处于同一网段, ...

  6. Memcache修改端口

    修改端口 网上很多的说法都无法起作用(像下面这样) D:\.......memcached -p 10000 -d start 现在有两种解决方法 ①直接修改注册表 HKEY_LOCAL_MACHIN ...

  7. PS网页设计教程XXVI——如何在PS中创建一个专业的网页布局

    作为编码者,美工基础是偏弱的.我们可以参考一些成熟的网页PS教程,提高自身的设计能力.套用一句话,“熟读唐诗三百首,不会作诗也会吟”. 本系列的教程来源于网上的PS教程,都是国外的,全英文的.本人尝试 ...

  8. Java 图片处理——如何生成高清晰度而占有磁盘小的缩略图

    现在的web项目,图片越来越多,图片大小也越来越大,随便就能达到1M,2M,甚至更大.用户上传的图片,一般是无法直接使用的.一般要生成两三种对应的缩略图,分别适配不同的终端,不同的场景.比如PC,手机 ...

  9. VISUAL STUDIO 调试

    调试术语 Visual Studio调试之断点基础篇 Visual Studio调试之断点进阶篇 不能设置断点的检查步骤 Visual Studio调试之断点技巧篇 Visual Studio调试之断 ...

  10. 用python自定义实现db2的连接池

    想要模仿zabbix的oracle插件orabix来实现对db2的监控,但是Java能力有限,就用python来实现了.但是python常用的连接池PooledDB似乎并不支持db2,一直报这样的错误 ...