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


    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量算过程》

  • 相关阅读:
    牛客(28)数组中出现次数超过一半的数字
    牛客(27)字符串的排列
    2、Spring Cloud和dubbo简介
    1、微服务简介
    12、Spring Boot监控
    11、Spring Boot热部署
    10、Spring Boot分布式
    9、Spring Boot安全
    8、Spring Boot任务
    7、Spring Boot检索
  • 原文地址:https://www.cnblogs.com/mthoutai/p/7391531.html
Copyright © 2020-2023  润新知