View的工作流程

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
EXACTLYAT_MOSTUNSPECIFIED
dp/pxEXACTLY
childSize
EXACTLY
childSize
EXACTLY
childSize
match_parentEXACTLY
parentSize
AT_MOST
parentSize
UNSPECIFIED
0
wrap_parentAT_MOST
parentSize
AT_MOST
parentSize
UNSPECIFIED
0
LinearLayout

查看实例的具体实现

@Override
protected 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

@Override
public 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:minWidth
public 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都做了处理。可查看源码

@Override
protected 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值