浅析View的requestLayout()方法

上周,为了实现在代码中动态移动View的位置的需求,遇到难以处理的问题。

先看看需求

在XML布局文件中,摆放好View的位置。接下来以此布局位置播放动画,当播放到一个结点的时候,要让一个ImageView、两个TextView出现在另外一个位置上,而且会有一些变化。除了基本的XY的左边变化了之外,ICON的图标的大小,TextView的TextSize、TextColor、Lines、maxWidth都产生了变化。本来用两个View,分别布局在对应的位置上,控制他们的Visibility就可以,但因为一些特殊性,不能那么做,只能让View在播放动画的同时进行View位置的移动。

解决方案

在布局文件中摆放好不显示的替身View,然后当变化的时候,将替身的XY值传递给要变化的View。
理论上是可以实现的,代码如下:


浅析View的requestLayout()方法_第1张图片
image.png

实际上确实困难重重。大小可以变,但是位置就是不能对上。

最后总结出问题出在requestLayout()上。

浅析requestLayout()

1.requestLayout()和invalidate()的区别

requestLayout():view调用这个方法要求parent view重新进行一次测量、布局、绘制这三个流程来更新自己位置。
invalidate():view调用这个方法迫使view进行重新绘制。
一句话,requestLayout()的效果是重新布局自己在父布局中的位置,invalidate()的效果是强制调用自己的onDraw()方法。

2.分析requestLayout()

我们先看View的requestLayout()方法的源码:

浅析View的requestLayout()方法_第2张图片
image.png

在requestLayout方法中,首先先为当前View设置上PFLAG_FORCE_LAYOUT的标记位,表示当前的View需要重新绘制。

接下来会判断当前View树是否正在布局流程,这会调用ViewRootImpl的isLayoutRequested()方法,如下:

image.png

当父布局已经开始重新布局的时候,不会继续传递重新布局的请求,而是带着FORCE_LAYOUT的标记等待重新绘制的流程走到这里。

当并没有已经在重新布局的时候,接着调用mParent.requestLayout方法,为父容器添加PFLAG_FORCE_LAYOUT标记位,而父容器又会调用它的父容器的requestLayout方法.
requestLayout的事件会层层上传,直到DecorView,即根View,而根View又会传递给ViewRootImpl。
即是说任何一个View的requestLayout事件,最终会被ViewRootImpl接收并得到处理。

接下来看一下ViewRootImpl的requestLayout方法:


浅析View的requestLayout()方法_第3张图片
image.png
浅析View的requestLayout()方法_第4张图片
image.png

在这里,调用了scheduleTraversals方法,这个方法是一个异步方法,post了一个Runnable,我们继续跟进,看一下这个TraversalRunnable接口。

浅析View的requestLayout()方法_第5张图片
image.png

这个Runnable只调用了一个方法,doTraversal(),如下:


浅析View的requestLayout()方法_第6张图片
image.png

浅析View的requestLayout()方法_第7张图片
image.png

最终我们看到,会调用到performTraversals方法,这也是View工作流程的核心方法。这个方法从1282行,一直到2082行,一共800行代码,太长了,就不展示了。

这个方法有多重要呢?

整个Android的UI绘制机制是从哪里开始的即入口在哪里呢?答案就是ViewRootImpl类的performTraversals()方法。在这个方法内部,分别调用measure、layout、draw方法来进行View的三大工作流程。

至此,我们就能明白了,requestLayout()会牵动出整个Android绘制机制重新走一遍流程。

接下来,继续看一下View的measure()方法:

浅析View的requestLayout()方法_第8张图片
image.png

浅析View的requestLayout()方法_第9张图片
image.png

首先是判断一下标记位,如果当前View的标记位为PFLAG_FORCE_LAYOUT,那么就会进行测量流程,调用onMeasure,对该View进行测量,接着最后为标记位设置为PFLAG_LAYOUT_REQUIRED,这个标记位的作用就是在View的layout流程中,如果当前View设置了该标记位,则会进行布局流程。

浅析View的requestLayout()方法_第10张图片
image.png

image.png

到目前为止,requestLayout的流程便完成了。

随着流程的梳理完,导致我们出问题的原因已经找到,再看一眼出问题的代码:


浅析View的requestLayout()方法_第11张图片
image.png

我们先看一下,TextView的setTextSize方法,


浅析View的requestLayout()方法_第12张图片
image.png

我们会发现,其中调用了requestLayout()方法和invalidate()方法。(requestLayout()方法的目的是重新调整TextView的摆放位置,invalidate()方法的目的是重新绘制一遍文字内容)
当这里已经调用过requestLayout()方法的时候,由于ViewRootImpl对象是唯一的,所以其他的requestLayout()方法的调用都会被搁置等待。而这个方法又是异步的,所以如果我们同步的去变更调整TextView的位置,都是根据的旧布局取到的XY坐标。所以解决方法如下:
浅析View的requestLayout()方法_第13张图片
image.png

不需要主动调用requestLayout()方法了,在同步的变化所有的View的属性之后,抛一个消息等待requestLayout()结束之后,再赋值,就可以在新布局的基础上变化了,于是就按照预期的产生位置变动了。

你可能感兴趣的:(浅析View的requestLayout()方法)