View的layout流程

Posted 呼啸

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了View的layout流程相关的知识,希望对你有一定的参考价值。

首先知道,layout方法的作用是确定元素的位置。ViewGroup中的layout方法用来确定子元素的位置。View中的layout方法则用来确定自身的位置。下面我们首先来看看View的layout方法:

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);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) 
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) 
                if(mRoundScrollbarRenderer == null) 
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                
             else 
                mRoundScrollbarRenderer = null;
            

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) 
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) 
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                
            
        

        final boolean wasLayoutValid = isLayoutValid();

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        if (!wasLayoutValid && isFocused()) 
            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
            if (canTakeFocus()) 
                // We have a robust focus, so parents should no longer be wanting focus.
                clearParentsWantFocus();
             else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) 
                // This is a weird case. Most-likely the user, rather than ViewRootImpl, called
                // layout. In this case, there's no guarantee that parent layouts will be evaluated
                // and thus the safest action is to clear focus here.
                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
                clearParentsWantFocus();
             else if (!hasParentWantsFocus()) 
                // original requestFocus was likely on this view directly, so just clear focus
                clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
            
            // otherwise, we let parents handle re-assigning focus during their layout passes.
         else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) 
            mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
            View focused = findFocus();
            if (focused != null) 
                // Try to restore focus as close as possible to our starting focus.
                if (!restoreDefaultFocus() && !hasParentWantsFocus()) 
                    // Give up and clear focus once we've reached the top-most parent which wants
                    // focus.
                    focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
                
            
        

        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) 
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        

        notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
    

可以看到layou方法的4个参数分别为l,t,r,b分别是从左、上、右、下相对于父容器的距离。

接下来我们看看setFrame方法里做了什么。

  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;
    

setFrame方法用传进来的l,t, r,b这4个参数分别初始化mLeft,mTop,mRight,mBottom这4个值。这样就确定了该View在父容器中的位置。在调用setFrame方法后,会调用onLayout方法:

 protected void onLayout(boolean changed, int left, int top, int right, int bottom) 
    

但是我们发现onLayout方法是一个空方法,这和onMeasure方法类似。确定位置时根据不同的控件有不同的实现,所以在View和ViewGroup中均没有实现onLayout方法。所以我们得找一个具体的实现来看下,比如LinearLayout的onLayout方法:

 @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);
        
    

与onMeasure方法类似,根据方向来调用不同的方法。这里仍旧查看垂直方向的layoutVertical方法:

 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);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            
        
    

可以看到,这个方法会遍历子元素并调用setChildFrame方法。其中childTop值是不断累加的。这样子元素才会依次按照垂直方向一个接一个排列下去而不会重叠的。接着看setChildFrame方法。

    private void setChildFrame(View child, int left, int top, int width, int height) 
        child.layout(left, top, left + width, top + height);
    

可以看到在setChildFrame方法调用子元素的layout方法来确定自己的位置。

加油,进步!

以上是关于View的layout流程的主要内容,如果未能解决你的问题,请参考以下文章

View的layout流程

Android视图的绘制流程(下)——View的Layout与Draw过程

Android View体系从源码解析View的layout和draw流程

Android View体系从源代码解析View的layout和draw流程

自定义View分类与流程

View的工作原理