源码解析Android中View的layout布局过程

(转载)http://blog.csdn.net/iispring/article/details/50366021

Android中的Veiw从内存中到呈现在UI界面上需要依次经历三个阶段:量算 -> 布局 -> 绘图,关于View的量算、布局、绘图的总体机制可参见博文 《 Android中View的布局及绘图机制》。量算是布局的基础,如果想了解量算的细节,可参见博文《源码解析Android中View的measure量算过程》。本文将从源码角度解析View的布局layout过程,本文会详细介绍View布局过程中的关键方法,并对源码加上了注释以进行说明。

对View进行布局的目的是计算出View的尺寸以及在其父控件中的位置,具体来说就是计算出View的四条边界分别到其父控件左边界、上边界的距离,即计算View的left、top、right、bottom的值。


layout

layout()方法是View布局的入口,其源码如下所示:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; background: transparent; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal;">    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">layout</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> l, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> t, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> r, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> b) {
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//成员变量mPrivateFlags3中的一些比特位存储着和layout相关的信息</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>) {
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//如果在mPrivateFlags3的低位字节的第4位(从最右向左数第4位)的值为1,</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//那么就表示在layout布局前需要先对View进行量算,</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//这种情况下就会执行View的onMeasure方法对View进行量算</span>
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//量算完成后就会将mPrivateFlags3低位字节的第4位重置为0,</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//移除掉标签PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT</span>
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> oldL = mLeft;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> oldT = mTop;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> oldB = mBottom;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> oldR = mRight;

        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//如果isLayoutModeOptical()返回true,那么就会执行setOpticalFrame()方法,</span>
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//否则会执行setFrame()方法。并且setOpticalFrame()内部会调用setFrame(),</span>
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//所以无论如何都会执行setFrame()方法。</span>
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//setFrame()方法会将View新的left、top、right、bottom存储到View的成员变量中</span>
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//并且返回一个boolean值,如果返回true表示View的位置或尺寸发生了变化,</span>
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//否则表示未发生变化</span>
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);


        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//如果View的布局发生了变化,或者mPrivateFlags有需要LAYOUT的标签PFLAG_LAYOUT_REQUIRED,</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//那么就会执行以下代码</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//首先会触发onLayout方法的执行,View中默认的onLayout方法是个空方法</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//不过继承自ViewGroup的类都需要实现onLayout方法,从而在onLayout方法中依次循环子View,</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//并调用子View的layout方法</span>
            onLayout(changed, l, t, r, b);
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//在执行完onLayout方法之后,从mPrivateFlags中移除标签PFLAG_LAYOUT_REQUIRED</span>
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//我们可以通过View的addOnLayoutChangeListener(View.OnLayoutChangeListener listener)方法</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//向View中添加多个Layout发生变化的事件监听器</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//这些事件监听器都存储在mListenerInfo.mOnLayoutChangeListeners这个ArrayList中</span>
            ListenerInfo li = mListenerInfo;
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (li != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span> && li.mOnLayoutChangeListeners != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//首先对mOnLayoutChangeListeners中的事件监听器进行拷贝</span>
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> numListeners = listenersCopy.size();
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> i = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>; i < numListeners; ++i) {
                    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//遍历注册的事件监听器,依次调用其onLayoutChange方法,这样Layout事件监听器就得到了响应</span>
                    listenersCopy.<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">get</span>(i).onLayoutChange(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//从mPrivateFlags中移除强制Layout的标签PFLAG_FORCE_LAYOUT</span>
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//向mPrivateFlags3中加入Layout完成的标签PFLAG3_IS_LAID_OUT</span>
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li><li style="box-sizing: border-box; padding: 0px 5px;">48</li><li style="box-sizing: border-box; padding: 0px 5px;">49</li><li style="box-sizing: border-box; padding: 0px 5px;">50</li><li style="box-sizing: border-box; padding: 0px 5px;">51</li><li style="box-sizing: border-box; padding: 0px 5px;">52</li><li style="box-sizing: border-box; padding: 0px 5px;">53</li><li style="box-sizing: border-box; padding: 0px 5px;">54</li><li style="box-sizing: border-box; padding: 0px 5px;">55</li><li style="box-sizing: border-box; padding: 0px 5px;">56</li><li style="box-sizing: border-box; padding: 0px 5px;">57</li><li style="box-sizing: border-box; padding: 0px 5px;">58</li></ul>
  • 在layout()方法内部刚开始执行的时候,首先会根据mPrivateFlags3变量是否具有标志位PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT判断是否需要执行View的onMeasure()方法。如果具有标志位PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT,则执行onMeasure()方法,从而对View进行量算,量算的结果会保存到View的成员变量中。量算完成后就会将mPrivateFlags3低位字节的第4位重置为0,移除掉标签PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT。

  • 如果isLayoutModeOptical()返回true,那么就会执行setOpticalFrame()方法,否则会执行setFrame()方法。并且setOpticalFrame()内部会调用setFrame(),所以无论如何都会执行setFrame()方法。setFrame()方法会将View新的left、top、right、bottom存储到View的成员变量中,并且返回一个boolean值,如果返回true表示View的位置或尺寸发生了变化,否则表示未发生变化。后面会对setFrame()方法详细介绍。

  • 如果View的布局发生了变化,或者mPrivateFlags有需要LAYOUT的标签PFLAG_LAYOUT_REQUIRED,就会触发onLayout方法的执行,View中默认的onLayout方法是个空方法。不过继承自ViewGroup的类都需要实现onLayout方法,从而在onLayout方法中依次循环子View,并调用子View的layout方法。在执行完onLayout方法之后,从mPrivateFlags中移除标签PFLAG_LAYOUT_REQUIRED。然后会遍历注册的Layout Change事件监听器,依次调用其onLayoutChange方法,这样Layout事件监听器就得到了响应。

  • 最后,从mPrivateFlags中移除强制Layout的标签PFLAG_FORCE_LAYOUT,向mPrivateFlags3中加入Layout完成的标签PFLAG3_IS_LAID_OUT。


setFrame

setFrame()方法是具体用来完成给View分配尺寸以及位置工作的,在layout()方法中会调用setFrame()方法。其源码如下所示:

<code class="hljs java has-numbering" style="display: block; padding: 0px; background: transparent; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal;">    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> <span class="hljs-title" style="box-sizing: border-box;">setFrame</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> left, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> top, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> right, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> bottom) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> changed = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>;

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (DBG) {
            Log.d(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"View"</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span> + <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">" View.setFrame("</span> + left + <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">","</span> + top + <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">","</span>
                    + right + <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">","</span> + bottom + <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">")"</span>);
        }

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//将新旧left、right、top、bottom进行对比,只要不完全相对就说明View的布局发生了变化,</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//则将changed变量设置为true</span>
            changed = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;

            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//先保存一下mPrivateFlags中的PFLAG_DRAWN标签信息</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> drawn = mPrivateFlags & PFLAG_DRAWN;

            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//分别计算View的新旧尺寸</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> oldWidth = mRight - mLeft;
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> oldHeight = mBottom - mTop;
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> newWidth = right - left;
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> newHeight = bottom - top;
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//比较View的新旧尺寸是否相同,如果尺寸发生了变化,那么sizeChanged的值为true</span>
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Invalidate our old position</span>
            invalidate(sizeChanged);

            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//将新的left、top、right、bottom存储到View的成员变量中</span>
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//mRenderNode.setLeftTopRightBottom()方法会调用RenderNode中原生方法的nSetLeftTopRightBottom()方法,</span>
            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//该方法会根据left、top、right、bottom更新用于渲染的显示列表</span>
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//向mPrivateFlags中增加标签PFLAG_HAS_BOUNDS,表示当前View具有了明确的边界范围</span>
            mPrivateFlags |= PFLAG_HAS_BOUNDS;


            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (sizeChanged) {
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//如果View的尺寸和之前相比发生了变化,那么就执行sizeChange()方法,</span>
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//该方法中又会调用onSizeChanged()方法,并将View的新旧尺寸传递进去</span>
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }

            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//有可能在调用setFrame方法之前,invalidate方法就被调用了,</span>
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//这会导致mPrivateFlags移除了PFLAG_DRAWN标签。</span>
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//如果当前View处于可见状态就将mPrivateFlags强制添加PFLAG_DRAWN状态位,</span>
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//这样会确保下面的invalidate()方法会执行到其父控件级别。</span>
                mPrivateFlags |= PFLAG_DRAWN;
                invalidate(sizeChanged);
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//invalidateParentCaches()方法会移除其父控件的PFLAG_INVALIDATED标签,</span>
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//这样其父控件就会重建用于渲染的显示列表</span>
                invalidateParentCaches();
            }

            <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 重新恢复mPrivateFlags中原有的PFLAG_DRAWN标签信息</span>
            mPrivateFlags |= drawn;

            mBackgroundSizeChanged = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mForegroundInfo != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
                mForegroundInfo.mBoundsChanged = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;
            }

            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> changed;
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li><li style="box-sizing: border-box; padding: 0px 5px;">48</li><li style="box-sizing: border-box; padding: 0px 5px;">49</li><li style="box-sizing: border-box; padding: 0px 5px;">50</li><li style="box-sizing: border-box; padding: 0px 5px;">51</li><li style="box-sizing: border-box; padding: 0px 5px;">52</li><li style="box-sizing: border-box; padding: 0px 5px;">53</li><li style="box-sizing: border-box; padding: 0px 5px;">54</li><li style="box-sizing: border-box; padding: 0px 5px;">55</li><li style="box-sizing: border-box; padding: 0px 5px;">56</li><li style="box-sizing: border-box; padding: 0px 5px;">57</li><li style="box-sizing: border-box; padding: 0px 5px;">58</li><li style="box-sizing: border-box; padding: 0px 5px;">59</li><li style="box-sizing: border-box; padding: 0px 5px;">60</li><li style="box-sizing: border-box; padding: 0px 5px;">61</li><li style="box-sizing: border-box; padding: 0px 5px;">62</li><li style="box-sizing: border-box; padding: 0px 5px;">63</li><li style="box-sizing: border-box; padding: 0px 5px;">64</li><li style="box-sizing: border-box; padding: 0px 5px;">65</li><li style="box-sizing: border-box; padding: 0px 5px;">66</li><li style="box-sizing: border-box; padding: 0px 5px;">67</li><li style="box-sizing: border-box; padding: 0px 5px;">68</li><li style="box-sizing: border-box; padding: 0px 5px;">69</li><li style="box-sizing: border-box; padding: 0px 5px;">70</li></ul>
  • 在该方法中,会将新旧left、right、top、bottom进行对比,只要不完全相同就说明View的布局发生了变化,则将changed变量设置为true。然后比较View的新旧尺寸是否相同,如果尺寸发生了变化,并将其保存到变量sizeChanged中。如果尺寸发生了变化,那么sizeChanged的值为true。

  • 然后将新的left、top、right、bottom存储到View的成员变量中保存下来。并执行mRenderNode.setLeftTopRightBottom()方法会,其会调用RenderNode中原生方法的nSetLeftTopRightBottom()方法,该方法会根据left、top、right、bottom更新用于渲染的显示列表。

  • 如果View的尺寸和之前相比发生了变化,那么就执行sizeChange()方法,该方法中又会调用onSizeChanged()方法,并将View的新旧尺寸传递进去。

  • 如果View处于可见状态,那么会调用invalidate和invalidateParentCaches方法。invalidateParentCaches()方法会移除其父控件的PFLAG_INVALIDATED标签,这样其父控件就会重建用于渲染的显示列表。


sizeChange

sizeChange方法会在View的尺寸发生变化时调用,在setFrame()方法中就可能会调用sizeChange()方法。当然,在View的setLeft()、setTop()、setRight()、setBottom()等其他改变View尺寸的方法中也会调用sizeChange()方法,其源码如下所示:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; background: transparent; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal;">    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">sizeChange</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> newWidth, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> newHeight, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> oldWidth, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> oldHeight) {
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">//将View的新旧尺寸传递给onSizeChanged()方法</span>
        onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mOverlay != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
            mOverlay.getOverlayView().setRight(newWidth);
            mOverlay.getOverlayView().setBottom(newHeight);
        }
        rebuildOutline();
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>

在该方法中其主要将View的新旧尺寸传递给onSizeChanged()方法使其执行。


onSizeChanged

onSizeChanged()方法是个空方法,代码如下所示:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; background: transparent; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal;">    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onSizeChanged</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> w, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> h, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> oldw, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> oldh) {
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

该方法会在View的尺寸发生变化时,通过sizeChange()方法的执行而被调用。当View第一次加入到View树中时,该方法也会被调用,只不过传入的旧尺寸oldWidth和oldHeight都是0。


总结

layout方法总的调用过程主线如下所示:

layout() -> onMeasure() -> setFrame() -> sizeChange() -> onSizeChanged() -> onLayout() ->遍历执行OnLayoutChangeListener.onLayoutChange()

希望本文对大家理解View的layout布局过程有所帮助!

相关阅读: 
《我的Android博文整理汇总》 
《 Android中View的布局及绘图机制》 
《源码解析Android中View的measure量算过程》


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值