频道栏目
首页 > 资讯 > Android > 正文

Android UI绘制流程(二)

17-05-06        来源:[db:作者]  
收藏   我要投稿

Android UI绘制流程。我们平时写布局应该对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()方法:
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。

相关TAG标签
上一篇:工作线程(耗时操作)与UI线程实现异步更新
下一篇:Android Canvas 绘图之 PorterDuffXfermode
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站