android开发

Android View焦点总结

2017-09-23  本文已影响195人  passiontim

本来之前说view下篇是写onMeasure,onLayou,onDraw相关的,笔者做盒子开发,遥控器按键,碰到的都是焦点控制相关。所以先把焦点放到了onMeasure,onLayou,onDraw之前。

Android View焦点

Android焦点相关逻辑大部分都在都在View, ViewGroup和FocusFinder三个类中.

ViewRoot

View对象都有一个mParent变量(添加到ViewGroup后), 代指其父容器. 绝大部分View的mParent都是ViewGroup类型, 除了根节点. 一个Window中View根节点DecorView的mParent称为ViewRoot, 在安卓4.0后ViewRoot对应ViewRootImpl, 它不是View的子类, 而是个ViewParent. ViewRootImpl是连接Window和DecorView的纽带, View的焦点, 按键, 布局, 渲染等流程都是从ViewRoot中开始的.

View的焦点

基本流程如下

View(包括ViewGroup)获取焦点都通过如下三个方法

View.java


从上面可以看到前两个最终会执行到第三个方法.

最后的requestFocusNoSearch先判断是否可以获取焦点, 然后进入下面的最后流程:

View.java


上面的流程比较简单: 如果当前没有焦点, 先置焦点标志, 再通知parent, 然后刷新图片.

  1. 主要的流程在mParent的requestChildFocus里面, 后面会分析. 那里会逐层向上修改焦点View并清除原来有焦点的View的焦点

  2. onFocusChange会触发invalidate刷新, 然后调用onFocusChangeListener. 默认情况每个View只能设置一个onFocusChangeListener, 而开发中经常遇到需要设置多个Listener的情况, 我们就可以重写onFocusChange方法, 实现回调多个onFocusChangeListener的需求.

ViewGroup的焦点

ViewGroup获取焦点是在View获取焦点流程中多了内部焦点处理

ViewGroup.java


上面代码中descendantFocusability决定了是先按View焦点流程处理(自己处理焦点)还是先把给子View处理

默认值FOCUS_BEFORE_DESCENDANTS, 我们可以通过setDescendantFocusability(int d)设置

onRequestFocusInDescendants方法是给子类重写使用, 可以控制子View处理焦点. 默认按照子View顺序处理, direction向下或向右则从第一个开始, 向上或向左则从最后一个开始, 直到某个子View获取焦点

注意此方法只在此ViewGroup及其上层View上调用requestFocus时会执行到

父容器焦点的处理

在View获取焦点流程中会调用mParent.requestChildFocus, 维护View树上焦点唯一, 在各层ViewGroup中保存有焦点的子View

ViewGroup.java


先清除自己的焦点, 如果原来内部有焦点, 先清除其焦点, 保存获取焦点的孩子, 然后调用上一层的requestChildFocus. 最后的调用可知, 这个方法会一直调用到View的树的root节点.

在当前ViewGroup内部, 任何一个孩子取得焦点都会执行到这个方法, 因此此方法也是ViewGroup得知孩子焦点变化的方法之一.(可惜不能得知孩子失去焦点)

失去焦点或清除焦点

获取焦点可以是主动的, 但失去焦点一般都是被动的(见上面的代码), 因此逻辑相对简单, 只要清除焦点状态即可.

ViewGroup.java


View.java


注意上面的方法是默认package访问级别的, 我们无法重写也不能调用

也可以主动清除焦点, 与获取焦点流程相似

ViewGroup.java


View.java


ViewGroup.java


以上是安卓View系统焦点处理的全部流程和涉及到的方法, ViewRootImpl的requestChildFocus和clearChildFocus实现我们不需要关注

另外还有以下一些辅助方法

焦点移动

除了在代码里面控制焦点, 系统对没有处理的方向键等一些按键自动按照焦点移动来处理, 见下面代码

ViewRootImpl.java



代码比较上, 但是主要做了三个步骤

  1. 如果View没有处理按键, 把上下左右tab等按键转换成对应方向

  2. 在当前焦点View上通过focusSearch方法查找对应方向的下一个View

  3. 查找到的View调用requestFocus
    因此主要的流程在focusSearch中

View.java


普通View查找什么都没做, 交给parent来完成.

ViewGroup.java


ViewRootImpl


我们可以重写focusSearch控制焦点移动顺序, 而默认的焦点移动顺序由FocusFinder决定

FocusFinder查找焦点

FocusFinder为public的工具类, 主要就两个方法, 可以在给定的View内在指定方向查找指定View或坐标的下一个焦点
如下:

核心逻辑就两步, 先查找setNextFocusXXId设置的View, 如果没有按照就近算法查找.
具体算法不再分析, SDK里面有源码.

总结

综合上面的流程分析, 我们在实现自定义View时, 对焦点的特殊需求有如下思路

  1. requestFocus和clearFocus直接对View清除或转移焦点

  2. 除了onFocusChangeListener, 还可以在onFocusChange方法中实现一些View失去/获得焦点时通知

  3. 对ViewGroup, 如果只需要在子View获取焦点时得到通知, 有requestChildFocus方法.

  4. 重写onRequestFocusInDescendants方法可以控制某些情景下ViewGroup焦点

  5. 控制焦点移动可以重写focusSearch方法

  6. 另外还有FocusFinder工具和上面的辅助方法.

上一篇下一篇

猜你喜欢

热点阅读