Android UI绘制流程(二)

来源:互联网 发布:js window.scrolltop 编辑:程序博客网 时间:2024/06/05 01:02

上一篇博客介绍了我们自己写的布局是如何通过setContentView()来加载到屏幕中。这一篇将会继续讲解当布局文件加载出来后,布局里边的控件是经过怎样的步骤来显示出来的。

在上一篇博客中最后分析出我们自己写的布局最终会加载到DecorView中id为content的FramLayout中,而Framlayout继承自ViewGroup。所以如果要分析整个控件的绘制流程首先要从ViewGroup着手。

我们平时写布局应该对ViewGroup非常了解。ViewGroup相当于一个容器,里边可以放ViewGroup或者View对象。而ViewGroup和View的包含区别就是ViewGroup里边可以添加,View不可以在添加。

了解了ViewGroup和View的关系后,那么ViewGroup里的ViewGroup和View是经过怎样的过程最终显示到设定的位置的呢?其实ViewGroup就相当于一棵树,View相当于树的叶子。ViewGroup要经历 测量onMeasure(),摆放onLayout(),画 onDraw()三个主要方法最终显示出来。


首先来看onMeasure()测量的方法。

首先我们提出一个猜想,在整个的ViewGroup的测量步骤中,首先要测量并确定所有的子View的大小,再确定View树的根ViewGroup的大小,就像数据结构中的树的前序遍历。

根据前面的猜想,我们应该先测量所有的子View。在ViewGroup方法的中,我们看到了measureChildren()方法,所以可以根据这个方法作为线索,向下分析。

1 measureChildren() 测量所有子View

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {        final int size = mChildrenCount;        final View[] children = mChildren;        //1 遍历子View        for (int i = 0; i < size; ++i) {            final View child = children[i];            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {                //2 测量子View                measureChild(child, widthMeasureSpec, heightMeasureSpec);            }        }    }

这个方法有两个参数,widthMeasureSpec和heightMeasureSpec。所以在看这个方法之前,先来看一下MeasureSpec。
MeasureSpeck用32位的int值来表示,高两位表示MeasureSpec的SpecMode属性,低30位表示SpecSize属性。
SpecMode有三种:

  • EXACTLY 精确值 当宽高设置具体的精确值 比如200dp。
  • AT_MOST 最大值 一般用于当设置宽高为martch_parent或者wrap_content时。
  • UNSPECIFIED 未指定的大小,一般用于ListView,ScrollView等不能确定大小的控件。

SpecSize就表示控件的大小的值。
SpecMode和SpecSize可以通过MeasureSpec的getSpecMode()和getSpecSize()方法来获取。
同时,也可以把SpecMode和SpecSize通过MeasureSpec的makeMeasureSpec()方法来合成一个MeasureSpec。

下面再来看上面这段代码,从这个方法中可以看出,首先要遍历所有的子View,再调用measureChild()方法测量子View。下面来看测量View自己的方法。

2 measureChild() 子View测量自己

 protected void measureChild(View child, int parentWidthMeasureSpec,            int parentHeightMeasureSpec) {        //1 获取child自己的相关参数            final LayoutParams lp = child.getLayoutParams();        //2 获取childWidthMeasureSpec 和 childHeightMeasureSpec         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                mPaddingLeft + mPaddingRight, lp.width);        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,                mPaddingTop + mPaddingBottom, lp.height);        //3 根据宽高属性,测量自己        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }

1 这个方法中首先获取了child自己的参数Layoutparams。
2 然后根据所在ViewGroup的MeasureSpec和padding,本身的宽度来获取宽和高的MeasureSpec。
3 根据宽和高的MeasureSpec来测量自己。

下面来看ViewGroup中测量的核心:
2.1 getChildMeasureSpec() 获得子View的MeasureSpec

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {        //1 获得父ViewGroup的specMode和specSize        int specMode = MeasureSpec.getMode(spec);        int specSize = MeasureSpec.getSize(spec);        int size = Math.max(0, specSize - padding);        int resultSize = 0;        int resultMode = 0;        //2判断父ViewGroup的specMode        switch (specMode) {            //2.1如果父ViewGroup是一个具体的值            case MeasureSpec.EXACTLY:                //2.1.1 child是一个具体值                if (childDimension >= 0) {                    //child的size将会是child自己设定的值                    resultSize = childDimension;                    //因为child的size是精确值,所以child的specMode是EXACTLY                    resultMode = MeasureSpec.EXACTLY;                //2.1.2 child想和父ViewGroup一样大                    } else if (childDimension == LayoutParams.MATCH_PARENT) {                    //因为此时父ViewGroup的size是确定的,child又想和父ViewGroup一样大,所以child的size和父ViewGroup的size相等                    resultSize = size;                    //因为child的size是精确值,所以child的specMode是EXACTLY                    resultMode = MeasureSpec.EXACTLY;                    //2.3.3 child想要自己本身的大小                      } else if (childDimension == LayoutParams.WRAP_CONTENT) {                    //限制child不能比父ViewGroup大 让child的size和父ViewGroup的size相等                    resultSize = size;                    //child大小此时不能确定 所以specMode也是AT_MOST                    resultMode = MeasureSpec.AT_MOST;                }                break;            //2.2如果父ViewGroup是一个最大值            case MeasureSpec.AT_MOST:                //2.2.1 child是一个具体值                if (childDimension >= 0) {                    //child的size将会是child自己设定的值                    resultSize = childDimension;                    //因为child的size是精确值,所以child的specMode是EXACTLY                    resultMode = MeasureSpec.EXACTLY;                //2.3.2 child想和父ViewGroup一样大                } else if (childDimension == LayoutParams.MATCH_PARENT) {                    //child想和父ViewGroup一样大,但是父ViewGroup的size是不固定的,所以限制child不能比父ViewGroup大                    resultSize = size;                    // child的specMode也是AT_MOST                    resultMode = MeasureSpec.AT_MOST;                //2.3.3 child想要自己本身的大小                     } else if (childDimension == LayoutParams.WRAP_CONTENT) {                    // Child wants to determine its own size. It can't be                    // bigger than us.                    //同样限制child不能比父ViewGroup大                    resultSize = size;                    //同样child的specMode也是AT_MOST                    resultMode = MeasureSpec.AT_MOST;                }                break;            //2.3如果父ViewGroup想知道子控件有多大            case MeasureSpec.UNSPECIFIED:                //2.3.1 child是一个具体值                if (childDimension >= 0) {                    //child的size将会是child自己设定的值                    resultSize = childDimension;                    //因为child的size是精确值,所以child的specMode是EXACTLY                    resultMode = MeasureSpec.EXACTLY;                //2.3.2 child想和父ViewGroup一样大                } else if (childDimension == LayoutParams.MATCH_PARENT) {                    //child的size将会是0或者父ViewGroup的大小                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                    //因为child的父ViewGroup是未指定的,child又想和父ViewGroup一样大,所以child的specMode也是UNSPECIFIED                    resultMode = MeasureSpec.UNSPECIFIED;                //2.3.3 child想要自己本身的大小                } else if (childDimension == LayoutParams.WRAP_CONTENT) {                    //child的size将会是0或者父ViewGroup的大小                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                    // 同时child的specMode也是UNSPECIFIED                    resultMode = MeasureSpec.UNSPECIFIED;                }                break;        }        //3 把child的SpecSize和SpecMode合成一个MeasureSpec并返回        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

这段代码可以算是ViewGroup中的重点代码了。我几乎把所有的代码都写了注释。这个方法主要是结合ViewGroup的MeasureSpec和child本身的大小来确定child的MeasureSpec。具体是如何影响child的MeasureSpec的,可以看我写的注释。写的非常清楚,这里不再赘述。

获得了child的MeasureSpec后,开始调用

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

而measure()方法中的测量工作主要是在onMeasure()中来完成了的。来看下面这段伪代码:

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

再来看onMeasure()方法:(注意这个是View中的方法,当然了,ViewGroup也是继承自View的,但是这里指的是View中的方法)
3 onMeasure()

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

在onMeasure()方法中,

1 首先获得推荐的最小宽度或高度。

protected int getSuggestedMinimumWidth() {        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());    }
protected int getSuggestedMinimumHeight() {        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());    }

2 根据推荐的高度和宽度,在结合child的MeasureSpec。来确定child的默认宽高。

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

3最后通过setMeasuredDimension()方法来把存储View的宽和高

4 setMeasuredDimension

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {        boolean optical = isLayoutModeOptical(this);        if (optical != isLayoutModeOptical(mParent)) {            Insets insets = getOpticalInsets();            int opticalWidth  = insets.left + insets.right;            int opticalHeight = insets.top  + insets.bottom;            measuredWidth  += optical ? opticalWidth  : -opticalWidth;            measuredHeight += optical ? opticalHeight : -opticalHeight;        }        setMeasuredDimensionRaw(measuredWidth, measuredHeight);    }

存储真正的操作是在setMeasuredDimensionRaw()方法中,在setMeasuredDimensionRaw()方法中直接把宽和高保存在成员变量中。

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {        mMeasuredWidth = measuredWidth;        mMeasuredHeight = measuredHeight;        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;    }

至此,View的宽和高已经确定。测量过成完成。但是这只是ViewGroup其中的一个子View的过程。如果ViewGroup1的子View也是一个ViewGroup2。那么最先测量的应该是ViewGroup2的子View。这是个递归的过程,就像是树的遍历。先遍历最小最深的结点。

需要注意的是,如果ViewGroup在测量过程中,子View也是一个ViewGroup。那么是怎么实现递归的呢?如FramLayout是怎么再去测量呢?其实FrameLayout重写了onMeasure()方法,在此方法中,还是要先去遍历所有的子View,然后再测量。如下:

  @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int count = getChildCount();        final boolean measureMatchParentChildren =                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;        mMatchParentChildren.clear();        int maxHeight = 0;        int maxWidth = 0;        int childState = 0;        //1 遍历每个child        for (int i = 0; i < count; i++) {            final View child = getChildAt(i);                if (mMeasureAllChildren || child.getVisibility() != GONE) {                         //2 开始测量每个child                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);                final LayoutParams lp = (LayoutParams) child.getLayoutParams();                maxWidth = Math.max(maxWidth,                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);                maxHeight = Math.max(maxHeight,                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);                childState = combineMeasuredStates(childState, child.getMeasuredState());                if (measureMatchParentChildren) {                    if (lp.width == LayoutParams.MATCH_PARENT ||                            lp.height == LayoutParams.MATCH_PARENT) {                        mMatchParentChildren.add(child);                    }                }            }        }

这是FramLayout中的onMeasure()方法的一部分,可以看出先去遍历,然后再去测量每个childView。

0 0
原创粉丝点击