View的测量、布局及绘制过程

一、Activity、Window及DecorView关系

  • Activity:页面
  • Window:交互窗口,是一个抽象类
  • PhoneWindow:Window的唯一实现类
  • DecorView:一个页面的根View,继承自FrameLayout
  • 既然我们知道整个View的Root是DecorView,那么View的绘制是从哪里开始的呢,我们知道每个Activity 均会创建一个 PhoneWindow对象,是Activity和整个View系统交互的接口,每个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系。对于Activity来说,ViewRootImpl是连接WindowManager和DecorView的纽带,绘制的入口是由ViewRootImpl的performTraversals方法来发起Measure,Layout,Draw等流程的。
private void performTraversals() { 
    ...... 
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); 
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 
    ...... 
    // mView就是DecorView
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
    ......
    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    ...... 
    mView.draw(canvas); 
    ......
}

private static int getRootMeasureSpec(int windowSize, int rootDimension) { 
   int measureSpec; 
   switch (rootDimension) { 
   case ViewGroup.LayoutParams.MATCH_PARENT: 
   // Window can't resize. Force root view to be windowSize.   
   measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
   break; 
  } 
 return measureSpec; 
}

二、setContentView后的ViewTree

3333.png

4444.png

上述的两个图已经很清楚地说明了setContentView后的结构。
View decorView = getWindow().getDecorView();
id是content的就是我们设置的布局的根布局,而DecorView是整个页面的根布局。
FrameLayout contentRootLayout = (FrameLayout)findViewById(android.R.id.content);
content下面id是linear的即是我们XML的布局结构。

三、MeasureSpec参数的理解

对于View的测量,肯定会和MeasureSpec接触,MeasureSpec是两个单词组成,翻译过来就是“测量规格”或者“测量参数”,更精确的说法应该这个MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通过简单的计算得出一个针对子View的测量要求,这个测量要求就是MeasureSpec。
大家都知道一个MeasureSpec是一个大小跟模式的组合值,MeasureSpec中的值是一个整型(32位)将size和mode打包成一个Int型,其中高两位是mode,后面30位存的是size,是为了减少对象的分配开支。

MeasureSpec一共有三种模式:

  • EXACTLY: 父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间。
  • AT_MOST:子容器可以是声明大小内的任意大小
  • UPSPECIFIED : 父容器对于子容器没有任何限制,子容器想要多大就多大。这种一般用于系统内部,表示一种测量状态。

如果从代码上来看view.measure(int widthMeasureSpec, int heightMeasureSpec) 的两个MeasureSpec是父类传递过来的,但并不是完全是父View的要求,而是父View的MeasureSpec和子View自己的LayoutParams共同决定的,而子View的LayoutParams其实就是我们在xml写的时候设置的layout_width和layout_height 转化而来的。
5555.png

四、Measure过程

1、单纯一个View的Measure过程

View的测量过程其实是由measure方法来完成,这个方法是一个final类型,就说明是不可以被重写的,因此所有ViewGroup及Layout里是没有该方法的。
measure方法中会调用onMeasure方法,我们来看看View里的onMeasure方法源码

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

/**
* 根据父控件传递下来的spec,来计算出当前这个View的尺寸
* 当模式是EXACTLY和AT_MOST时,View设置的大小即为父控件里传递下来的specSize
* 当模式是UNSPECIFIED时,使用当前View的默认大小(这个默认大小和是否设置背景及minWidth属性有关)
*/
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

可以得出一个结论:直接继承View的自定义控件,假如我们没有重写onMeasure去处理大小,那么当我们在布局里使用这个自定义控件时,使用wrap_content和match_parent是没有区别的,结果都是match_parent。需要结合上面的4X4表格以及View的getDefaultSize源码来进行分析,当我们的自定义View设置wrap_content时,这个View的测量规格应该是AT_MOST + parentSize,而上述源码我们也知道AT_MOST模式下,这个parentSize即为这个自定义控件设置的最终大小。
解决方案就是,自己重写onMeasure方法,在AT_MOST模式下自己去设置对应宽高即可

2、ViewGroup的Measure过程

ViewGroup是一个抽象类,并没有重写View的onMeasure方法,这是因为其子类(LinearLayout、FrameLayout等)都有不同布局特性及表现形式,因此ViewGroup没法做到统一实现,而交由其各个布局自己去实现onMeasure。
我们就以LinearLayout作为例子看源码:

/**
* LinearLayout的onMeasure方法
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

/**
* LinearLayout的measureVertical方法
*/
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);
        final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
        // 去测量和设置子控件的宽高
        measureChildBeforeLayout(child, i, widthMeasureSpec, 0,heightMeasureSpec, usedHeight);
        ...
        // 最后才设置LinearLayout的宽高
        // 通过mTotalLength这个变量来存储LinearLayout在竖直方向累加的高度
        // 最终假如自己是wrap_content,就用这个mTotalLength来设置成自己的高度
        // 最后部分看看resolveSizeAndState源码
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);
        ...
    }
}

/**
* LinearLayout的measureChildBeforeLayout方法
*/
void measureChildBeforeLayout(View child, int childIndex,
        int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
        int totalHeight) {
    measureChildWithMargins(child, widthMeasureSpec, totalWidth,
            heightMeasureSpec, totalHeight);
}

/**
* ViewGroup的measureChildWithMargins方法(不是LinearLayout的)
*/
protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);
    // 最终这里就是调用View的measure方法,View的measure方法上面有介绍
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

/**
* LinearLayout的resolveSizeAndState方法
* 假如该LinearLayout是AT_MOST模式,那么就使用当前内容的累加高度和其父控件给
* 他的最大高度进行比较,取其最小的值。
*/
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    switch (specMode) {
        case MeasureSpec.AT_MOST:
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}
3、测量后获取控件的宽高

在Activity中要去获取控件的宽高,很多时候我们调用getMeasureHeight/Width获取到是0,这是因为我们在onCreate、onStart、onResume中获取时,ViewTree并一定已测量完毕,因为View和Activity的生命周期兵没有任何关系,不能保证。有四种获取方法供参考:

3.1 Activity/View#onWindowFocusChanged,这个方法的含义是View已经初始化完毕了,宽高已经准备好了,这个时候去获取宽高是没有问题的。但是要注意的是onWindowFocusChanged会被调用多次,当Activity的窗口获取的焦点和失去焦点的时候均会调用。
3.2 view.post(runnable),把一个消息放到消息队列的尾部,然后等待主线程的Looper去执行。
3.3 ViewTreeObserver,view树的状态发生改变时,会进行回调,注意其里的方法(比如onGlobalLayoutListener会被多次回调)
mView.getViewTreeObserver().addOnGlobalLayoutListener(new XXXListener) {
    mView.getViewTreeObserver().removeGlobalLayoutListener();
    int width = mView.getMeasuredWidth();
}
3.4 通过调用View的measure方法进行测量,比如
/*
* 具体数值
*/
int widthSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY);
mActiveAreaLayout.measure(widthSpec, heightSpec);

/*
* wrap_content
* 1<<30 - 1 = 2的30次方-1 = Spec能存放的最大值 
*/
int widthSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
int heightSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
mActiveAreaLayout.measure(widthSpec, heightSpec);

五、Layout过程

1、单纯一个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方法,而View自己是没有实现onLayout方法的
         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);
            }
        }
    }
2、ViewGroup的Layout过程

和measure一样,ViewGroup也是没有实现onLayout方法,而是交由其子类(LinearLayout/FrameLayout等)来实现,我们仍然以LinearLayout作为例子:

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

/**
* LinearLayout的layoutVertical方法
*/
void layoutVertical(int left, int top, int right, int bottom) {
    ...
    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去设置子控件的位置
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child, i);
        }
    }
}

/**
* LinearLayout的setChildFrame方法
* 最终调用的就是View的layout方法,上面已经介绍过了
*/
private void setChildFrame(View child, int left, int top, int width, int height) {
    child.layout(left, top, left + width, top + height);
}

六、Draw过程

draw绘制就比较简单了,直接看源码

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      1. Draw the background
     *      2. If necessary, save the canvas' layers to prepare for fading
     *      3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */

    // Step 1, draw the background, if needed
    int saveCount;

    if (!dirtyOpaque) {
        // 第一步,绘制背景
        drawBackground(canvas);
    }

    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // 第二步,绘制自己
        if (!dirtyOpaque) onDraw(canvas);

        // 第三步,绘制子控件
        dispatchDraw(canvas);

        drawAutofilledHighlight(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }

        // we're done...
        return;
    }

    // Step 4, draw the children
    dispatchDraw(canvas);
}


@Override
 protected void dispatchDraw(Canvas canvas) {
       ...
        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        } else {
            for (int i = 0; i < count; i++) {
                final View child = children[getChildDrawingOrder(count, i)];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        }
      ......
}

7777.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值