View的layout流程

作者: Chase_stars | 来源:发表于2019-07-29 21:13 被阅读3次

    理想是人生的太阳。 — 德莱塞

    写在前面

    《View的绘制流程》一篇中介绍了View如何工作,最终会调用ViewRootImpl的performTraversals()遍历View树,分别执行measure,layout和draw流程。

    本篇文章来看下layout流程,layout的作用是确定元素的位置,ViewGroup中layout方法用来确定子元素的位置,View中的layout方法用来确定自身的位置。

    ViewGroup的layout流程

    public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    
        @Override
        public final void layout(int l, int t, int r, int b) {
            if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
                if (mTransition != null) {
                    mTransition.layoutChange(this);
                }
                super.layout(l, t, r, b); // 1
            } else {
                // record the fact that we noop'd it; request layout when transition finishes
                mLayoutCalledWhileSuppressed = true;
            }
        }
    }
    

    ViewGroup的layout方法中并没有做太多事情,而是调用super.layout(l, t, r, b),我们知道ViewGroup继承自View,所以super.layout(l, t, r, b)调用的就是其父类View的layout(l, t, r, b)函数,那么我们就直接看View的layout流程。

    View的layout流程

    public class View implements Drawable.Callback, KeyEvent.Callback,
            AccessibilityEventSource {
    
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        }
    
        public void layout(int l, int t, int r, int b) {
            if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
                onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
    
            int oldL = mLeft;
            int oldT = mTop;
            int oldB = mBottom;
            int oldR = mRight;
    
            boolean changed = isLayoutModeOptical(mParent) ?
                    setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); // 1
    
            if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
                onLayout(changed, l, t, r, b); // 2
    
                 ......
              
           } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
    
                ......
    
            }
    
        }
    
       protected boolean setFrame(int left, int top, int right, int bottom) {
            boolean changed = false;
    
            if (DBG) {
                Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                        + right + "," + bottom + ")");
            }
    
            if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
                changed = true;
    
                // Remember our drawn bit
                int drawn = mPrivateFlags & PFLAG_DRAWN;
    
                int oldWidth = mRight - mLeft;
                int oldHeight = mBottom - mTop;
                int newWidth = right - left;
                int newHeight = bottom - top;
                boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
    
                // Invalidate our old position
                invalidate(sizeChanged);
    
                mLeft = left;
                mTop = top;
                mRight = right;
                mBottom = bottom;
                mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
    
                mPrivateFlags |= PFLAG_HAS_BOUNDS;
    
    
                if (sizeChanged) {
                    sizeChange(newWidth, newHeight, oldWidth, oldHeight);
                }
    
                if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
                    // If we are visible, force the DRAWN bit to on so that
                    // this invalidate will go through (at least to our parent).
                    // This is because someone may have invalidated this view
                    // before this call to setFrame came in, thereby clearing
                    // the DRAWN bit.
                    mPrivateFlags |= PFLAG_DRAWN;
                    invalidate(sizeChanged);
                    // parent display list may need to be recreated based on a change in the bounds
                    // of any child
                    invalidateParentCaches();
                }
    
                // Reset drawn bit to original value (invalidate turns it off)
                mPrivateFlags |= drawn;
    
                mBackgroundSizeChanged = true;
                mDefaultFocusHighlightSizeChanged = true;
                if (mForegroundInfo != null) {
                    mForegroundInfo.mBoundsChanged = true;
                }
    
                notifySubtreeAccessibilityStateChangedIfNeeded();
            }
            return changed;
        }
    }
    

    首先我们来看layout()函数中注释1处调用的setFrame()函数,setFrame()内部会先计算出之前的宽和高与现在的宽和高进行比较,判断宽和高是否有改变,无论宽还是高,只要有一个改变,sizeChanged就会置为true,说明View的位置已经有变化了。接下来会给mLeft,mTop,mRight,mBottom进行赋值,并将结果changed返回。setOpticalFrame(l, t, r, b)内部最终调用的也是setFrame(l, t, r, b)函数。现在回到layout()方法,如果setFrame()函数的返回值changed为true,则会调用onLayout()方法,onLayout()方法是一个空方法,和onMeasure()方法类似, 确定位置时需要根据不同的控件有不同的实现,所以View和ViewGroup均没有实现该方法。参数l, t, r, b分别表示View从左,上,右,下距离父容器的距离。

    LinearLayout的layout流程

    public class LinearLayout extends ViewGroup {
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            if (mOrientation == VERTICAL) {
                layoutVertical(l, t, r, b);
            } else {
                layoutHorizontal(l, t, r, b);
            }
        }
    
        void layoutVertical(int left, int top, int right, int bottom) {
            final int paddingLeft = mPaddingLeft;
    
            int childTop;
            int childLeft;
    
            // Where right end of child should go
            final int width = right - left;
            int childRight = width - mPaddingRight;
    
            // Space available for child
            int childSpace = width - paddingLeft - mPaddingRight;
    
            final int count = getVirtualChildCount();
    
            final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
            final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
    
            switch (majorGravity) {
               case Gravity.BOTTOM:
                   // mTotalLength contains the padding already
                   childTop = mPaddingTop + bottom - top - mTotalLength;
                   break;
    
                   // mTotalLength contains the padding already
               case Gravity.CENTER_VERTICAL:
                   childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
                   break;
    
               case Gravity.TOP:
               default:
                   childTop = mPaddingTop;
                   break;
            }
    
            for (int i = 0; i < count; i++) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    childTop += measureNullChild(i);
                } else if (child.getVisibility() != GONE) {
                    final int childWidth = child.getMeasuredWidth();
                    final int childHeight = child.getMeasuredHeight();
    
                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();
    
                    int gravity = lp.gravity;
                    if (gravity < 0) {
                        gravity = minorGravity;
                    }
                    final int layoutDirection = getLayoutDirection();
                    final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                        case Gravity.CENTER_HORIZONTAL:
                            childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                    + lp.leftMargin - lp.rightMargin;
                            break;
    
                        case Gravity.RIGHT:
                            childLeft = childRight - childWidth - lp.rightMargin;
                            break;
    
                        case Gravity.LEFT:
                        default:
                            childLeft = paddingLeft + lp.leftMargin;
                            break;
                    }
    
                    if (hasDividerBeforeChildAt(i)) {
                        childTop += mDividerHeight;
                    }
    
                    childTop += lp.topMargin;
                    setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                            childWidth, childHeight); // 1
                    childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
    
                    i += getChildrenSkipCount(child, i);
                }
            }
        }
    
        private void setChildFrame(View child, int left, int top, int width, int height) {
            child.layout(left, top, left + width, top + height);
        }
    }
    

    之前说过onLayout()是一个空方法,和onMeasure()类似,确定位置需要根据不同的控件右不同的实现,所以View和ViewGroup均没有实现该方法。

    LinearLayout继承自ViewGroup,那么就来看下LinearLayout的onLayout()方法是如何实现的?

    首先看onLayout()方法,会根据布局方向调用不同的函数,这里以垂直方向为例,会调用layoutVertical(l, t, r, b),在layoutVertical(l, t, r, b)函数中注释1处可以看到调用setChildFrame()函数,setChildFrame()函数内部调用了子元素的layout()方法,而子元素还可以再重写onLayout()方法确定位置,依此类推。因为布局方向是垂直的,所以childTop会一直增加,直到遍历完子元素,childTop就是当前的子元素到父控件顶边的距离。由此就可以推导出如果布局方向是水平的,childLeft会一直增加,直到遍历完子元素,childLeft就是当前的子元素到父控件左边的距离。

    总结

    View的layout()流程就这么多,是不是很简单呢?感兴趣的童鞋也可以深入了解下TextView和FrameLayout等layout()流程。

    PS:本人才疏学浅,若有不足请赐教!!!

    相关文章

      网友评论

        本文标题:View的layout流程

        本文链接:https://www.haomeiwen.com/subject/nybirctx.html