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

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

layout

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

    public void layout(int l, int t, int r, int b) {        //成员变量mPrivateFlags3中的一些比特位存储着和layout相关的信息        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {            //如果在mPrivateFlags3的低位字节的第4位(从最右向左数第4位)的值为1,            //那么就表示在layout布局前需要先对View进行量算,            //这种情况下就会执行View的onMeasure方法对View进行量算            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);            //量算完成后就会将mPrivateFlags3低位字节的第4位重置为0,            //移除掉标签PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;        }        int oldL = mLeft;        int oldT = mTop;        int oldB = mBottom;        int oldR = mRight;        //如果isLayoutModeOptical()返回true,那么就会执行setOpticalFrame()方法,        //否则会执行setFrame()方法。并且setOpticalFrame()内部会调用setFrame(),        //所以无论如何都会执行setFrame()方法。        //setFrame()方法会将View新的left、top、right、bottom存储到View的成员变量中        //并且返回一个boolean值,如果返回true表示View的位置或尺寸发生了变化,        //否则表示未发生变化        boolean changed = isLayoutModeOptical(mParent) ?                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {            //如果View的布局发生了变化,或者mPrivateFlags有需要LAYOUT的标签PFLAG_LAYOUT_REQUIRED,            //那么就会执行以下代码            //首先会触发onLayout方法的执行,View中默认的onLayout方法是个空方法            //不过继承自ViewGroup的类都需要实现onLayout方法,从而在onLayout方法中依次循环子View,            //并调用子View的layout方法            onLayout(changed, l, t, r, b);            //在执行完onLayout方法之后,从mPrivateFlags中移除标签PFLAG_LAYOUT_REQUIRED            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;            //我们可以通过View的addOnLayoutChangeListener(View.OnLayoutChangeListener listener)方法            //向View中添加多个Layout发生变化的事件监听器            //这些事件监听器都存储在mListenerInfo.mOnLayoutChangeListeners这个ArrayList中            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnLayoutChangeListeners != null) {                //首先对mOnLayoutChangeListeners中的事件监听器进行拷贝                ArrayList<OnLayoutChangeListener> listenersCopy =                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();                int numListeners = listenersCopy.size();                for (int i = 0; i < numListeners; ++i) {                    //遍历注册的事件监听器,依次调用其onLayoutChange方法,这样Layout事件监听器就得到了响应                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);                }            }        }        //从mPrivateFlags中移除强制Layout的标签PFLAG_FORCE_LAYOUT        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;        //向mPrivateFlags3中加入Layout完成的标签PFLAG3_IS_LAID_OUT        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;    }
  • 在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()方法。其源码如下所示:

    protected boolean setFrame(int left, int top, int right, int bottom) {        boolean changed = false;        if (DBG) {            Log.d("View", this + " View.setFrame(" + left + "," + top + ","                    + right + "," + bottom + ")");        }        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {            //将新旧left、right、top、bottom进行对比,只要不完全相对就说明View的布局发生了变化,            //则将changed变量设置为true            changed = true;            //先保存一下mPrivateFlags中的PFLAG_DRAWN标签信息            int drawn = mPrivateFlags & PFLAG_DRAWN;            //分别计算View的新旧尺寸            int oldWidth = mRight - mLeft;            int oldHeight = mBottom - mTop;            int newWidth = right - left;            int newHeight = bottom - top;            //比较View的新旧尺寸是否相同,如果尺寸发生了变化,那么sizeChanged的值为true            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);            // Invalidate our old position            invalidate(sizeChanged);            //将新的left、top、right、bottom存储到View的成员变量中            mLeft = left;            mTop = top;            mRight = right;            mBottom = bottom;            //mRenderNode.setLeftTopRightBottom()方法会调用RenderNode中原生方法的nSetLeftTopRightBottom()方法,            //该方法会根据left、top、right、bottom更新用于渲染的显示列表            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);            //向mPrivateFlags中增加标签PFLAG_HAS_BOUNDS,表示当前View具有了明确的边界范围            mPrivateFlags |= PFLAG_HAS_BOUNDS;            if (sizeChanged) {                //如果View的尺寸和之前相比发生了变化,那么就执行sizeChange()方法,                //该方法中又会调用onSizeChanged()方法,并将View的新旧尺寸传递进去                sizeChange(newWidth, newHeight, oldWidth, oldHeight);            }            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {                //有可能在调用setFrame方法之前,invalidate方法就被调用了,                //这会导致mPrivateFlags移除了PFLAG_DRAWN标签。                //如果当前View处于可见状态就将mPrivateFlags强制添加PFLAG_DRAWN状态位,                //这样会确保下面的invalidate()方法会执行到其父控件级别。                mPrivateFlags |= PFLAG_DRAWN;                invalidate(sizeChanged);                //invalidateParentCaches()方法会移除其父控件的PFLAG_INVALIDATED标签,                //这样其父控件就会重建用于渲染的显示列表                invalidateParentCaches();            }            // 重新恢复mPrivateFlags中原有的PFLAG_DRAWN标签信息            mPrivateFlags |= drawn;            mBackgroundSizeChanged = true;            if (mForegroundInfo != null) {                mForegroundInfo.mBoundsChanged = true;            }            notifySubtreeAccessibilityStateChangedIfNeeded();        }        return changed;    }
  • 在该方法中,会将新旧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()方法,其源码如下所示:

    private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {        //将View的新旧尺寸传递给onSizeChanged()方法        onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);        if (mOverlay != null) {            mOverlay.getOverlayView().setRight(newWidth);            mOverlay.getOverlayView().setBottom(newHeight);        }        rebuildOutline();    }

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

onSizeChanged

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

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {    }

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

总结

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

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

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

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

更多相关文章

  1. Android(安卓)JNI入门
  2. Android(安卓)Binder概述
  3. Android(安卓)系统中 gps Location Service 的实现与架构,本文可
  4. 同一功能在Android不同版本进行兼容的方法
  5. 基于Qt for Android联想到调用Android(安卓)API
  6. Android是否可以实现静默安装模式
  7. 源码解析Android中View的layout布局过程
  8. 浅谈Java中Collections.sort对List排序的两种方法
  9. Python list sort方法的具体使用

随机推荐

  1. 在模拟器上运行编译好的android
  2. Android相对布局实现各种梅花效果
  3. Android(安卓)基本控件Text属性
  4. Android(安卓)并发之Handler、Looper、Me
  5. View 控件EditText属性
  6. Android内存管理基本介绍
  7. 《Android开发从零开始》——13.Table La
  8. 常用知识篇 一 Selector state状态对应说
  9. Android中定义样式(1)
  10. [Android] TextView只显示一行,多余显示