View绘制流程

1.View的测量流程:

1.1 MeasureSpec:

大小:32位,高两位是mode,低30位是size;
意义:父View对子View期望的宽高要求,是父View传递给子View的

mode有三种:
UNSPECIFIED:父容器不对子View进行限制,要多大给多大,不用
EXACTLY:父容器已经检测到子View需要的精确大小,对应LayoutParams中的match_parentxxxdp两种情况
AT_MOST:父容器提供一个数值作为子View大小的上界,对应LayoutParams中的wrap_content这种情况

DecorView的MeasureSpec由窗口尺寸和其自身的LayoutParams确定
普通View的MeasureSpec由父容器的MeasureSpec和子View自身的LayoutParams确定;

1.2 DecorView:

获取DecorView的MeasureSpec的过程:

//className = ViewRootImpl
childWidthMeasureSpec = getRootMeasureSpec(windowWidth, lp,width);
childHeightMeasureSpec = getRootMeasureSpec(windowHeight, lp,height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//windowSizes:屏幕尺寸
//rootDimension:MATCH_PARENT,WRAP_CONTENT,或具体数值
//这个方法就是通过屏幕尺寸和DecorView的LayoutParams计算出DecorView的MeasureSpec
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
	int measureSpec;
	switch (rootDimension) {
		case ViewGroup.LayoutParams.MATCH_PARENT:
			measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
			break;
		case ViewGroup.LayoutParams.WRAP_CONTENT:
			measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
			break;
		default:
			measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
			break;
	}
	return measureSpec;
}
1.3 普通ViewGroup:这里以FrameLayout为例

绘制过程由ViewRootImpl开始:

public class ViewRootImpl implements ViewParent... {
	 View mView;														//真正的root视图
	 private void performTraversals() {
	 	...
	 	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);	//间接调用了mView.measure方法
	 	...
	 	performLayout(lp, mWidth, mHeight);								//间接调用了mView.layout方法
	 	...
	 	performDraw();													//间接调用了mView.draw方法
	 }
}

FrameLayout的measure方法:

@Override
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ......
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
	...
    for (int i = 0; i < count; i++) {    
    	//遍历子view
        final View child = getChildAt(i);    
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            //考虑带margin去测量子View
            //两个0是父容器中已经用掉的大小,在FrameLayout中这里一直是0
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
			...
			//计算子View最大的宽高
            maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
         	...
        }
    }
	...
    //设置测量过的宽高
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
}

measureChildWithMargins:

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
    //得到子View的LayoutParams
    final MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
    //计算子View的宽的MeasureSpec
    final int childWidthMeasureSpec =
              getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight +
                                  lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
    //计算子View的高的MeasureSpec
    final int childHeightMeasureSpec =
              getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom +
                                  lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
    //测量子View
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

childWidthMeasureSpec :

// spec = 父容器的MeasureSpec
// padding = 父容器的padding
// childDimension = 子View中LayoutParams的长宽
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = View.MeasureSpec.getMode(spec);//父容器的模式
    int specSize = View.MeasureSpec.getSize(spec);//父容器的宽或者高的大小
    //子View的最大宽/高
    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;
    //根据父容器的测量模式
    switch (specMode) {
        case View.MeasureSpec.EXACTLY:
            //LayoutParams.MATCH_PARENT=-1,LayoutParams.WRAP_CONTENT=-2 
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = View.MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = View.MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = View.MeasureSpec.AT_MOST;
            }
            break;

        case View.MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = View.MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = View.MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = View.MeasureSpec.AT_MOST;
            }
            break;

        case View.MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = View.MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = View.MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = View.MeasureSpec.UNSPECIFIED;
            }
            break;
    }
    return View.MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

这个函数的实际效果是做了这样的映射:

xxdpmatch_parentwrap_content
EXACTLYxxdp / EXACTLYparentSize / EXACTLYparentSize / AT_MOST
AT_MOSTxxdp / EXACTLYparentSize / AT_MOSTparentSize / AT_MOST
UNSPECIFIEDxxdp / EXACTLY0 / UNSPECIFIED0 / UNSPECIFIED

总结下就是:
1)遍历所有子View;
2)对于每个子View,获取其LayoutParams,结合三个参数:parentMeasureSpec,childPadding,childSize,计算得到每个子View的MeasureSpec;
3)计算childSpec流程,childSpecMode如何确定:
由parentSpecMode(三种)和childViewLayoutParams中的size(match_parent,content_wrap,xxdp)三种,共9种情况直接映射得到特定的specMode;
4)调用每个子View的measure(widthSpec,heightSepc)方法测量每个子View的大小;
5)遍历完成后每个子View都已经测量完成,它们的大小都已确定,最后依据所有子View占用的空间设置自己的大小setMeasuredDimension(int width,int height),完成ViewGroup的测量;
6)MeasureSpec如何决定最终的大小:如果是EXACTLY模式,那么ViewGroup的大小就直接由MeasureSpec确定好了,就是MeasureSpec中的Size;如果是AT_MOST模式,那么ViewGroup的最终大小不确定,需要先测量完子View后才能确定。

1.4 View

1)View的measure方法会调用onMeasure:

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

2)getSuggestedMinSize():返回最小大小和背景最小大小的较大者,作为建议的最小Size(这个值多数情况下会被丢弃)

public static int getDefaultSize(int size, int measureSpec) {
	int specMode = MeasureSpec.getMode(measureSpec);
	int specSize = MeasureSpec.getSize(measureSpec);
	switch (specMode){
		case MeasureSpec.UNSPECIFIED:
			result = size;
			break;
		//这里View对match_parent和wrap_content做了相同的处理,如果要继承View需要自己实现wrap_content的情况
		case MeasureSpec.AT_MOST:
		case MeasureSpec.EXACTLY:
			result = specSize;
			break;
	}
	return result;
}

3)将步骤2计算得到的size设置为View的Dimension,结束;

1.5 总结:

1)越外层的ViewGroup越早得到MeasureSpec,但越晚完成测量;
2)getMeasuredDimension在测量完成后可以获取到
3) 继承View实现控件时wrap_content会导致该View的MeasureSpec是AT_MOST模式,且AT_MOST模式下效果与match_parent相同,所以继承View实现控件时要自己处理wrap_content的情况,如TextView等都对wrap_content有相应处理

2.Layout过程:

1)View的Layout过程是从ViewRootImpl开始的:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
    ...      
    final View host = mView;
    //调用DecorView的layout方法
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

 }

2)layout方法通过setFrame方法确定View自身的位置和大小,并调用onLayout方法;

public void layout(int l,int t,int r,int b){
	...
	setFrame(l,t,r,b);
	...
	//change = 是否需要重新布局
	onLayout(changed,l,t,r,b);
}

3)onLayout方法遍历子View并调用它们的layout方法以确定各个子View的位置和大小;

//onLayout方法在View中只有空实现,只有ViewGroup需要实现onLayout方法
public void onLayout(boolean changed,int l,int t,int r,int b){
	int count = getVirtualChildCount();
	for(int i = 0;i < count;i++){
		View child = getVirtualChildAt(i);
		...
		//setChildFrame方法实际上调用了child的layout方法
		setChildFrame(child, childLeft, childTop, childWidth, childHeight);
	}
}

4)getWidth()在layout时调用setFrame后可以获取得到

3.Draw过程:

1)View的draw过程是从ViewRootImpl开始的:

private void performDraw() {
	...
    mView.draw(canvas);
}

2)View.draw

public void draw(Canvas canvas) {
	//绘制背景
	drawBackground(canvas);
	//调用onDraw
	if (!dirtyOpaque) onDraw(canvas)
	//绘制子控件
	dispatchDraw(canvas);
	//绘制前景
	onDrawForeground(canvas);
	return;
}

2)分发draw事件

protected void dispatchDraw(Canvas canvas) {
    for (int i = 0; i < childrenCount; i++) {
		child.draw(canvas, this, drawingTime)
    }
}
4.其他:

View的重绘:
invalidate():调用onDraw()方法,用于主线程;
postInvalidate():通过handler调用了invalidate方法,用于子线程;
requestLayout():会调用measure和layout方法;

5.如何正确地获取控件宽高:

1.Activity或View # onWindowFocusChanged()
这个方法调用的时机是View已经绘制完成,窗口获取/失去焦点都会调用,这个时候去获取View的宽高是没有问题的,但是由于这个方法会被多次调用,所以不建议在这里添加复杂逻辑

2.View.post(Runnable)
Runnable中的逻辑会在View绘制完成后执行

3.ViewTreeObserver

ViewTreeObserver observer=view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
     @Override
     public void onGlobalLayout() {
           if(observer.isAlive()){
             observer.removeGlobalOnLayoutListener(this);
             //获得宽高
             int viewWidth=view.getMeasuredWidth();
             int viewHeight=view.getMeasuredHeight();
           }
        }
   });

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值