黑马程序员技术交流社区

标题: 分析理解View的源码——Layout [打印本页]

作者: 大蓝鲸小蟀锅    时间: 2018-9-26 21:47
标题: 分析理解View的源码——Layout
Measure的过程分析完了,来分析一下layout。

measure方法执行完之后,系统确定了view的测量大小,接下来会执行到view的layout代码。

该过程会确定视图的显示位置,即子view在父控件中的位置。

既然涉及到摆放的位置,如果是自定义view,一般是用不到重写Layout,因为只有一个控件,还要怎么去摆放呢?

如果自定义viewGroup,这个时候需要重写,但是也不是重写layout方法,而是重写onLayout方法。为什么呢?看代码

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);
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}
protected abstract void onLayout(boolean changed,
        int l, int t, int r, int b);
ViewGroup重写的layout是final修饰的,不能重写,onLayout是abstract修饰的,必须要重写,让子类自己实现自己需要的布局效果,重写onLayout在哪一步被调用呢?我们来看看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);    //判断布局view有没有变化

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);                    //有变化就会调用onLayout

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

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
在判断change时,此处调用setFrame()方法,该方法会把之前的left,top,right,bottom和传入的判断,如果有改变,说明view的位置发生了改变,返回true,接下来会调用onLayout方法,重新布局。
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) {      
        changed = true;                                  //如果有变化,返回的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;                                    //在这一步给mLeft等赋值的
        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;
        if (mForegroundInfo != null) {
            mForegroundInfo.mBoundsChanged = true;
        }

        notifySubtreeAccessibilityStateChangedIfNeeded();
    }
    return changed;
}
上诉代码可以看到,什么时候会调用onLayout(),也发现getMeasureWidth(),和getWidth()的区别;
public final int getMeasuredWidth() {
    return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getWidth() {
    return mRight - mLeft;
}
熟悉了执行的流程,从两个方法中就可以看出来区别:
mMeasuredWidth方法在onMeasure时就会给每一个view获取到MeasureSpec的属性。所以在Measure流程执行过后,就可以得到测量的宽高。
getWidth方法需要属性mRight - mLeft,才能得到具体的值,但是这两个值在setFrame方法执行的时候才回去赋值,所以在layout执行完之后才能得到值。layout之后完后得到的值才是View的实际大小。

下面看下LinearLayout的代码,看看它是如何实现onLayout方法的。
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;      //计算child可使用的空间大小。用宽度 - padding值
   
    final int count = getVirtualChildCount();            //垂直方法子view的个数
      
    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;      //判断LinearLayout的Gravity属性是什么,只是top从顶部,中间,还是底部开始
    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++) {                  //遍历每一个子view
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {          
            final int childWidth = child.getMeasuredWidth();            //测量子view的宽高,确定子view的宽和高
            final int childHeight = child.getMeasuredHeight();
            
            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();
            
            int gravity = lp.gravity;                        //确定子view的位置。
            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),        //得到子view的位置,让子view去摆放自己
                    childWidth, childHeight);
            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);                      //setChildFrame实际上就是调用child的layout方法
}
看到这里我们就清楚了,ViewGroup首先调用了layout方法确定自己本身在父View的位置(ViewRoot),然后调用onLayout确定每个子view的位置,每个子View会调用自己的layout,确定自己在ViewGroup的位置。
简单的说,就是View的layout方法用于view确定自己本身在父View的位置,ViewGroup的onLayout方法用来确定子View的位置。





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2