天天看点

View学习(三)- View的布局(layout)过程

前段开始学习View的工作原理,前两篇博客的草稿都已经写好了,本想一鼓作气写完所有的相关文章,然后经历了一段连续加班,结果今天准备继续写文章时,把之前写好的东西都忘记了,又重新梳理了一遍,所以说那怕就是已经掌握的知识,也要记得温故而知新。

言归正传,之前我们讨论过了measure过程,measure过程完成之后,我们就可以通过 <code>getMeasuredWidth</code>或<code>getMeasuredHeight</code>来得到View的宽高尺寸了。而知道了宽高尺寸之后,剩下的就是布局(layout)过程了。说直白点,怎么把具一个宽高已定的View摆放在屏幕上。

我们先来看一个demo。

具体的实现效果,是这个样子。

View学习(三)- View的布局(layout)过程

我们暂且不讨论代码当中的实现原理之类的,仅仅就想想一种场景,给你一些尺寸固定但不同的盒子,摆放在一个大房间的地面上,我们应该怎么摆放呢。

之所以要根据这个demo来设想这种场景,是因为我觉的,不管代码怎么写,但是基本的道理都不复杂,正所谓大道至简,(毕竟我们不是做那些高大上的算法的)。自己根据场景去设想怎么摆放,同样的也有助于理解代码。

假设我们按照 <code>LinearLayout</code>竖直方式的规则来摆放,那么开始摆放第一个盒子肯定从房间的左上角开始计算位置,根据父容器padding,本身的margin等来决定具体摆放的坐标。然后再在竖直方向上 向前推进摆放第二个盒子。

我并不知道上面那段文字有没有能清楚的表达出我的意思, 但是这是我前段时间关于这个layout的思考过程。思考过程本身就很难表达,但是我想这样思考是有助于理解这个过程的。

和measure过程一样,layout过程也是一个递归过程,并且ViewGroup类本身不 应该具体实现<code>onLayout</code>,具体实现过程应该放在<code>Framelayout</code>,<code>LinearLayout</code>,<code>RelativieLayout</code>等容器类中。

当layout过程完成之后,我们就可以得到View的左上角和右下角的坐标了。(就是left,top,right,bottom)。值得注意的是这个坐标是相对坐标,就是相对于View父容器的坐标。所以通过递归来计算是最自然最方便的方式。

这几天看了关于编程语言历史方面的文章, 在早期的编程语言中,是没有递归这个概念的,是后来有人想出了这个概念,才逐渐的在各种编程语言中实现。所以说现在我们来看稀奇平常很自然的概念,但是当初第一个想出这个概念的人,那就是天才了。

下面我们就从源码角度来进行分析。

<code>performTraversals</code>调用了<code>PerformLayout</code>方法。

<code>host</code>其实就是<code>DecorView</code>了,在执行<code>layout</code>时,注意传递的头两个参数都是0,这说明 整个layout过程是从屏幕坐标系的 左上角开始执行的。

下一步还是走到了<code>View#Layout()</code>, <code>View#Layout</code>方法如下:

<code>setFrame()</code>方法的作用也不复杂。

通过分析以上代码可以知道,在<code>layout()</code>方法当中,主要做的就是将left,top,right,bottom方法保存起来,而View自身怎么被布局(其实我觉的用摆放这个词更合适)。则是父容器需要完成的工作。

因为总所周知的原因(其实就是各个容器类ViewGroup布局子元素的方式差异很大)。ViewGroup并没有实现具体的<code>onLayout</code>方法,各个具体的ViewGroup,比如<code>LinearLayout</code>,<code>RelativeLayout</code>,<code>FrameLayout</code>等,它们都有它们具体的 <code>onLayout</code>实现方式。

我们以<code>LinearLayout</code>为例来进行分析。

和measure过程是一样的套路,也是分为两种不同的情况,我们就以 竖直方向为例。(源码比较多,并且实际也要考虑使用<code>layout_weight</code>的情况,不过我们仅仅只抽取一部分分析基本原理)。

在该方法中,通过调用<code>setChildFrame</code>来为子元素指定相应的位置。

而<code>setChildFrame</code>只是很简单的一句话。

而<code>child.layout</code>其实不就是继续调用 <code>onLayout</code>之类的嘛。所以这样就形成了完美的递归。直到把整个View树都完成了layout过程。

自此,我们就分析完了layout过程,相比与measure过程,简单了很多。并且理解起来也更容易,就想想在一个大房间的地面上,我们怎么使用递归的方式来摆放很多的盒子。

最后再来看看几个方法。

同样都是得到宽高的方式,这两组方法有什么区别呢?现在我们了解了Measure和layout过程,那么我们就知道了,<code>getMeasuredWidth()</code>方法是在measure过程完成之后可以调用的,而<code>getWidth()</code>方法则是在 layout过程之后可以调用的。并且几乎在所有情况下,两者的返回值都是相等的。

作者:

www.yaoxiaowen.com

github:

https://github.com/yaowen369

欢迎对于本人的博客内容批评指点,如果问题,可评论或邮件([email protected])联系