View工作流程

鸽了好久还是弄完了,相当于看书记的笔记了......

1、基础概念

  • ViewRoot: ViewRootImpl 类,连接 WindowManager 和 DecorView,三大流程均通过 ViewRoot 完成。
  • decorView:本质上是一个FrameLayout,是Activity中所有View祖先。

View绘制流程是从ViewRoot的performTraversals方法开始,经过measure,layout,draw三步完成。

  • measure 用于测量 View 的宽高
  • layout 用于确定 View 在父容器中的位置‘
  • draw 用于绘制

performTraverslas会依次调用performMeasure,performLayout,performDraw三个方法,分别完成顶级View的三大流程。在performMeasure中调用measure方法,在measure中调用onMeasure对所有子元素测量,完成对View所有子元素的测量。performLayout和performDraw与performMeasure类似。

2、MeasureSpec

MeasureSpec决定了一个View的尺寸规格,但还受父容器影响。在测量过程中,系统会将View的LayoutParams根据父容器施加的规则转化成对应的MeasureSpec,然后再测量View的宽高。
MeasureSpec代表了一个32位int值,高2位代表SpecMode,即测量模式,低30位代表SpecSize,规格大小。
SpecMode有三类,如下:

  • UNSPECIFIED:父容器不对View有任何限制,一般用于系统内部
  • EXACTLY:父容器已经检测出View的精确大小,此时最终大小就是SpecSize的值,对应LayoutParams中的match_parent和具体数值两种情况。
  • AT_MOST:父容器指定SpecSize,View的大小不能大于这个值,对应于LayoutParams中的wrap_content。

3、View的工作流程

View的工作流程主要指measure,layout,draw这三大流程。

3.1、measure

measure的测量分为对ViewGroup的测量和对View的测量,对View测量时,通过measure即可完成,如果是一个ViewGroup时,除了完成自己的测量,还会遍历所有子元素发的measure方法。

3.1.1、View

view的measure由其measure方法完成,在View的measure方法中会去调用onMeasure方法。

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

setMeasureDimension会设置View的宽高测量值,需要调用getDefaultSize方法

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

在getDefaultSize方法中,在AT_MOST和EXACTLY状态,getDefaultSize返回MeasureSpec中的SpecSize,即View测量后的大小。在UNSPECIFIED状态下,返回值为size的值,即宽高分别为getSuggestedMinimumWidth和getSuggestedMinimumHeight的返回值。

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

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

在上述代码中,如果mBackground是null,即View没有设置背景,那么宽度为mMinWidth,高度为mMinHeight。而mminWidth对应android:minWidth属性对应的值,默认为0,如果View指定了背景,则View的宽度为max(mMinWidth,mBackground.getMinimumWidth()),即mMinWidth和mBackground.getMinimumWidth()中较大的值。

  • mBackground.getMinimumWidth
public int getMinimumWidth(){
    final int intinsicWidth=getIntrinsicWidth();
    return intrinsicWidth > 0 ? intrinsicWidth :0 ; 
}

则getMinimumWidth返回的是Drawable的原始宽度。

由此可见,View的宽高有SpecSize决定。直接继承View的空间需要重写onMeasure方法并设置wrap_content时的自身大小,否则View的SpecSize是parentSize。

3.1.2、ViewGroup

对于ViewGroup来说,除了完成自己的measure过程以外,还需要遍历所有子元素的measure,子元素递归执行。ViewGroup没有重写inMeasure方法,同时它提供了一个measureChildren的方法。

  • measureChildren
protected void measureChildren(int widthMeasureSprc,int heightMeasureSpec){
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for(int i=0;i

在measureChildren方法中,会遍历所有子元素,并测量所有子元素的大小。

  • measureChild
protect void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec){
    final LayoutParams lp=child.getLayoutParams();
    final int childWidthMeasureSpec=getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec=getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height);
    child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}

在measureChild中取出子元素的LayoutParams,之后通过getChildMeasureSpec创建子元素的MeasureSpec,之后将MeasureSpec传递给View的measure方法测量。

ViewGroup是一个抽象类,不同的布局有不同的测量方法,所以其测量过程onMeasure需要有各个子类的实现。

分析LinearLayout的onmeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
    if(mOrientation == VERTICAL){
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    }
    else{
        measureHerizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

在LinearLayout的measure方法中会根据orientation选择是measureVertical还是measureHerizontal方法,在相应方法中会遍历子元素执行measureChildBeforeLayout方法,该方法内部会调用子元素的measure方法,并且通过mTotalLength来储存LinearLayout在数值方向的初步高度,在MTotalLength中包含了子元素的高度和在竖直方向上的margin,子元素测量完毕后,LinearLayout会测量自己的大小。

3.2、Layout

Layout是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定以后,会在onLayout中遍历所有子元素并调用layout方法,在了layout方法中onLayout方法又会被调用,layout方法确定View本身的位置,而onLayout方法确定所有子元素的位置。

  • layout
public void layout(int l,int t,int r.int b){
    if((mPrivateFlags3 & PFLAGS3_MEASURE_NEEDED_BEFORE_LAYOUT)!+0){
        onMeasure(mOldWidthMeasureSpec,mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAGS3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
    int oldL=mLeft;
    int oldT=mTop;
    int oldB=mButtom;
    int oldR=mRight;

    boolean changed=isLayoutModeOptical(mParent)? setOpticalFrame(l,t,r,b): setFrame(l,t,r,b);

    if(changed || (mPrivateFlags & PLAG_LAYOUT_REQUIRED)==PFLAG_LAYOUT_REQUIRED){
        onLayout(changed,l,t,r,b);
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        ListenerInfo li=mListenerInfo;
        if(li != null && li.mOnLayoutChangedListeners != null){
            ArrayList listenersCopy = (ArrayList)li.mOnLayoutChangeListeners.clone();
            int numListeners=listenersCopy.size();
            for(int i=0;i

layout方法的流程如下,首先通过setFrame方法设定四个顶点的位置,初始化mLeft,MRight,mTop,mBottom四个值,之后调用onLayout方法,父容器确定子元素的位置,和onMeasure方法类似,onLayout具体的实现取决于布局。
示例LinearLayout的onLayout方法。

  • onLayout
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);
    }
}

在onLayout中,依旧会根据不同的布局(vertical和horizontal)选择不同的方法。

  • layoutVertical
void layoutVretical(int left,int top,int right,int bottom){
    ...
    final int count=getVerticalChildCount();
    for(int i=0;i

layoutVertical会遍历所有子元素,并且会调用getChildFrame方法位子元素指定相应的位置,其中childTop会逐渐增大,即后面的子元素会放在靠下的位置。setChildFrame调用了元素的layout方法,这样父元素在layout方法中完成自己的定位以后,通过onLayout方法调用子元素的layout方法,子元素又通过自己的layout方法来确定自己的位置。

  • setChildFrame
private void setChildFrame(View child,int left,int top,int width,int height){
    child.layout(left,top,left+width,top+height);
}

在setChildFrame中的width和height实际就是子元素的测量的宽高。
而在layout方法中会通过setFrame去设置子元素四个顶点的位置。

3.3、Darw

Draw的绘制步骤如下:

  • 绘制背景 background.draw(canvas)
  • 绘制自己 onDraw
  • 绘制children dispatchDraw
  • 绘制装饰 onDrawScrollBars
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;

    int saveCount;

    //绘制背景
    if(!dirtyOpaque){
        drawBackground(canvas);
    }

    final int viewFlags=mViewFlags;
    boolean horizontalEdges=(viewFlags & FADING_EDGE_HORIZONTAL) !=0;
    boolean verticalEdges=(viewFlags & FADING_EDGE_VERTICAL) !=0;
    if(!verticalEdges && !horizontalEdges){
        if(!dirtyOpaque) onDraw(canvas);

        dispatchDraw(canvas);

        onDrawScrollBars(canvas);

        if(mOverlay != null && ! mOverlayl.isEmpty()){
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }
        return;
    }

    ...
}

View的绘制是通过dispatchDraw实现的,dispatchDraw会遍历所有子元素的draw方法。

参考来源:

《Android开发艺术探索》

你可能感兴趣的:(View工作流程)