Android TV焦点总结
【1】 ViewRootImpl.ViewPostImeInputStage.onProcess()
当你拿着遥控器瞎按的时候,按键处理的起点是这儿
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
final class ViewPostImeInputStage extends InputStage {
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
//按键处理的起点
return processKeyEvent(q);
} else {
//...
}
}
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
//【1.1】
//这里的 mView 当然是我们的 DecorView,
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
//...
// Handle automatic focus changes.
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (groupNavigationDirection != 0) {
//...
} else {
//关键代码
// 自动寻焦的逻辑【2】
if (performFocusNavigation(event)) {
return FINISH_HANDLED;
}
}
}
return FORWARD;
}
}
【1.1】DecorView.dispatchKeyEvent()
【/frameworks/base/core/java/android/internal/policy/DecorView.java】
从此开始,DecorView开始进行KeyEvent的分发
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
//1. 重写了父类FrameLayout的dispatchKeyEvent()方法,但不会执行 super.dispatchKeyEvent()了
//2.
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;
//...
if (!mWindow.isDestroyed()) {
// 获取 Callback对象 就是activity
//见【1.2】
final Window.Callback cb = mWindow.getCallback();
//执行activity 的dispatchKeyEvent(),super.dispatchKeyEvent(event)不会执行滴
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
: mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
}
【1.2】Activity.dispatchKeyEvent()
按键分发来到Activity中
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback{
//参数省略了..
final void attach() {
//这儿将 activity 传入
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
//setCallback
mWindow.setCallback(this);
//...
}
// 注意 Activity实现了Window.Callback接口
//dispatchKeyEvent()方法 就是来自Window.Callback
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
//...
//win 是PhoneWindow 对象
Window win = getWindow();
//关键代码【1.3】
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
}
【1.3】PhoneWindow
按键分发来到PhoneWindow中
兜兜转换 执行DecorView的
mDecor.superDispatchKeyEvent(event)方法,
又回到DecorView中
public class PhoneWindow extends Window implements MenuBuilder.Callback {
//
@Override
public boolean superDispatchKeyEvent(KeyEvent event) {
//mDecor就是DecorView
//
return mDecor.superDispatchKeyEvent(event);
}
}
【1.4】
按键分发来到DecorView中
经过PhoneWindow的按键分发,又来到DecorView的superDispatchKeyEvent()方法
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
public boolean superDispatchKeyEvent(KeyEvent event) {
//...
//执行 父类的dispatchKeyEvent()方法
//父类是FrameLayout,发现FrameLayout没有dispatchKeyEvent()
//进入ViewGroup 【1.5】
if (super.dispatchKeyEvent(event)) {
return true;
}
return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);
}
}
【1.5】
按键分发来到ViewGroup中
至此我们按键的分发:
DecorView
【---->】
Activity
【---->】
DecorView
【---->】
ViewGroup
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
//
@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)) {
//如果 view 处理返回 true,这里也返回 true
//【1.6】 View
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
//ViewGroup自己把KeyEvent交给mFocused处理
//ViewGroup处理 也是回调view 的 dispatchKeyEvent()
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
//...
return false;
}
}
通过flag的判断,有两个处理路径,也可以看到在处理keyEvent时,ViewGroup扮演两个角色:
1.View的角色,也就是此时keyEvent需要在自己与其他View之间流转
2.ViewGroup的角色,此时keyEvent需要在自己的子View之间流转
【1.6】 View
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public boolean dispatchKeyEvent(KeyEvent event) {
//如果设置了mOnKeyListener,则优先走onKey方法
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
//把View自己当作参数传入,调用KeyEvent的dispatch方法 在里面回调 onKeyDown/onKeyUp
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
//...
return false;
}
}
View这里,会优先处理OnKeyListener的onKey回调。
然后才可能会走KeyEvent的dispatch,最终走到View的OnKeyDown或者OnKeyUp
ok,我要开始写绕口令了...
上面的执行流程总结就是:
DecorView.dispatchKeyEvent() 如果下面向上返回 false 交给 window 处理
【---->】
Activity.dispatchKeyEvent() 如果下面向上返回 false 先交给自己的onKey Down/onKeyUp处理 ,还是返回 false,往上抛出交给 DecorView 处理
【---->】
PhoneWindow.superDispatchKeyEvent() 如果下面向上返回 false ,往上抛出交给 Activity 处理
【---->】
DecorView.superDispatchKeyEvent() 如果下面向上返回 false ,往上抛出交给 PhoneWindow 处理
【---->】
DecorView.this.super.dispatchKeyEvent(event) 如果下面向上返回 false ,往上抛出
【---->】
ViewGroup.dispatchKeyEvent() 如果下面向上返回 false ,看看自己或者子 view 有没有焦点,如果有,自身处理 先执行onKey(),
没有处理,再执行onKeyDown/onKeyUp ,还是返回 false,往上抛出
【---->】
View.dispatchKeyEvent 看看有没有焦点,如果有,自身处理 先执行onKey(),没有处理,再执行onKeyDown/onKeyUp ,还是返回 false,往上抛出
如果以上没有处理
执行自动寻焦的逻辑
【2】 ViewRootImpl.ViewPostImeInputStage.processKeyEvent()
按键事件自动寻焦
代码回到ViewRootImpl【1.1】返回 false ,代码继续往下执行
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
final class ViewPostImeInputStage extends InputStage {
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
//按键处理的起点
return processKeyEvent(q);
} else {
//...
}
}
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
//【1.1】
//这里的 mView 当然是我们的 DecorView,
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
//...
// Handle automatic focus changes.
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (groupNavigationDirection != 0) {
//...
} else {
//关键代码
// 自动寻焦的逻辑【2.2】
if (performFocusNavigation(event)) {
return FINISH_HANDLED;
}
}
}
return FORWARD;
}
}
【2.2】 performFocusNavigation()
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
private boolean performFocusNavigation(KeyEvent event) {
//...
if (direction != 0) {
View focused = mView.findFocus();
if (focused != null) {
//关键代码
//【2.3】
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
//...
}
// Give the focused view a last chance to handle the dpad key.
if (mView.dispatchUnhandledMove(focused, direction)) {
return true;
}
} else {
if (mView.restoreDefaultFocus()) {
return true;
}
}
}
return false;
}
}
【2.3】View.focusSearch()
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public View focusSearch(@FocusRealDirection int direction) {
if (mParent != null) {
//关键代码
return mParent.focusSearch(this, direction);
} else {
return null; }}}
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
//
public View focusSearch(View focused, int direction) {
//最终进入 if 分支
if (isRootNamespace()) {
//关键代码 【2.4】
return FocusFinder.getInstance().findNextFocus(this, focused, direction);
} else if (mParent != null) {
//不是 root 布局 继续递归,最终进入 if 分支
return mParent.focusSearch(focused, direction);
}
return null; }
}
View并不会直接去找,而是交给它的parent去找,
而 VieGroup 呢?来一个递归的判断直到是顶层布局,执行FocusFinder.getInstance().findNextFocus(this, focused, direction)
【2.4】FocusFinder
public class FocusFinder {
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
View next = null;
ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
if (focused != null) {
//优先从xml或者代码中指定focusid的View中找
next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
}
if (next != null) {
return next;
}
ArrayList<View> focusables = mTempList;
try {
focusables.clear();
effectiveRoot.addFocusables(focusables, direction);
if (!focusables.isEmpty()) {
//其次,根据算法去找,原理就是找在方向上最近的View
next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
}
} finally {
focusables.clear();
}
return next;
}
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
int direction, ArrayList<View> focusables) {
if (focused != null) {
//...
} else {
//...
}
}
}
- 优先找开发者指定的下一个focus的视图 ,就是在xml或者代码中指定NextFocusDirection Id的视图
- 其次,根据算法去找,原理就是找在方向上最近的视图
更多内容 欢迎关注公众号
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- mysqlbinlog命令详解 Part 1-实验环境准备
- mysqlbinlog命令详解 Part 2 -MySQL 事件类型
- mysqlbinlog命令详解 Part 4 -查看行事件具体SQL语句
- mysqlbinlog命令详解 Part 5 通过位置和时间查看日志
- mysqlbinlog命令详解 Part 6 读取远程MySQL服务器日志
- mysqlbinlog命令详解 Part 7 备份二进制日志文件
- mysqlbinlog命令详解 Part 8 指定 Server ID
- mysqlbinlog命令详解 Part 9 MySQL备份策略
- mysqlbinlog命令详解 Part 10 恢复MySQL
- mysqldump命令详解 Part 2- 建立触发器 事件
- mysqlbinlog命令详解 Part 11 其他的一些参数
- mysqldump命令详解 Part 1 -MySQL测试数据的构造
- mysqldump命令详解 Part 3- 备份全库
- mysqldump命令详解 Part 4-备份单表
- mysqldump命令详解 Part 5-按条件备份表数据