请尊重分享成果,转载请注明出处:

http://blog.csdn.net/hejjunlin/article/details/52263256

前言:View框架写到第六篇,发现前面第二篇竟然没有,然后事情是在微信公众号发了,忘记在博客上更新,所以关注微信公众号的应该都看过了,趁今天有时间遂补上。(PS:本篇文章中源码均是android 6.0,请知晓)

本来之前说view下篇是写onMeasure,onLayou,onDraw相关的,笔者做盒子开发,遥控器按键,碰到的都是焦点控制相关。所以先把焦点放到了onMeasure,onLayou,onDraw之前。以前踩过不少坑,最近公司同事做分享,遂下决心总结下,Agenda如下:

  • ViewRoot
  • View的焦点
  • ViewGroup的焦点
  • 父容器焦点的处理
  • 失去焦点或清除焦点
  • 焦点移动
  • FocusFinder查找焦点
  • 总结

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 -> requestFocus()

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

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

View.java -> requestFocusNoSearch()

View.java -> handleFocusGainInternal()

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

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

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

ViewGroup的焦点

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

ViewGroup.java -> requestFocus()

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

  • FOCUS_BLOCK_DESCENDANTS 不允许子View获取焦点,那么按照View的流程进行
  • FOCUS_BEFORE_DESCENDANTS 先按照View的流程处理, 如果自己不能获取焦点则给孩子处理
  • FOCUS_AFTER_DESCENDANTS 先尝试给子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()

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

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

失去焦点或清除焦点

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

ViewGroup.java -> unFocus()

View.java -> unFocus() -> clearFocusInternal()

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

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

View.java -> clearFocus()

ViewGroup.java -> clearFocus()

ViewGroup.java -> clearChildFocus()

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

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

  • boolean isFocusable() View是否可以获取焦点
  • boolean isFocused() View是否获取焦点
  • boolean hasFocus() View/ViewGroup内部是否有焦点
  • View findFocus() 取到View/ViewGroup内部的焦点View
  • View getFocusedChild() 取到ViewGroup内部有焦点的子View
  • View getRootView() 取到根节点View(一般是DecorView或顶层ViewGroup)

焦点移动

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

ViewRootImpl.java -> processKeyEvent()





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

  1. 如果View没有处理按键, 把上下左右tab等按键转换成对应方向
  2. 在当前焦点View上通过focusSearch方法查找对应方向的下一个View
  3. 查找到的View调用requestFocus,因此主要的流程在focusSearch中

View.java -> focusSearch()

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

ViewGroup.java -> focusSearch()

ViewRootImpl.java -> focusSearch()

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

FocusFinder查找焦点

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

如下:

FocusFinder.java -> findNextFocus()

核心逻辑就两步, 先查找setNextFocusXXId设置的View, 如果没有按照就近算法查找.

具体算法不再分析, SDK里面有源码.

总结

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

  • requestFocus和clearFocus直接对View清除或转移焦点
  • 除了onFocusChangeListener,还可以在onFocusChange方法中实现一些View失去/获得焦点时通知
  • 对ViewGroup, 如果只需要在子View获取焦点时得到通知, 有requestChildFocus方法.
  • 重写onRequestFocusInDescendants方法可以控制某些情景下ViewGroup焦点
  • 控制焦点移动可以重写focusSearch方法
  • 查找焦点的过程,主要是从View的focusSearch(…)方法开始,从当前焦点开始逐层往外,最终在最外层布局执行FocusFinder中的核心方法来获得下个焦点所在的视图view.
  • 另外还有FocusFinder工具和上面的辅助方法.
  • 在view获得焦点之前,必须先判断该view是否具有获得焦点的权限,可通过isFocusable和isFocusableInTouchMode来判断;//这个前文代码中有
  • 同时可以通过setFocusable和setFocusableInTouchMode来设置指定view具有获取焦点的权限.
  • 在XML阻止子view获得焦点的属性是:android:descendantFocusability =”blocksDescendants”,但是如果当前activity经过了pause或者stop后再重新resume后该属性会失效,这时可以在onresume里面加上requestFocusFromTouch方法就能重新时属性生效.

第一时间获得博客更新提醒,以及更多android干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。

如果你觉得好,随手点赞,也是对笔者的肯定,也可以分享此公众号给你更多的人,原创不易

Android View框架总结(二)View焦点的更多相关文章

  1. Android显示框架:自定义View实践之绘制篇

    文章目录 一 View 二 Paint 2.1 颜色处理 2.2 文字处理 2.3 特殊处理 三 Canvas 3.1 界面绘制 3.2 范围裁切 3.3 集合变换 四 Path 4.1 添加图形 4 ...

  2. Android Afinal框架学习(二) FinalActivity 一个IOC框架

    框架地址:https://github.com/yangfuhai/afinal 相应的源代码: net.tsz.afinal.annotation.view.* FinalActivity Fina ...

  3. Android 网络通信框架Volley(二)

    Volley提供2个静态方法: public static RequestQueue newRequestQueue(Context context) {} public static Request ...

  4. Android网络框架-Volley实践 使用Volley打造自己定义ListView

    这篇文章翻译自Ravi Tamada博客中的Android Custom ListView with Image and Text using Volley 终于效果 这个ListView呈现了一些影 ...

  5. Android View框架总结(八)ViewGroup事件分发机制

    请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52298780 上篇分析了View的事件分发流程,留了一个问题:如果上 ...

  6. Android View框架总结(六)View布局流程之Draw过程

    请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52236145 View的Draw时序图 ViewRootImpl.p ...

  7. Android View框架总结(四)View布局流程之Measure

    View树的measure流程 View的measures时序图 View布局流程之measure measure过程 View的measure过程 ViewGroup的measure过程 Frame ...

  8. Android View框架总结(一)

    View和Activity的区别 View有哪些? ViewGroup是什么? 为什么Google产生ViewGroup? View的层级结构是什么? View的onMeasure()/onLayou ...

  9. Android开发艺术探索笔记——View(二)

    Android开发艺术探索笔记--View(二) View的事件分发机制 学习资料: 1.Understanding Android Input Touch Events System Framewo ...

随机推荐

  1. [SDOI2016]生成魔咒

    题目描述 魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示.例如可以将魔咒字符 1.2 拼凑起来形成一个魔咒串 [1,2]. 一个魔咒串 S 的非空字串被称为魔咒串 S 的生成魔咒. 例如 S=[1, ...

  2. allocator

    allocator: 通常c++内存配置和释放操作是这样的: class Fo{}; Fo *p = new Fo; delete p; new算式主要有三个阶段: 调用::operator new配 ...

  3. bzoj3930[CQOI2015]选数 容斥原理

    3930: [CQOI2015]选数 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 1383  Solved: 669[Submit][Status] ...

  4. bzoj1791: [Ioi2008]Island 岛屿 单调队列优化dp

    1791: [Ioi2008]Island 岛屿 Time Limit: 20 Sec  Memory Limit: 162 MBSubmit: 1826  Solved: 405[Submit][S ...

  5. 10分钟 5步 发布以太坊 ERC20 代币

    1.安装 METAMASK Brings Ethereum to your browser 一个可以浏览器上进行操作的以太坊钱包,推荐 Chrome. Chrome 插件安装地址: https://c ...

  6. 记录一次widora sdk编译ipk 实战编译redis

      因为业务需求,需要用到redis存储一点简单的数据,因为redis有良好的哈希机制,可以完美实现我的某些需求,但openwrt官方提供memcached的ipk并没有提供redis,没办法,只能自 ...

  7. java中什么是序列化和反序列化

    序列化:能够把一个对象用二进制的表示出来. 类似我第一个字节表示什么属性名词,第二个字节表示什么属性值,第几个字段表示有几个属性等. 而且这个二进制可以写到硬盘或者在网络上传输但不会破坏他的结构.一般 ...

  8. centos7.2中文乱码解决办法

    centos7.2 中文乱码解决办法 1.查看安装中文包: 查看系统是否安装中文语言包 (列出所有可用的公共语言环境的名称,包含有zh_CN) # locale -a |grep "zh_C ...

  9. 微信小程序 发现之旅(一)—— 项目搭建与页面跳转

    开发微信小程序需要注册一个小程序账号,具体流程可以参照官方教程: https://mp.weixin.qq.com/debug/wxadoc/dev/index.html 开通账户之后,在 “开发设置 ...

  10. H5--Web Worker

    Web Worker是H5的新特性. JS是单线程的,所以在消息队列中如果用户想进行一些阻塞操作,比如时延timeout和定时器interval,或者是数据量较大及处理过程非常长的场景,就很容易出现页 ...