一点见解: 焦点那点事(二)

一点见解: 焦点那点事(二),第1张

上一篇文章, 一点见解: 焦点那点事(一) , 了解了焦点相关的一些基本知识, 提到焦点切换的关键方法 ViewParent#focusSearch , 本文接着看, 焦点是从什么时候产生的, 又是如何在控件间切换的, 当控件被移除或者新增进布局时焦点又会发生什么变化.

页面创建出来后, 什么时候开始分发焦点?

关于页面创建流程的和绘制过程的文章有很多, 这里不再累述, 通过这些文章, 我们可以知道页面控件的绘制入口是 ViewRootImpl#performTraversals 方法.

在这个方法中, 如果是 第一次 执行这个方法, 同时, ViewRoot 相关联的 DecorView 没有焦点控件, 那么就会调用 DecorView#requestFocus , 实际上也就是调用了 ViewGroup#requestFocus , 上一篇文章 一点见解: 焦点那点事(一) 介绍过, 在这个方法里, 会遍历子控件, 执行 View#requestFocus 直到某个控件持有焦点.

虽然在触摸模式也能产生焦点, 但是一般不会用到, 因此这里着重分析通过键盘 *** 作来切换焦点的情况.

既然是通过键盘切换焦点, 因此从键盘事件开始入手.

关于输入事件的处理流程已经有很多文章了, 这个也不是本文关注的重点, 因此不再累述, 可以参考 原来Android触控机制竟是这样的? .

概括起来就是

在这个方法里, 会先把事件传递给 ViewGroup#dispatchKeyEvent() 方法, 如果这个方法没有消费掉这个事件, 并且这个事件是 方向事件 按下事件 , 例如 KeyEvent.KEYCODE_DPAD_LEFT 等, 那么就会触发焦点切换, 也就是 focusSearch 方法.

首先看这个方法, 因为在 ViewRootImpl 中持有的是 DecorView , 它本质上是一个 FrameLayout , 因此分发键盘事件时实际调用的会是 ViewGroup#dispatchKeyEvent() .

在这个方法里

在 View#dispatchKeyEvent 里面

由上可以知道, 一般情况下, ViewGroup#dispatchKeyEvent() 只会消费确认事件, 方向事件是会继续执行下一步的.

方向事件 按下事件 表明, 在按下的时候就会触发焦点切换了, 这解释了为什么 长按方向键会一直切换焦点 .

焦点切换时

这个过程说明

上一篇文章提到控件要获取焦点必须符合

改变控件的这两个状态, 最终会调用 View#setFlags 方法, 在该方法中, 如果焦点控件是变为了不可见或者不可获取焦点, 那么就会调用 View#clearFocus 来清除焦点, 跟手动清除焦点流程一样.

如果父控件突然变为了 FOCUS_BLOCK_DESCENDANTS , 不会影响当前焦点控件的状态, 只会 影响下一次 焦点分发/查找的流程.

控件被移除, 最终都会调用 ViewGroup#removeViewInternal 方法, 在这个方法中, 首先会调用 View#unFocus 来清除焦点, 具体参考上一篇文章的介绍, 因为 View#unFocus 方法不会调用 ViewParent#clearChildFocus , 因此 ViewGroup 会主动调用自己的 clearChildFocus 方法, 紧接着会调用 View#rootViewRequestFocus 方法, 在这个方法中会调用 getRootView()#requestFocus , 然后就会 遍历一次控件树 来重新分发焦点.

和失去焦点资格类似, 最终会调用 View#setFlags 方法, 然后调用 ViewParent#focusableViewAvailable 方法, 默认实现中会一直向上级父控件传递, 最终就会调用 ViewRootImpl#focusableViewAvailable 方法, 在这个方法中, 两种情况下这个新控件可以获得焦点

通过 addView 方式添加控件, 都会调用 ViewGroup#addViewInner 方法, 在这个方法中, 如果新增的控件的 hasFocus 方法为 true , 那么就会调用父控件的 ViewParent#requestChildFocus , 参考上一篇文章可以知道, 在这个方法里会把现有的焦点控件的焦点清除掉. 也就是说, 新增的控件如果持有焦点, 那么就会 替换现有的控件成为焦点控件 .

如果新增的控件没有持有焦点, 即使它有焦点资格, 也不会有任何焦点相关的回调

RecyclerView 是一个非常常用的控件, 其中列表中的子控件会复用/移除/新增等, 因此焦点的处理也比较特殊, 下一篇会详细分析 RecyclerView 的焦点处理逻辑, 以此得到移除焦点控件后重新分发焦点的解决方案.

ViewSwitcher 代表了视图切换组件, 本身继承了FrameLayout ,可以将多个View叠在一起 ,每次只显示一个组件.当程序控制从一个View切换到另个View时,ViewSwitcher 支持指定动画效果。

ViewAnimator是一个基类,它继承了 FrameLayout,因此它表现出FrameLayout的特征,可以将多个View组件叠在一起。 ViewAnimator额外增加的功能正如它的名字所暗示的一样,ViewAnimator可以在View切换时表现出动画效果。

iewAnimator及其子类的继承关系图如下图所示:

ViewAnimator

ViewAnimator及其子类也是一组非常重要的UI组件,这种组件的主要功能是增加动画效果,从而使界面更加炫。使用ViewAnimator 时可以指定如下常见XML属性。

ViewSwitcher继承ViewAnimator,主要用于视图的切换:

ViewSwitcher重写了addView(View, int, ViewGroup.LayoutParams)方法,使其子控件不超过2个:

通过配置属性指定切换动画:

setFactory设置视图

ViewSwitcher中setFactory(ViewFactory)方法设置了子视图,调用obtainView()方法添加了两个子控件。

切换图片案例:

进入动画anim_enter_from_bottom.xml

退出动画anim_exit_to_top.xml

动态给ViewSwitcher添加子View

多个视图切换

有多个视图需要时,需要自定义next()和previous()方法。

为了给ViewSwitcher 添加多个组件, 一般通过ViewSwitcher 的setFactory 方法为止设置ViewFactory ,并由ViewFactory为之创建View 即可.

进入动画anim_enter_from_top.xml

退出动画anim_exit_to_bottom.xml

登陆界面布局:

ViewSwitcherActivity

slide_in_from_right.xml

slide_out_to_right.xml

slide_in_from_left.xml

slide_out_to_left.xml

ViewFlipper继承ViewAnimator,用于视图的轮播。

主要方法:

startFlipping()用于手动开始轮播,而stopFlipping()则停止轮播。

showNext()和showPrevious()显示视图的切换。

ImageSwitcher和TextSwitcher的继承关系是一样的。两个重要的父类:ViewSwitcher和ViewAnimator。

继承于ViewSwitcher,说明具备了切换功能,

继承于ViewAnimator,说明具备了动画功能。

ViewGroup和View在Android中都是视图类,但是它们的作用和功能有所不同。

View是Android中最基本的UI组件,它用于显示一些简单的控件,例如按钮、文本框、图像等。View可以包含一些简单的属性,例如背景色、宽度、高度等。

ViewGroup是一个容器,它可以包含其他的View和ViewGroup。ViewGroup有一些特殊的属性,例如布局方式、子视图的位置等。因此,ViewGroup拥有一些View没有的方法,例如addChildView()、removeChildView()等,用于添加、删除子视图。

总之,ViewGroup和View虽然都是视图类,但是它们的作用和功能有所不同,因此在Android中它们拥有不同的方法。


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/bake/7835957.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-10
下一篇2023-04-10

发表评论

登录后才能评论

评论列表(0条)

    保存