View的工作流程

来源:互联网 发布:微信公众平台修改域名 编辑:程序博客网 时间:2024/05/16 17:38

View的工作流程

measure
测量View的宽高;measure完成之后,通过getMeasuredWidth(),getMeasuredHeight()可以获取控件的宽高,大部分View宽高就是测量的宽高
layout
确定View在父容器中放置的位置,四个顶点坐标及View实际宽高
完成后可通过getTop(),getBottom(),getLeft(),getRight()获取具体坐标;getWidth(),getHeight()获取控件真实宽高
draw
负责将View绘制在屏幕上

measure

ViewGroup

它没有measure方法,measureChildren与其类似

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

测量子控件measureChildren

protected void measureChild(View child, int parentWidthMeasureSpec,        int parentHeightMeasureSpec) {    //获取parent布局参数       final LayoutParams lp = child.getLayoutParams();    //宽度MeasureSpec    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,            mPaddingLeft + mPaddingRight, lp.width);    //高度MeasureSpec            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,            mPaddingTop + mPaddingBottom, lp.height);    //调用child的测量方法            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}
//Dimension尺寸public static int getChildMeasureSpec(int spec, int padding, int childDimension) {      int specMode = MeasureSpec.getMode(spec);      int specSize = MeasureSpec.getSize(spec);      int size = Math.max(0, specSize - padding);      int resultSize = 0;      int resultMode = 0;      switch (specMode) {      // Parent has imposed an exact size on us      case MeasureSpec.EXACTLY:          if (childDimension >= 0) {              resultSize = childDimension;              resultMode = MeasureSpec.EXACTLY;          } else if (childDimension == LayoutParams.MATCH_PARENT) {              // Child wants to be our size. So be it.              resultSize = size;              resultMode = MeasureSpec.EXACTLY;          } else if (childDimension == LayoutParams.WRAP_CONTENT) {              // Child wants to determine its own size. It can't be              // bigger than us.              resultSize = size;              resultMode = MeasureSpec.AT_MOST;          }          break;      // Parent has imposed a maximum size on us      case MeasureSpec.AT_MOST:          if (childDimension >= 0) {              // Child wants a specific size... so be it              resultSize = childDimension;              resultMode = MeasureSpec.EXACTLY;          } else if (childDimension == LayoutParams.MATCH_PARENT) {              // Child wants to be our size, but our size is not fixed.              // Constrain child to not be bigger than us.              resultSize = size;              resultMode = MeasureSpec.AT_MOST;          } else if (childDimension == LayoutParams.WRAP_CONTENT) {              // Child wants to determine its own size. It can't be              // bigger than us.              resultSize = size;              resultMode = MeasureSpec.AT_MOST;          }          break;      // Parent asked to see how big we want to be      case MeasureSpec.UNSPECIFIED:          if (childDimension >= 0) {              // Child wants a specific size... let him have it              resultSize = childDimension;              resultMode = MeasureSpec.EXACTLY;          } else if (childDimension == LayoutParams.MATCH_PARENT) {              // Child wants to be our size... find out how big it should              // be              resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;              resultMode = MeasureSpec.UNSPECIFIED;          } else if (childDimension == LayoutParams.WRAP_CONTENT) {              // Child wants to determine its own size.... find out how              // big it should be              resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;              resultMode = MeasureSpec.UNSPECIFIED;          }          break;      }      return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  }

表1-1        普通View的MeasureSpec创建规则

parentSpecMode
childLayoutParams EXACTLY AT_MOST UNSPECIFIED dp/px EXACTLY
childSize EXACTLY
childSize EXACTLY
childSize match_parent EXACTLY
parentSize AT_MOST
parentSize UNSPECIFIED
0 wrap_parent AT_MOST
parentSize AT_MOST
parentSize UNSPECIFIED
0

LinearLayout

查看实例的具体实现

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){    if (mOrientation == VERTICAL) {        //测量垂直布局        measureVertical(widthMeasureSpec, heightMeasureSpec);    } else {        measureHorizontal(widthMeasureSpec, heightMeasureSpec);    }}

方法:measureVertical

// See how tall everyone is. Also remember max width.遍历子控件for (int i = 0; i < count; ++i) {    final View child = getVirtualChildAt(i);        ......        // Determine how big this child would like to be. If this or        // previous children have given a weight, then we allow it to        // use all available space (and we will shrink things later        // if needed).        measureChildBeforeLayout(               child, i, widthMeasureSpec, 0, heightMeasureSpec,               totalWeight == 0 ? mTotalLength : 0);        if (oldHeight != Integer.MIN_VALUE) {           lp.height = oldHeight;        }        final int childHeight = child.getMeasuredHeight();        final int totalLength = mTotalLength;        mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));         }}

一般在onLayout中获取测量的宽/高会比较准确

在activity启动的时候,获取view测量的宽高

1.Activity/View#onWindowFocusChanged

@Overridepublic void onWindowFocusChanged(boolean hasFocus) {    super.onWindowFocusChanged(hasFocus);    view.getMeasuredWidth();    view.getMeasuredHeight();}

View已经初始化完毕,宽/高已经准备好了。这个时候去获取宽高是没有问题的。onWindowFocusChanged会被调用多次,当activity获取焦点或失去焦点的时候都会被调用。具体就是当activity继续执行和暂停执行都会被调用,onResume和onPause也会调用
2.view.post(runnable)

protected void onStart(){    super.onStart();     view.post(new Runnable() {         @Override         public void run() {             view.getMeasuredHeight();             view.getMeasuredWidth();         }     });}

通过post可以将一个runnable投递到消息队列尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了
3.ViewTreeObserver
ViewTreeObserver可以完成,当View树的状态发生改变或者View树内部的View可见性发生改变,onGlobalLayout将被回调。但可能会被调用多次

protected void onStart() {    super.onStart();    ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();    viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {        @Override        public void onGlobalLayout() {            view.getViewTreeObserver().removeOnGlobalLayoutListener(this);            view.getMeasuredHeight();            view.getMeasuredWidth();        }    });}

4.view.measure(widthSpec,heightSpec)
根据View的LayoutParams来分

  • match_parent

  • 具体数值

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);view.measure(widthMeasureSpec,heightMeasureSpec);
  • wrap_parent
    MeasureSpec的size最大值是30个1,2^30-1=(1<<30)-1,所以用最大的值去构造MeasureSpec
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.EXACTLY);int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.EXACTLY);view.measure(widthMeasureSpec,heightMeasureSpec);

View

View的measure方法是final的,所以他不能被子类重写。measure方法中调用了

onMeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  //设置测量的宽、高  //protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),           getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}

方法:getDefaultSize

/** * Utility to return a default size. Uses the supplied size if the * MeasureSpec imposed no constraints. Will get larger if allowed * by the MeasureSpec. * 应用程序返回一个默认的大小.如果没有强制约束,则会使用建议的size.如果MeasureSpec允许,将可以让size更大 * @param size Default size for this view * @param measureSpec Constraints imposed by the parent(parent的强制约束) * @return The size this view should be. */public static int getDefaultSize(int size, int measureSpec) {    int result = size;    int specMode = MeasureSpec.getMode(measureSpec);    //View测量后的大小    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;}

UNSPECIFIED一般用于系统内部的测量过程
具体测量过程如下,
方法:getSuggestedMinimumWidth()

protected int getSuggestedMinimumWidth() {    //没有背景:返回xml中android:minWidth,未指定则为0    //有背景:返回android:minWidth、背景宽度的较大值    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());}//stream.addProperty("measurement:minWidth", mMinWidth);//xml中的配置属性android:minWidthpublic int getMinimumWidth() {    return mMinWidth;}

Drawable.java

public int getMinimumWidth() {    //    final int intrinsicWidth = getIntrinsicWidth();    return intrinsicWidth > 0 ? intrinsicWidth : 0;}

方法:getSuggestedMinimumHeight()

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

直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时自身大小,否则在布局中使用wrap_content与match_content是一样的-见表1,
像系统的TextView、ImageView都做了处理。可查看源码

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {      super(widthMeasureSpec, heightMeasureSpec);      int widthMode = MeasureSpec.getMode(widthMeasureSpec);      int heightMode = MeasureSpec.getMode(heightMeasureSpec);      int widthSize = MeasureSpec.getSize(widthMeasureSpec);      int heightSize = MeasureSpec.getSize(heightMeasureSpec);      if(widthMode == MeasureSpec.AT_MOST&&heightMode == MeasureSpec.AT_MOST){          setMeasuredDimension(mWidthSize mHeight);      }else if(widthMode == MeasureSpec.AT_MOST){               setMeasuredDimension(mWidth, heightSize );      }else if(heightMode == MeasureSpec.AT_MOST){                  setMeasuredDimension(widthSize, mHeight);      }   }

Layout

layout确定view的位置,onlayout确定所以子元素的位置

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(changed, l, t, r, b);        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);            }        }    }    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;}

setFrame设定View顶点的四个值,View和ViewGroup并没有真实实现onLayout。

LinearLayout

LinearLayout分为水平和垂直布局

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);}}
void layoutVertical(int left, int top, int right, int bottom) {           ......    final int count = getVirtualChildCount();    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(child, childLeft, childTop + getLocationOffset(child),                    childWidth, childHeight);            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);            i += getChildrenSkipCount(child, i);        }    }}

此方法会遍历所有子元素并调用setChildFrame来为子元素指定位置,其中childTop会逐渐增大,后面元素就会放置到下方。childWidth、childHeight是测量高度

private void setChildFrame(View child, int left, int top, int width, int height) {    //调用View的layout方法            child.layout(left, top, left + width, top + height);}

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) {            // Step 3, draw the content            if (!dirtyOpaque) onDraw(canvas);            // Step 4, draw the children            dispatchDraw(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);            // we're done...            return;        }}
  1. 绘制背景 background.draw(canvals)
  2. 绘制自己(onDraw)
  3. 绘制children(dispatchDraw)
  4. 绘制装饰(onDrawScrollBars)

View有一个特殊的方法
如果一个View不需要绘制任何内容,那么设置这个标记位为true之后,系统会进行相应的优化。默认情况下,View没有开启这个标记位,但是ViewGroup会默认启用这个优化标记位。这个标记位对实际开发的意义是:当我们的自定义控件继承于ViewGroup并且本身不具备绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化

 /** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations(优化计算). By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or not this View draw on its own */public void setWillNotDraw(boolean willNotDraw) {    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);}

注意

1.直接继承View或ViewGroup的控件,在onMeasure中对wrap_content要做特殊处理。
2.继承自View,在draw方法中处理padding,否则padding值无法起作用
3.继承自ViewGroup,要在onMeasure、onLayout中考虑padding和子元素的margin对其造成的影响,不然将导致padding和子元素的margin失效
4.View中如果有线程或者动画,需要及时停止View#onDetachedFromWindow

0 0