Android之View的工作原理

一、ViewRoot

ViewRoot对应与ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRootImpl来完成的。

在ActivityThread中,当Activity对象被创建完毕后,会将DecoreView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecoreView关联。源码如下:

root = new ViewRootImpl(view.getContext(),display);
root.setView(view,wparms,panelParentView);

View的绘制流程是从ViewRootperformTraversal方法开始的,它经过measure,layout和draw三个过程才能将一个View绘制出来。 其中measure 用来测量View的宽和高,layout用来确定View在父容器中的放置位置,而draw则负责将View绘制在屏幕上,针对perforTraversal的大致流程,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YDJtx149-1594379997773)(C7E82B6259974839B8B5AA0C7D41E13B)]

performTraversal会依次调用performMeasure、performLayout和performDraw三个方法,这三个方法分别完成顶级View的measure、layout和draw这三大流程,其中performMeasure会调用measure方法,在measure方法又会调用onMeasure方法,在onMeasure方法中则会对所有子元素进行measure过程,这个时候measure流程就从父容器传递带子元素中,这样就完成了一次measure过程。 接着子元素会重复父容器的measure过程,如此反复就完成了整个View树的遍历。 同理performLayout和performDraw的传递流程和performMeasure类似。 唯一不同的是,performDraw的过程在draw方法中通过dispathDraw来实现。



二、DecorView

DecorView为顶级View,其实是一个FrameLayout。 一般情况下它内部包含一个竖直方向的LinearLayout,在这个LinearLayout里有上下文两部分(具体情况和Android版本及主题有关),上面是标题栏,下面是内容栏。
在Activity中通过setContentView所设置的布局就是被加到内容栏中,而内容栏的id为content。因此我们可以ViewGroup content = findViewById(R.id.content).

在这里插入图片描述



三、MeasureSpec

MeasureSpec 代表一个32位int数值,高2位代表SpecMode测量模式,低30位代表SpecSize某种测量模式下的规格大小。

先看看它的源码:

  public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;  
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT; //11000000000000000000000000000000
        
        
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;//00000000000000000000000000000000
        public static final int EXACTLY     = 1 << MODE_SHIFT;//01000000000000000000000000000000
        public static final int AT_MOST     = 2 << MODE_SHIFT;//10000000000000000000000000000000
        
         public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK); //把size填充到后30位,mode填充到前2位
            }
        }
        
        
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }
        
        
          public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
        
        ...
        ...
        
    }    

可以看到定义的MODE_MASK=0x3 << 30 频繁地被使用。 那它为什么是 0x3 再左移30位呐?为什么不是0x4再左移40位呐?这肯定有它使用道理的,而不是说Google觉得3很吉利就选它。
首先 0x3 用二进制表示就是 11,左移30位后就是11000000000000000000000000000000。为什么是30位呐?因为Int是32位的,11左右30位后就是刚好32位了。

好了,了解了这个之后。那问题来了,它又有什么用呐?二进制肯定要看运算符运算嘛。

 0 & 0 = 0
 0 | 0 = 0
 0 & 1 = 0
 0 | 1 = 1
 1 & 1 = 1
 1 | 1 = 1 
 ~ 0 = 1
 ~ 1 = 0

也就是任何值 & 1 或者 ~ 0 都是原来的值。

前面为什么说MeasureSpec 代表一个32位int数值,高2位代表SpecMode测量模式,低30位代表SpecSize呐?

因为MODE_MASK 的30位为 0 , 前2位为1 。那么只要拿MeasureSpec & MODE_ MASK就可以拿到MeasureSpec的前2位的值,也就是 代表的mode的值。同理拿
MeasureSpec & ~MODE_ MASK就可以拿到MeasureSpec的后30位的值,也就是size。

如:MeasureSpec的值为 100000000000000000001111011000。

getSize :  100000000000000000001111011000 & ~ 11000000000000000000000000000000   = 00000000000000000000001111011000 (等于十进制值 1080)

getMode :  100000000000000000001111011000 &   11000000000000000000000000000000   = 10000000000000000000000000000000 (等于AT_MOST)

模式二进制数值描述
UNSPCIFIED00默认值,父控件没有给子View任何限制,子View可以设置为任意大小。
这种情况不多。 (不确定值)
EXCATLY01表示父控件已经确切的指定了子View的大小。(完全确定值)
AT_MOST10表示子View具体没有尺寸限制,但是存在上限,上限一般为父View的大小。
当控件的宽高指定为wrap_content时为这种模式。 (最大值)

以数值1080(二进制位1111011000)为例(其中模式和实际数值是连载一起的,为了展示将他们分开了):

模式名称模式数值实际数值
UNSPECIFIED00000000000000000000001111011000
EXCATLY01010000000000000000001111011000
AT_MOST10100000000000000000001111011000

对于顶级iView和普通View来说,MeasureSpec的转换过程略有不同。

DecoreView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定的。
普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定的。

MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。

对于普通的View的measure过程由ViewFroup传递而来,从ViewGroup的measureChildWithMargins方法开始:

 protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width); // 获取子View的宽MeasureSpec
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

从上面获取子View宽的MeasurePec的方法getChildMeasureSpec()可以看到 子View的MeasureSpec的创建与父容器的MeasureSpec和子View自身的LayoutParams有关。

再具体看下ViewGroup的getChildMeasureSpec(…)方法:

 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);  //父View的测量模式
        int specSize = MeasureSpec.getSize(spec);  //父View的测量尺寸

        int size = Math.max(0, specSize - padding); // 子View可设置的最大尺寸(父View尺寸减去内边距和子View的margin)

        int resultSize = 0;
        int resultMode = 0;
        // 注意 : LayoutParams.MATCH_PARENT = -1, LayoutParams.WRAP_CONTENT = -2。childDimension小于0时为这两种情况
        switch (specMode) {
        case MeasureSpec.EXACTLY: // 父View测量模式为 : 精准模式
            if (childDimension >= 0) {  //子View的尺寸为具体值 
                resultSize = childDimension;   //子View最终尺寸即为原本的指定尺寸
                resultMode = MeasureSpec.EXACTLY;   //子View的测量模式为精准模式
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;//父View为具体值了,子View为MatchParent,所以子View尺寸也确定具体值了
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.AT_MOST: //父View为最大模式,如MatchParent或者WrapContent
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST; 
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);//把测量尺寸和模式结合生成MeasureSpec
    }

View的MeasureSpec的创建规则总结如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i0E8AMyy-1594379997779)(WEBRESOURCE4c1a40592fee5379ea18d37eb5e326d0)]

简单的说就是:

  • 当View采用固定宽高时,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式且其大小遵循LayoutParams的大小;
  • 当View的宽/高为MatchParent时,如果父容器是精准模式,那么View也是最大模式并且其大小是父容器的剩余空间,如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。
  • 当View的宽/高为wrapContent时,不管父容器的模式是精准还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。


四、View的measure过程

如果只是一个原始的View,那么通过measure过程就完成了测量过程;
如果是一个ViewGroup,除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归执行这个过程。

4.1 View的measure过程

View的measure过程由其measure方法来完成,measure方法是一个final类型的方法。因此不能重新该方法,在View的measure过程会调用View的onMeasure方法。因此只需要看onMeasure的实现即可。

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

setMeasureDimensiion方法会设置View宽/高的测量值。 我们只需要getDefaultSize这个方法即可:

  /**
    * size为上面传入的getSuggestedMinimumWidth()
    */
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;
    }

对于我们来说,我们只需看AT_MOST和XACTLY这两种情况。
至于UNSPECIFIED这种情况,一般用于系统内部的测量过程。这种情况 下View的大小为getDefaultSize()的第一个参数,即getSuggestedMinnimumWidth和即getSuggestedMinnimumHeight这两个方法的返回值。看一下源码:

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

看代码可知如果View没有设置背景,那么View的宽度为通过android:minWidth这个属性所指定的mMinWidth,如果这个属性不指定,则mMinWidth默认为0;如果View指定了背景,则View的宽度为max(mMinWidth, mBackground.getMinimumWidth())。那么mBackground.getMinimumWidth())是什么呢?该方法在Drawable类中:

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

可以看出,getMinimumWidth返回的就是Drawable的原始宽度,否则返回0 。 如ShapeDrawable原始宽度为0,BitmapDrawable有原始宽度(图片的尺寸)。

getSuggestedMinimumWidth和getSuggestedMinimumHeight的返回值就是View在UNSPECIFIED情况下的测量宽/高。

现在问题来了。那么平时我们自定义View时为什么要从写onMeasure()方法呐?

从第三部分MeasureSpec的讲解getChildMeasureSpec(…)可以看到,如果View在布局使用的是wrap_content,那么它的specMode是AT_MOST模式,在这种模式下,它的宽高等于specSize,也就是parentSize,相当于matchParent。 也就是说继承View的自定义控件需要自定义处理wrapContent的情况:

int mWidth = 50;
int mHeight = 100;

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, mHeight);
    } else if (widthSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, heightSpecSize);
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widthSpecSize, mHeight);
    }
}

在上面代码中,我们只需给View指定一个默认的内部宽/高(mWidth/mHeight),并在wrap_content是设置宽/高即可。对于非wrap_content情形,我们沿用系统的测量值即可。

4.2、ViewGroup的measure过程

对于ViewGroup来说,除了完成自己的measure过程以外,还会遍历调用所有子元素的measure方法,各个子元素在递归去执行这个过程。 和View不同的是,ViewGroup是一个抽象类,因此它没有重写View的onMeasure
方法,但是它提供了一个叫measureChildren的方法,如下:

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


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

那为什么ViewGroup并没有定义其测量的具体过程?

因为ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现。比如LinearLayout,RelativeLayout等。

4.3、注意:

1、是否可以在onCreate或者onResume里面去获取这个View的宽/高?

答: 不行。因为View的测量measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate、onStart、onResume时某个View已经测量完毕。如果没有,则获得的宽/高为0.那解决方法呢?

  1. 在Activity/View的onWindowFocusChange中获取。但是要注意的是每次onResume或者onPause时该方法都会被调用。代码如下:
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
    super.onWindowFocusChanged(hasWindowFocus);
    if (hasWindowFocus){
        int width = view.getMeasuredWidth();
        int height = view.getMinimumHeight();
    }
}
  1. view.post(runnable)。 通过post将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好。
  2. 使用ViewTreeObserver。
    ViewTreeObserver的众多回调可以完成这个功能,比如使用OnGlobalLayoutListener这个接口。当View树的状态发生改变或者View树内部的View的可见性发生改变时,onGlobalLayout方法将被回调,因此这是获取View的宽/高一个很好的方法。 需要注意的是,随着View树的状态改变,onGlobalLayout会被调用多次。
 @Override
    protected void onStart() {
        super.onStart();
    
        ViewTreeObserver observer = customView.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                customView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                int width = customView.getMeasuredWidth();
                int height = customView.getMeasuredHeight();
            }
        });
    }

2、重写measure时,一定要调用setMeasuredDimension()。

在这里插入图片描述



五、View的layout过程

在说layout之前,先说下View的坐标体系:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aSsxA6kH-1594379997781)(https://camo.githubusercontent.com/09df2f3f82180fd70ca62b084d906977215458f3/687474703a2f2f7777322e73696e61696d672e636e2f6c617267652f30303558746469326777316631717a7177766b6b626a33303863306477676d392e6a7067?ynotemdtimestamp=1546853083193)]

可以看到通过 getLeft和getTop等可以获取到View在父容器内的相对位置。查源码看:

@ViewDebug.CapturedViewProperty
public final int getLeft() {
    return mLeft;
}

可以看到返回的是一个mLeft,那这个mLeft是怎么来的呢? 这就是View的layout的作用。 通过layout()方法确定了View的mLeft,mTop,mRight,mBottom。也就是layout确定了View在父类的位置。

5.1、View的layout()过程
 //该方法会在父容器调用(也就是说这几个参数是有父容器确定的)
 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);
         ... ... 
         }
     }
    ... ...
 }
 
 //通过该方法给mLeft等赋值,确定
 protected boolean setFrame(int left, int top, int right, int bottom) {
     boolean changed = false;

    
     if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
         changed = true;

        ... ...

         mLeft = left;
         mTop = top;
         mRight = right;
         mBottom = bottom;
         mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);


         ... ...
     }
     return changed;
 }
  
  //因为单一View是没有子View的,所以空实现
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
 }
 
 
5.2、ViewGroup的layout()过程

ViewGroup不仅会通过layout()过程计算自身的位置,还会遍历子View并通过onLayout()方法确定子View在自身中的位置。 我们实现ViewGroup时需要重写onLayout方法去自定义控件的子控件放置规则。

以下为FrameLayout的onLayout方法:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount();

    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();

    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();

    for (int i = 0; i < count; i++) {   //遍历子View
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            int childLeft;
            int childTop;

            int gravity = lp.gravity;
            if (gravity == -1) {
                gravity = DEFAULT_CHILD_GRAVITY;
            }

            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                    lp.leftMargin - lp.rightMargin;
                    break;
                case Gravity.RIGHT:
                    if (!forceLeftGravity) {
                        childLeft = parentRight - width - lp.rightMargin;
                        break;
                    }
                case Gravity.LEFT:
                default:
                    childLeft = parentLeft + lp.leftMargin;
            }

            switch (verticalGravity) {
                case Gravity.TOP:
                    childTop = parentTop + lp.topMargin;
                    break;
                case Gravity.CENTER_VERTICAL:
                    childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                    lp.topMargin - lp.bottomMargin;
                    break;
                case Gravity.BOTTOM:
                    childTop = parentBottom - height - lp.bottomMargin;
                    break;
                default:
                    childTop = parentTop + lp.topMargin;
            }

            child.layout(childLeft, childTop, childLeft + width, childTop + height);//调用子View的layout()方法
        }
    }
}
5.3、注意: 对于重写onLayout方法,一定要对子View调用layout()方法,这样才能将子View放到合适的位置。

在这里插入图片描述



六、view的draw过程

View的draw过程是将View绘制到屏幕上面,View的绘制过程遵循以下几步:

  1. 绘制背景background.draw(canvas);
  2. 绘制自己(onDraw);
  3. 绘制children(dispatchDraw);
  4. 绘制装饰(onDrawScrollBars).

对于单一的View来说,其dispathDraw()方法为空实现,而ViewGroup的dispatch方法内部为遍历子View并调用其draw(canvas)的过程。

@CallSuper
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;

    // 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);  //绘制子View

        drawAutofilledHighlight(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);  //绘制装饰

        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }

        // we're done...
        return;
    }

   ...
    }
    



@CallSuper
public void dispatchDraw(Canvas canvas) {

    ......
    
    for (int i = 0; i < childrenCount; i++) {
       ......
        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            more |= drawChild(canvas, child, drawingTime); // 绘制子View
        }
    }
    
   ......
}



protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime); // 调用了子View的draw()方法
}
    

以下为draw过程:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值