View 绘制流程

对于view的绘制流程以前只是知道需要经过measure、layout以及draw这个过程,至于细节还是一脸懵逼直到看了Kelin的这篇文章才感觉有点眉目了,终于是知道了view绘制的整个流程,在此附上原文链接http://www.jianshu.com/p/5a71014e7b1b在此做些自己的整理

MeasureSpec

首先从测量开始,毕竟不经过测量不知道view具体的规格,就不方便layout和绘制了,而说到测量就免不了要提到view的MeasureSpec一个封装了view测量的一些规则,只有知道了依据什么样的规则去测量,才能更好对view进行测量工作。MeasureSpec是一个32为的int值,其中前两位表示的是view测量需要遵循的规则(mode)即测量模式,后30位是测量值的要求,MeasureSpec可以理解为父view给子view的一些绘制的建议,至于子view具体会如何执行不得而知(有点像报考志愿时父母会给点意见,但最后报考那儿还是自己来决定)

MeasureSpec有三种模式:

测量可以看做子view在跟父view申请空间

EXACTLY:

即确定模式,父view明确知道自己有多少空间,子view测量申请的空间大小不能超出父view所具有的空间

AT_MOST:

即最大模式,父view只知道自己最大能够拥有多少空间,子view能够通过测量申请到不超过父view最大所能拥有的空间

UPSPECIFIED :

即不确定模式,父view拥有足够大的空间供子view申请,子view可以根据自己的意愿测量申请任意空间

总结:无论如何 父view拥有的空间>=子view所能申请的空间
,当子view所申请的空间大于父view所拥有的空间时,多余的部分会被截取掉子view显示会出现异常显示不完全

知道了这三种模式的具体情况再结合MATCH_PARENT和WRAP_CONTENT就能更好的了解view的测量是如何进行的,代码中measure(widthMeasureSpec,heightMeasureSpec);这个传进来的测量要求是mode+size而mode(父view的要求)size是父view传递过来的限制边界值,这个值由父view的值和子view的LayoutParams共同决定(举个例子:你能跟父亲申请的经费需要由 父亲拥有的money和你准备申请的money来决定)

父view的测量是在子view的测量工作结束之后进行,当所有子view的测量工作结束之后,父view就可以测量自己用多大的空间来存放子view了
/**
* 告诉让这个view的一个子view自行测量需要的MeasureSpec、padding和
* margin。子view必须获取的MarginLayoutParams 在
* getChildMeasureSpec()方法中完成
* @param child 需要测量的view
* @param parentWidthMeasureSpec 这个控件的宽度需求
* @param widthUsed 要测量的view父控件已经使用掉的水平空间
* (可能是该控件父控件的其他子view使用掉的)
* @param parentHeightMeasureSpec 这个控件的高度需求
* @要测量的view父控件已经使用掉的垂向空间
* (可能是该控件父控件的其他子view使用掉的)
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {

//获取子控件的布局参数
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

//获取子控件的MeasureSpec
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

//测量该子控件
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}


/**
 * 进行子views测量工作: 确定父view中同层视图树的子view的MeasureSpec      
 * 该方法中确定一个子view某个维度上正确的MeasureSpec(高或者宽)
 *
 * 目的是组合 MeasureSpec 和子view的LayoutParams获取最可能的值。 
 * 例如:这个view知道自己的大小 (因为它的MeasureSpec的mode是
 *EXACTLY), 并且在LayoutParams中指出要具有和父view具有相同的大小, 
 *父view会让子view设置个确定的值。
 * @param spec 该子view需要的
 * @param padding 当前维度上设置的padding和margin值
 * @param childDimension 子view在当前维度申请的空间大小
 * @return a MeasureSpec 为子view返回的integer值,即MeasureSpec 
 */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {

//首先获取父view的specMode 和specSize
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);

//剔除父view设置的padding和自己设置的margin影响
int size = Math.max(0, specSize - padding);

//定义子view自身身的specMode 和specSize
int resultSize = 0;
int resultMode = 0;

    switch (specMode) {
    // 父view传进来的specMode是EXACTLY
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // 子view想要申请父view的所有空间,即大小确定了
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // 子view要自己计算大小,但是最大不能超过父view的大小
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // 父view传递了最大值进来
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            //子view有个确定的大小,那么模式肯定是确定的大小也是自己的大小
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // 子view想要向父view一样大,但是父控件的大小还没有确定,唯一能够确定的是子view肯定是不比父控件大
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // child想要自己测量,测量的结果是不能超出父控件的大小的
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    //父控件对测量没有添加任何限制
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            //子控件确定了自己的大小,那么模式即为EXACTLY
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            //子控件想向父控件一样大,父控件可以无限大,那么子控件同样可以
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
           // 子控件需要自行测量,但是不能超过父控件,父控件无限,所以子控件同理
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    //最后可以确定MeasureSpec并返回
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
从子view的角度来讲

当具有固定的大小或者说是大小确定时,那么模式就是EXACTLY,因为本身大小都是具体值了,>不必要去参考父view传递的模式(相当于你自己知道该怎么报志愿,目标明确,不会受别人左
右,所以不管父母给任何建议,你都会按自己意愿进行)。

子view:LayoutParam是MATCH_PARENT

父view模式EXACTLY,那么父view的大小是固定值,子view拿到的是一个固定值
父view模式AT_MOST,那么父view的大小不能确定,子view的大小不能确定,但是不会超出父view最大限制值,所以为模式应为AT_MOST
父view的模式UNSPECIFIED,那么父view的大小不确定,子view的大小不能确定,可以和父view一样获取没有限制的值,所以模式应为UNSPECIFIED

子view:LayoutParam是WRAP_CONTENT

父view模式EXACTLY,子view包裹内容可以取任意值,但是父view大小固定子view不能超出父view的大小,所以模式为AT_MOST
父view模式AT_MOST,子view包裹内容可以取任意值,父view也可以取任意值,但是父view有最大值限制,子view不会超过父view的大小,同样受到这样的限制,所以模式为AT_MOST
父view的模式UNSPECIFIED,子view包裹内容可以取任意值,父view也可以取任意值并且没有任何限制,子view可以和父view一样在这广阔的空间中取值,所以模式应为UNSPECIFIED

明白了这一切我们就可以真正的进行view的绘制流程了view的绘制是在measure、layout和draw中进行的,但是在View.java中我们可以看到
//final 不能进行重写
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
//可以以重写但是没有必要
public void layout(int l, int t, int r, int b)

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime)
前边就没主已忘记了是要干啥,在Activity 的oncreate中获取一个控件的大小,结果获取到的值是0,现在才知道,那时候视图树还没有构建没有测量,肯定获取不到,学习了源码现在终于知道了

onMeasure()

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
……
onMeasure(widthMeasureSpec, heightMeasureSpec);
……
}

/**
 * @param widthMeasureSpec 父控件传递过来的水平方向上的mode
 * @param heightMeasureSpec 父控件传递过来的垂直方向上的mode
 */
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//测量一旦走到这一步时就说明测量结束了,setMeasuredDimension相当于告诉你好了现在的测量结果就是这个值了
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

记得view中有getMeasureXXX方法和getXXX方法,以前不知道,只知道这两个得到的值是相等的,前边在自定义view时遇到了坑,原来getMeasureXXX方法在测量结束后就可以拿到值了,但是getXXX方法是需要等到layout结束之后才会有值,所以当时自定义的view使用了getXXX来布局什么也看不到,而且这两个方法返回值也不一定是相等的因为计算的方式是不同的getXXX是通过控件的左右或者上下边相减得到的如width=right-left如果布局的时候没有用测量到的大小,获取的结果就会不同,当然大部分时候都没人会这么无聊,获取到的值是相等的
/* * 该方法返回一个默认值 ,如果没有约束就使用传递过来的默认值.
*否则使用计算出来的值
* @param size 该view的默认大小
* @param measureSpec 父view传递过来的约束条件
* @return 该view测量到的大小
*/
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可能会使用的最小值,即view的最小高度和背景之间的较小值。
* @return 建议的最小值
*/
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
一些系统控件如TextView、Button、ImageView等都重写了onMeasure方法,最后返回值就是计算出来的值:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ……
    int width;
    int height;
    ……
    setMeasuredDimension(width, height);
}

对于ViewGroup:如ScrollView中是在如下的方法中测量的,因为只有一个直接的子view只要能够拿到子view的规格就可以确定了,如果是其他的ViewGroup那么就要遍历所有的子控件进行运算,这个可以参考http://www.jianshu.com/p/5a71014e7b1b这个博客

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
……
if (getChildCount() > 0) {
……
final int desiredHeight = getMeasuredHeight() - heightPadding;
if (child.getMeasuredHeight() < desiredHeight) {
final int childWidthMeasureSpec = getChildMeasureSpec(
widthMeasureSpec, widthPadding, lp.width);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
desiredHeight, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
Activity中保存一个Window对象mWindow,实现类是PhoneWindow

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值