自定义控件(View的绘制流程源码解析)

一直以来咱们都是在用Andriod自带的控件,好比TextView,Im ageView等来构建咱们的应用,有的时候并不能知足咱们的须要,接下来就来研究一下如何自定义咱们本身的控件,固然本身造新的轮子,天然先要搞懂系统自带的轮子的实现原理,下面将从源码的角度深刻分析view的绘制流程,进而来分析一下如何自定义新的控件:
下面首先从源码的角度来分析一下view的绘制流程:
先看一下view树的结构图:
能够看到android中任何一个布局或控件都是直接或间接继承于view实现的,对于咱们后面要自定义的控件天然也不例外,因此想要自定义控件首先必须得先搞明白android自带的view布局和控件究竟是如何绘制并显示到屏幕上的。
一、既然要把view绘制到屏幕上,那天然首先要搞清楚绘制是从哪开始的?
整个View树的绘图流程是在ViewRootImpl类的performTraversals()方法开始的,这个方法很长,咱们抽取主要的代码逻辑展现出来:
1 childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
2                 childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
3                 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);


......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
      ......
 1 /**
 2      * Figures out the measure spec for the root view in a window based on it's
 3      * layout params.
 4      *
 5      * @param windowSize
 6      *            The available width or height of the window
 7      *
 8      * @param rootDimension
 9      *            The layout params for one dimension (width or height) of the
10      *            window.
11      *
12      * @return The measure spec to use to measure the root view.
13      */
14     private static int getRootMeasureSpec(int windowSize, int rootDimension) {
15         int measureSpec;
16         switch (rootDimension) {
17 
18         case ViewGroup.LayoutParams.MATCH_PARENT:
19             // Window can't resize. Force root view to be windowSize.
20             measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
21             break;
22         case ViewGroup.LayoutParams.WRAP_CONTENT:
23             // Window can resize. Set max size for root view.
24             measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
25             break;
26         default:
27             // Window wants to be an exact size. Force root view to be that size.
28             measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
29             break;
30         }
31         return measureSpec;
32     }
能够看到该方法内部会调用view的measure方法,getRootMeasureSpec方法中传入了两个参数,其中lp.width和lp.height在建立ViewGroup实例时等于MATCH_PARENT,也就是说在ViewGroup实例建立的时候就被赋值了
那这里lp是什么呢?    WindowManager.LayoutParams lp = mWindowAttributes;  (注: WindowManager.LayoutParams 是 WindowManager 接口的嵌套类;它继承于 ViewGroup.LayoutParams; 它用于向WindowManager描述Window的管理策略。)实际上lp即为屏幕参数,从getRootMeasureSpec方法的实现中能够看出不管rootDimension是MATCH_PARENT或WRAP_CONTENT,最后都会强制将root view转换为全屏的,查看上面的case语句分支能够知道经过调用makeMeasureSpec方法根据传入的specSize:windowSize和specMode参数对MeasureSpec从新封装,尺寸所有为windowSize,因此根视图老是全屏的
至此咱们也便明白了最外层根视图的widthMeasureSpec和heightMeasureSpec是从哪里来的了。
接下来就是对view的绘制流程,下面就是绘制流程图:
 首先看一下measure源码:
/**
     * <p>
     * This is called to find out how big a view should be. The parent
     * supplies constraint information in the width and height parameters.
     * </p>
     *
     * <p>
     * The actual measurement work of a view is performed in
     * {@link #onMeasure(int, int)}, called by this method. Therefore, only
     * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
     * </p>
     *
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the
     *        parent
     * @param heightMeasureSpec Vertical space requirements as imposed by the
     *        parent
     *
     * @see #onMeasure(int, int)
     */
 1 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
 2         boolean optical = isLayoutModeOptical(this);
 3         if (optical != isLayoutModeOptical(mParent)) {
 4             Insets insets = getOpticalInsets();
 5             int oWidth  = insets.left + insets.right;
 6             int oHeight = insets.top  + insets.bottom;
 7             widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
 8             heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
 9         }
10 
11         // Suppress sign extension for the low bytes
12         long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
13         if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
14 
15         if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
16                 widthMeasureSpec != mOldWidthMeasureSpec ||
17                 heightMeasureSpec != mOldHeightMeasureSpec) {
18 
19             // first clears the measured dimension flag
20             mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
21 
22             resolveRtlPropertiesIfNeeded();
23 
24             int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
25                     mMeasureCache.indexOfKey(key);
26             if (cacheIndex < 0 || sIgnoreMeasureCache) {
27                 // measure ourselves, this should set the measured dimension flag back
28  onMeasure(widthMeasureSpec, heightMeasureSpec); 29                 mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
30             } else {
31                 long value = mMeasureCache.valueAt(cacheIndex);
32                 // Casting a long to int drops the high 32 bits, no mask needed
33                 setMeasuredDimensionRaw((int) (value >> 32), (int) value);
34                 mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
35             }
36 
37             // flag not set, setMeasuredDimension() was not invoked, we raise
38             // an exception to warn the developer
39             if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
40                 throw new IllegalStateException("View with id " + getId() + ": "
41                         + getClass().getName() + "#onMeasure() did not set the"
42                         + " measured dimension by calling"
43                         + " setMeasuredDimension()");
44             }
45 
46             mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
47         }
48 
49         mOldWidthMeasureSpec = widthMeasureSpec;
50         mOldHeightMeasureSpec = heightMeasureSpec;
51 
52         mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
53                 (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
54     }

仔细看measure方法声明能够看到该方法是final的,也就是子view不能对其进行重写,根据注释能够知道:为整个View树计算实际的大小,而后设置实际的高和宽,每一个View控件的实际宽高都是由父视图和自身决定的。实际的测量是在onMeasure方法进行,因此在View的子类须要重写onMeasure方法,这是由于measure方法是final的,不容许重载,因此View子类只能经过重载onMeasure来实现本身的测量逻辑java

该方法中的两个参数都是父类传递进来的,表示父类的规格;android

下面看一下View的onMeasure源码:canvas

 1 /**
 2      * <p>
 3      * Measure the view and its content to determine the measured width and the
 4      * measured height. This method is invoked by {@link #measure(int, int)} and
 5      * should be overridden by subclasses to provide accurate and efficient
 6      * measurement of their contents.
 7      * </p>
 8      *
 9      * <p>
10      * <strong>CONTRACT:</strong> When overriding this method, you
11      * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
12      * measured width and height of this view. Failure to do so will trigger an
13      * <code>IllegalStateException</code>, thrown by
14      * {@link #measure(int, int)}. Calling the superclass'
15      * {@link #onMeasure(int, int)} is a valid use.
16      * </p>
17      *
18      * <p>
19      * The base class implementation of measure defaults to the background size,
20      * unless a larger size is allowed by the MeasureSpec. Subclasses should
21      * override {@link #onMeasure(int, int)} to provide better measurements of
22      * their content.
23      * </p>
24      *
25      * <p>
26      * If this method is overridden, it is the subclass's responsibility to make
27      * sure the measured height and width are at least the view's minimum height
28      * and width ({@link #getSuggestedMinimumHeight()} and
29      * {@link #getSuggestedMinimumWidth()}).
30      * </p>
31      *
32      * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
33      *                         The requirements are encoded with
34      *                         {@link android.view.View.MeasureSpec}.
35      * @param heightMeasureSpec vertical space requirements as imposed by the parent.
36      *                         The requirements are encoded with
37      *                         {@link android.view.View.MeasureSpec}.
38      *
39      * @see #getMeasuredWidth()
40      * @see #getMeasuredHeight()
41      * @see #setMeasuredDimension(int, int)
42      * @see #getSuggestedMinimumHeight()
43      * @see #getSuggestedMinimumWidth()
44      * @see android.view.View.MeasureSpec#getMode(int)
45      * @see android.view.View.MeasureSpec#getSize(int)
46      */
47     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
48         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
49                 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
50     }
View Code

代码中的注释对于onMeasure的重写有详细的说明,该方法是由measure触发的,子类必须重写以提供精确而有效的测量c#

当重写该方法时必须调用setMeasuredDimension(int, int)来存储这个view测量的宽和高,不然会报异常,调用超类的onMeasure方法能够正常运行,不过是按照系统即超类的测量方式进行的app

对于非ViewGroup的view,经过调用上面默认的onMeasure就能够实现view的测量,也能够重载onMeasure并调用setMeasuredDimension来设置任意大小的布局,但建议通常不这样作,后面会分析缘由less

从上面的代码能够看出,默认的onMeasure只实现了调用setMeasuredDimension方法,该方法对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值,measure的主要目的就是对View树中的每一个View的mMeasuredWidth和mMeasuredHeight进行赋值,因此一旦这两个变量被赋值意味着该View的测量工做结束异步

默认实现中参数是经过调用getDefaultSize方法来传递的,接下来看一下该方法的源码:ide

 1     public static int getDefaultSize(int size, int measureSpec) {
 2         int result = size;
 3         //经过MeasureSpec解析获取mode与size
 4         int specMode = MeasureSpec.getMode(measureSpec);
 5         int specSize = MeasureSpec.getSize(measureSpec);
 6 
 7         switch (specMode) {
 8         case MeasureSpec.UNSPECIFIED:
 9             result = size;
10             break;
11         case MeasureSpec.AT_MOST:
12         case MeasureSpec.EXACTLY:
13             result = specSize;
14             break;
15         }
16         return result;
17     }

能够看到若是specMode等于AT_MOST或EXACTLY就返回specSize,这是系统默认的格式函数

回过头继续看上面onMeasure方法,其中getDefaultSize参数的widthMeasureSpec和heightMeasureSpec都是由父View传递进来的。getSuggestedMinimumWidth与getSuggestedMinimumHeight都是View的方法,具体以下:源码分析

 1  /**
 2      * Returns the suggested minimum width that the view should use. This
 3      * returns the maximum of the view's minimum width)
 4      * and the background's minimum width
 5      *  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
 6      * <p>
 7      * When being used in {@link #onMeasure(int, int)}, the caller should still
 8      * ensure the returned width is within the requirements of the parent.
 9      *
10      * @return The suggested minimum width of the view.
11      */
12     protected int getSuggestedMinimumWidth() {
13         return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
14     }
15 
16 
17 protected int getSuggestedMinimumHeight() {
18         return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
19 
20     }

能够看到:建议的最小宽度和高度都是由View的Background尺寸与经过设置View的miniXXX属性共同决定的

到这里就完成了view的measure过程

 对于一个界面可能包括不少次measure,由于对于一个布局而言,通常会包括不少子view,每一个子视图都须要measure,根据view的继承结构能够知道能够嵌套的view都是继承于ViewGroup的,故而在ViewGroup中定义了不少方法:measureChildren, measureChild, measureChildWithMargins,来对子视图进行测量,measureChildren内部实质只是循环调用measureChild,measureChild和measureChildWithMargins的区别就是是否把margin和padding也做为子视图的大小

下面首先来看一下measureChildren的源码:

 1 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
 2     final int size = mChildrenCount;
 3     final View[] children = mChildren;
 4     for (int i = 0; i < size; ++i) {
 5         final View child = children[i];
 6         if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
 7  measureChild(child, widthMeasureSpec, heightMeasureSpec);
 8         }
 9     }
10 }

能够看到循环遍历子视图,而后第7行调用measureChild方法,和上面讲的一致,接下来先来看一下measureChild的源码:

1 protected void measureChild(View child, int parentWidthMeasureSpec,
2         int parentHeightMeasureSpec) {
3     final LayoutParams lp = child.getLayoutParams();
4     final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
5             mPaddingLeft + mPaddingRight, lp.width);
6     final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
7             mPaddingTop + mPaddingBottom, lp.height);
8  child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
9 }

能够看到,在第4行和第6行分别调用了getChildMeasureSpec()方法来去计算子视图的MeasureSpec,计算的依据就是布局文件中定义的MATCH_PARENT、WRAP_CONTENT等值,这个方法的内部细节就再也不贴出;而后在第8行调用子视图的measure方法,并把计算出的MeasureSpec传递进去,这样就和前面分析单独view的测量过程是同样的了,至此就能够明白整个测量过程了,接着再来看一下measureChilldWithMargins源码:

 1  /**
 2      * Ask one of the children of this view to measure itself, taking into
 3      * account both the MeasureSpec requirements for this view and its padding
 4      * and margins. The child must have MarginLayoutParams The heavy lifting is
 5      * done in getChildMeasureSpec.
 6      *
 7      * @param child The child to measure
 8      * @param parentWidthMeasureSpec The width requirements for this view
 9      * @param widthUsed Extra space that has been used up by the parent
10      *        horizontally (possibly by other children of the parent)
11      * @param parentHeightMeasureSpec The height requirements for this view
12      * @param heightUsed Extra space that has been used up by the parent
13      *        vertically (possibly by other children of the parent)
14      */
15     protected void measureChildWithMargins(View child,
16             int parentWidthMeasureSpec, int widthUsed,
17             int parentHeightMeasureSpec, int heightUsed) {
18         //获取子视图的LayoutParams
19         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
20         //调整MeasureSpec
21         //经过这两个参数以及子视图自己的LayoutParams来共同决定子视图的测量规格
22         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
23                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin 24                         + widthUsed, lp.width);
25         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
26                 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
27                         + heightUsed, lp.height);
28         //调运子View的measure方法,子View的measure中会回调子View的onMeasure方法
29         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
30     }

能够看出和上面的measureChild方法相似,惟一不一样的就是加上了左右边距margin,刚才前面没有对getChildMeasureSpec方法源码进行分析,这里为了全面理解,仍是来查看一下源码:

 1   public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
 2         //获取当前Parent View的Mode和Size
 3         int specMode = MeasureSpec.getMode(spec);
 4         int specSize = MeasureSpec.getSize(spec);
 5         //获取Parent size与padding差值(也就是Parent剩余大小),若差值小于0直接返回0
 6         int size = Math.max(0, specSize - padding);
 7         //定义返回值存储变量
 8         int resultSize = 0;
 9         int resultMode = 0;
10         //依据当前Parent的Mode进行switch分支逻辑
11         switch (specMode) {
12         // Parent has imposed an exact size on us
13         //默认Root View的Mode就是EXACTLY
14         case MeasureSpec.EXACTLY:
15             if (childDimension >= 0) {
16                 //若是child的layout_width属性在xml或者java中给予具体大于等于0的数值
17                 //设置child的size为真实layout_width属性值,mode为EXACTLY
18                 resultSize = childDimension;
19                 resultMode = MeasureSpec.EXACTLY;
20             } else if (childDimension == LayoutParams.MATCH_PARENT) {
21                 //若是child的layout_width属性在xml或者java中给予MATCH_PARENT
22                 // Child wants to be our size. So be it.
23                 //设置child的size为size,mode为EXACTLY
24                 resultSize = size;
25                 resultMode = MeasureSpec.EXACTLY;
26             } else if (childDimension == LayoutParams.WRAP_CONTENT) {
27                 //若是child的layout_width属性在xml或者java中给予WRAP_CONTENT
28                 //设置child的size为size,mode为AT_MOST
29                 // Child wants to determine its own size. It can't be
30                 // bigger than us.
31                 resultSize = size;
32                 resultMode = MeasureSpec.AT_MOST;
33             }
34             break;
35         ......
36         //其余Mode分支相似
37         }
38         //将mode与size经过MeasureSpec方法整合为32位整数返回
39         return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
40     }

能够看见,getChildMeasureSpec的逻辑是经过其父View提供的MeasureSpec参数获得specMode和specSize,而后根据计算出来的specMode以及子View的childDimension(layout_width或layout_height)来计算自身的measureSpec,若是其自己包含子视图,则计算出来的measureSpec将做为调用其子视图measure函数的参数,同时也做为自身调用setMeasuredDimension的参数,若是其不包含子视图则默认状况下最终会调用onMeasure的默认实现,并最终调用到setMeasuredDimension。

因此能够看见onMeasure的参数其实就是这么计算出来的。同时从上面的分析能够看出来,最终决定View的measure大小是View的setMeasuredDimension方法,因此咱们能够经过setMeasuredDimension设定死值来设置View的mMeasuredWidth和mMeasuredHeight的大小,可是一个好的自定义View应该会根据子视图的measureSpec来设置mMeasuredWidth和mMeasuredHeight的大小,这样的灵活性更大,因此这也就是上面分析onMeasure时说View的onMeasure最好不要重写死值的缘由

能够看见当经过setMeasuredDimension方法最终设置完成View的measure以后View的mMeasuredWidth和mMeasuredHeight成员才会有具体的数值,因此若是咱们自定义的View或者使用现成的View想经过getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程以后被调用才能返回有效值

 

经过上面的分析,其实view的measure过程就是从顶层向下依次测量子视图的过程,即调用子视图的measure方法(measure又会回调onMeasure方法)。

核心点:

  • MeasureSpec(View的内部类)测量规格为int型,值由高16位规格模式specMode和低16位具体尺寸specSize组成。其中specMode只有三种值:
MeasureSpec.EXACTLY //肯定模式,父View但愿子View的大小是肯定的,由specSize决定; MeasureSpec.AT_MOST //最多模式,父View但愿子View的大小最可能是specSize指定的值; MeasureSpec.UNSPECIFIED //未指定模式,父View彻底依据子View的设计值来决定; 
  • View的measure方法是final的,不容许重载,View子类只能重载onMeasure来完成本身的测量逻辑。

  • 最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法肯定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。

  • ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算。

  • 只要是ViewGroup的子类就必需要求LayoutParams继承子MarginLayoutParams,不然没法使用layout_margin参数。

  • View的布局大小由父View和子View共同决定。

  • 使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程以后被调用才能返回有效值。

由上综述即分析完了整个测量过程

 接下来咱们来分析layout的过程:

首先大体说一下layout的执行过程:子视图的具体位置是相对于父视图的。View的onLayout是一个空方法,而ViewGroup的onLayout是一个抽象方法,因此若是自定义的View要继承于ViewGroup就必须重写该方法

由最开始的ViewRootImpl的performTraversals方法中能够看出在measure执行完毕后就会执行layout:

mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());

 

这里先来看一下layout的源码:

 1 public void layout(int l, int t, int r, int b) {
 2     int oldL = mLeft;
 3     int oldT = mTop;
 4     int oldB = mBottom;
 5     int oldR = mRight;
 6     boolean changed = setFrame(l, t, r, b);
 7     if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
 8         if (ViewDebug.TRACE_HIERARCHY) {
 9             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
10         }
11  onLayout(changed, l, t, r, b); 12         mPrivateFlags &= ~LAYOUT_REQUIRED;
13         if (mOnLayoutChangeListeners != null) {
14             ArrayList<OnLayoutChangeListener> listenersCopy =
15                     (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
16             int numListeners = listenersCopy.size();
17             for (int i = 0; i < numListeners; ++i) {
18                 listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
19             }
20         }
21     }
22     mPrivateFlags &= ~FORCE_LAYOUT;
23 }

第6行调用setFrame方法,传入新的view坐标参数,返回一个boolean值,用来决定是否改变了,若是改变了就须要从新布局,第11行调用onLayout,到这里你会发现和上面分析measure的过程很相似,也是会回调,接着咱们就来看一下onLayout的源码,结果会发现方法实现为空:由于onLayout()过程是为了肯定视图在布局中所在的位置,而这个操做应该是由布局来完成的,即父视图决定子视图的显示位置

那咱们再来看一下ViewGroup的onLayout方法实现,你会发现是个抽象方法

protected abstract void onLayout(boolean changed, int l, int t, int r, int b); 

因此ViewGroup的全部子视图都必须重写该方法,事实上,LinearLayout和RelativeLayout都是重写了这个方法,而后在内部按照各自的规则对子视图进行布局的

那既然这样,咱们要知道onLayout的编写实现就须要来参考一下系统控件的实现了,这里以LinearLayout为例:

 1 @RemoteView
 2 public class LinearLayout extends ViewGroup {
 3 
 4 @Override
 5     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 6         if (mOrientation == VERTICAL) {
 7             layoutVertical(l, t, r, b);
 8         } else {
 9             layoutHorizontal(l, t, r, b);
10         }
11     }
12     .....
13 }

能够看到对于LinearLayout的onLayout方法内部会根据布局方向来调用不一样的方法,这也正说明了咱们再使用LinearLayout时设置方向了

这里咱们就来分析Vertical的状况吧:

 1 /**
 2      * Position the children during a layout pass if the orientation of this
 3      * LinearLayout is set to {@link #VERTICAL}.
 4      *
 5      * @see #getOrientation()
 6      * @see #setOrientation(int)
 7      * @see #onLayout(boolean, int, int, int, int)
 8      * @param left
 9      * @param top
10      * @param right
11      * @param bottom
12      */
13     void layoutVertical(int left, int top, int right, int bottom) {
14         final int paddingLeft = mPaddingLeft;
15 
16         int childTop;
17         int childLeft;
18         
19         // Where right end of child should go   计算父窗口推荐的子View宽度
20         final int width = right - left;
21         int childRight = width - mPaddingRight;  //计算父窗口推荐的子View右侧位置
22         
23         // Space available for child    child可以使用空间大小
24         int childSpace = width - paddingLeft - mPaddingRight;
25         // 经过ViewGroup的getChildCount方法获取ViewGroup的子View个数
26         final int count = getVirtualChildCount();
27      // 获取Gravity属性设置
28         final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
29         final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
30         //依据majorGravity计算childTop的位置值
31         switch (majorGravity) {
32            case Gravity.BOTTOM:
33                // mTotalLength contains the padding already
34                childTop = mPaddingTop + bottom - top - mTotalLength;
35                break;
36 
37                // mTotalLength contains the padding already
38            case Gravity.CENTER_VERTICAL:
39                childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
40                break;
41 
42            case Gravity.TOP:
43            default:
44                childTop = mPaddingTop;
45                break;
46         }
47         //开始遍历整个ViewGroup
48         for (int i = 0; i < count; i++) {
49             final View child = getVirtualChildAt(i);
50             if (child == null) {
51                 childTop += measureNullChild(i);
52             } else if (child.getVisibility() != GONE) {//LinearLayout中其子视图显示的宽和高由measure过程来决定的
53                 final int childWidth = child.getMeasuredWidth();
54                 final int childHeight = child.getMeasuredHeight();
55                 //获取子View的LayoutParams
56                 final LinearLayout.LayoutParams lp =
57                         (LinearLayout.LayoutParams) child.getLayoutParams();
58                 
59                 int gravity = lp.gravity;
60                 if (gravity < 0) {
61                     gravity = minorGravity;
62                 }
63                 final int layoutDirection = getLayoutDirection();
64                 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
65                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {  //依据不一样的absoluteGravity计算childLeft位置
66                     case Gravity.CENTER_HORIZONTAL:
67                         childLeft = paddingLeft + ((childSpace - childWidth) / 2)
68                                 + lp.leftMargin - lp.rightMargin;
69                         break;
70 
71                     case Gravity.RIGHT:
72                         childLeft = childRight - childWidth - lp.rightMargin;
73                         break;
74 
75                     case Gravity.LEFT:
76                     default:
77                         childLeft = paddingLeft + lp.leftMargin;
78                         break;
79                 }
80 
81                 if (hasDividerBeforeChildAt(i)) {
82                     childTop += mDividerHeight;
83                 }
84 
85                 childTop += lp.topMargin;////经过垂直排列计算调用child的layout设置child的位置
86                 setChildFrame(child, childLeft, childTop + getLocationOffset(child),
87                         childWidth, childHeight);
88                 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
89 
90                 i += getChildrenSkipCount(child, i);
91             }
92         }
93     }

从上面分析的ViewGroup子类LinearLayout的onLayout实现代码能够看出,通常状况下layout过程会参考measure过程当中计算获得的mMeasuredWidth和mMeasuredHeight来安排子View在父View中显示的位置,但这不是必须的,measure过程获得的结果可能彻底没有实际用处,特别是对于一些自定义的ViewGroup,其子View的个数、位置和大小都是固定的,这时候咱们能够忽略整个measure过程,只在layout函数中传入的4个参数来安排每一个子View的具体位置。

到这里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对方法之间的区别(上面分析measure过程已经说过getMeasuredWidth()、getMeasuredHeight()必须在onMeasure以后使用才有效)。能够看出来getWidth()与getHeight()方法必须在layout(int l, int t, int r, int b)执行以后才有效。那咱们看下View源码中这些方法的实现吧,以下:

 1 public final int getMeasuredWidth() {
 2         return mMeasuredWidth & MEASURED_SIZE_MASK;
 3     }
 4 
 5     public final int getMeasuredHeight() {
 6         return mMeasuredHeight & MEASURED_SIZE_MASK;
 7     }
 8 
 9     public final int getWidth() {
10         return mRight - mLeft;
11     }
12 
13     public final int getHeight() {
14         return mBottom - mTop;
15     }
16 
17     public final int getLeft() {
18         return mLeft;
19     }
20 
21     public final int getRight() {
22         return mRight;
23     }
24 
25     public final int getTop() {
26         return mTop;
27     }
28 
29     public final int getBottom() {
30         return mBottom;
31     }

layout总结:

整个layout过程比较容易理解,从上面分析能够看出layout也是从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所获得的布局大小和布局参数,将子View放在合适的位置上。具体layout核心主要有如下几点:

  • View.layout方法可被重载,ViewGroup.layout为final的不可重载,ViewGroup.onLayout为abstract的,子类必须重载实现本身的位置逻辑。

  • measure操做完成后获得的是对每一个View经测量过的measuredWidth和measuredHeight,layout操做完成以后获得的是对每一个View进行位置分配后的mLeft、mTop、mRight、mBottom,这些值都是相对于父View来讲的。

  • 凡是layout_XXX的布局属性基本都针对的是包含子View的ViewGroup的,当对一个没有父容器的View设置相关layout_XXX属性是没有任何意义的(前面《Android应用setContentView与LayoutInflater加载解析机制源码分析》也有提到过)。

  • 使用View的getWidth()和getHeight()方法来获取View测量的宽高,必须保证这两个方法在onLayout流程以后被调用才能返回有效值

那分析了layout的具体原理,那接下来附上一个简单的实例来看一下实际应用中如何使用onLayout:这一块实例是引用郭大神的博文中的

 1 public class SimpleLayout extends ViewGroup {
 2 
 3     public SimpleLayout(Context context, AttributeSet attrs) {
 4         super(context, attrs);
 5     }
 6 
 7     @Override
 8     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 9         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
10         if (getChildCount() > 0) {
11             View childView = getChildAt(0);
12             measureChild(childView, widthMeasureSpec, heightMeasureSpec);
13         }
14     }
15 
16  @Override 17     protected void onLayout(boolean changed, int l, int t, int r, int b) { 18         if (getChildCount() > 0) { 19             View childView = getChildAt(0); 20             childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight()); 21  } 22  } 23 
24 }

上面的例子只包含一个子view,onLayout的实现也很简单,就是判断子视图个数不为空,而后获取子视图,根据measure测量的宽高直接调用layout方法便可

具体显示的位置能够自由改变layout中的参数

最后来分析draw的详细过程:

由最开始的ViewRootImpl的performTraversals()方法能够看出在执行完measure和layout以后就要执行draw方法了(ViewRoot中的代码会继续执行并建立出一个Canvas对象,而后调用View的draw()方法来执行具体的绘制工做),由前面measure和layout的比较咱们知道二者总体执行过程很类似,都会在递归执行measure或layout 之中回调相应的onMeasure或onLayout方法,因而到这里咱们能够猜测draw是否是也相似呢?实际上draw的过程要相对复杂一些,除了会调用onDraw方法以外,还有一些其余的方法须要调用,接下来咱们就源码来具体分析一下:
这里咱们首先来画一下整个view树结构的遍历过程:

 接下来先来看一下draw的源码:代码较长

  1    /**
  2      * Manually render this view (and all of its children) to the given Canvas.
  3      * The view must have already done a full layout before this function is
  4      * called.  When implementing a view, implement
  5      * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
  6      * If you do need to override this method, call the superclass version.
  7      *
  8      * @param canvas The Canvas to which the View is rendered.
  9      */
 10     @CallSuper
 11     public void draw(Canvas canvas) {
 12         final int privateFlags = mPrivateFlags;
 13         final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
 14                 (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
 15         mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
 16 
 17         /*
 18          * Draw traversal performs several drawing steps which must be executed
 19          * in the appropriate order:
 20          *
 21          *      1. Draw the background
 22          *      2. If necessary, save the canvas' layers to prepare for fading
 23          *      3. Draw view's content
 24          *      4. Draw children
 25          *      5. If necessary, draw the fading edges and restore layers
 26          *      6. Draw decorations (scrollbars for instance)
 27          */
 28 
 29         // Step 1, draw the background, if needed
 30         int saveCount;
 31 
 32         if (!dirtyOpaque) {
 33             drawBackground(canvas);
 34         }
 35 
 36         // skip step 2 & 5 if possible (common case)
 37         final int viewFlags = mViewFlags;
 38         boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
 39         boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
 40         if (!verticalEdges && !horizontalEdges) {
 41             // Step 3, draw the content
 42             if (!dirtyOpaque) onDraw(canvas);
 43 
 44             // Step 4, draw the children
 45             dispatchDraw(canvas);
 46 
 47             // Overlay is part of the content and draws beneath Foreground
 48             if (mOverlay != null && !mOverlay.isEmpty()) {
 49                 mOverlay.getOverlayView().dispatchDraw(canvas);
 50             }
 51 
 52             // Step 6, draw decorations (foreground, scrollbars)
 53             onDrawForeground(canvas);
 54 
 55             // we're done...
 56             return;
 57         }
 58 
 59         /*
 60          * Here we do the full fledged routine...
 61          * (this is an uncommon case where speed matters less,
 62          * this is why we repeat some of the tests that have been
 63          * done above)
 64          */
 65 
 66         boolean drawTop = false;
 67         boolean drawBottom = false;
 68         boolean drawLeft = false;
 69         boolean drawRight = false;
 70 
 71         float topFadeStrength = 0.0f;
 72         float bottomFadeStrength = 0.0f;
 73         float leftFadeStrength = 0.0f;
 74         float rightFadeStrength = 0.0f;
 75 
 76         // Step 2, save the canvas' layers
 77         int paddingLeft = mPaddingLeft;
 78 
 79         final boolean offsetRequired = isPaddingOffsetRequired();
 80         if (offsetRequired) {
 81             paddingLeft += getLeftPaddingOffset();
 82         }
 83 
 84         int left = mScrollX + paddingLeft;
 85         int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
 86         int top = mScrollY + getFadeTop(offsetRequired);
 87         int bottom = top + getFadeHeight(offsetRequired);
 88 
 89         if (offsetRequired) {
 90             right += getRightPaddingOffset();
 91             bottom += getBottomPaddingOffset();
 92         }
 93 
 94         final ScrollabilityCache scrollabilityCache = mScrollCache;
 95         final float fadeHeight = scrollabilityCache.fadingEdgeLength;
 96         int length = (int) fadeHeight;
 97 
 98         // clip the fade length if top and bottom fades overlap
 99         // overlapping fades produce odd-looking artifacts
100         if (verticalEdges && (top + length > bottom - length)) {
101             length = (bottom - top) / 2;
102         }
103 
104         // also clip horizontal fades if necessary
105         if (horizontalEdges && (left + length > right - length)) {
106             length = (right - left) / 2;
107         }
108 
109         if (verticalEdges) {
110             topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
111             drawTop = topFadeStrength * fadeHeight > 1.0f;
112             bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
113             drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
114         }
115 
116         if (horizontalEdges) {
117             leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
118             drawLeft = leftFadeStrength * fadeHeight > 1.0f;
119             rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
120             drawRight = rightFadeStrength * fadeHeight > 1.0f;
121         }
122 
123         saveCount = canvas.getSaveCount();
124 
125         int solidColor = getSolidColor();
126         if (solidColor == 0) {
127             final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
128 
129             if (drawTop) {
130                 canvas.saveLayer(left, top, right, top + length, null, flags);
131             }
132 
133             if (drawBottom) {
134                 canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
135             }
136 
137             if (drawLeft) {
138                 canvas.saveLayer(left, top, left + length, bottom, null, flags);
139             }
140 
141             if (drawRight) {
142                 canvas.saveLayer(right - length, top, right, bottom, null, flags);
143             }
144         } else {
145             scrollabilityCache.setFadeColor(solidColor);
146         }
147 
148         // Step 3, draw the content
149         if (!dirtyOpaque) onDraw(canvas);
150 
151         // Step 4, draw the children
152         dispatchDraw(canvas);
153 
154         // Step 5, draw the fade effect and restore layers
155         final Paint p = scrollabilityCache.paint;
156         final Matrix matrix = scrollabilityCache.matrix;
157         final Shader fade = scrollabilityCache.shader;
158 
159         if (drawTop) {
160             matrix.setScale(1, fadeHeight * topFadeStrength);
161             matrix.postTranslate(left, top);
162             fade.setLocalMatrix(matrix);
163             p.setShader(fade);
164             canvas.drawRect(left, top, right, top + length, p);
165         }
166 
167         if (drawBottom) {
168             matrix.setScale(1, fadeHeight * bottomFadeStrength);
169             matrix.postRotate(180);
170             matrix.postTranslate(left, bottom);
171             fade.setLocalMatrix(matrix);
172             p.setShader(fade);
173             canvas.drawRect(left, bottom - length, right, bottom, p);
174         }
175 
176         if (drawLeft) {
177             matrix.setScale(1, fadeHeight * leftFadeStrength);
178             matrix.postRotate(-90);
179             matrix.postTranslate(left, top);
180             fade.setLocalMatrix(matrix);
181             p.setShader(fade);
182             canvas.drawRect(left, top, left + length, bottom, p);
183         }
184 
185         if (drawRight) {
186             matrix.setScale(1, fadeHeight * rightFadeStrength);
187             matrix.postRotate(90);
188             matrix.postTranslate(right, top);
189             fade.setLocalMatrix(matrix);
190             p.setShader(fade);
191             canvas.drawRect(right - length, top, right, bottom, p);
192         }
193 
194         canvas.restoreToCount(saveCount);
195 
196         // Overlay is part of the content and draws beneath Foreground
197         if (mOverlay != null && !mOverlay.isEmpty()) {
198             mOverlay.getOverlayView().dispatchDraw(canvas);
199         }
200 
201         // Step 6, draw decorations (foreground, scrollbars)
202         onDrawForeground(canvas);
203     }
View Code

 代码注释中已经详细描述了绘制的步骤:

/*
 18          * Draw traversal performs several drawing steps which must be executed
 19          * in the appropriate order:
 20          *
 21          *      1. Draw the background
 22          *      2. If necessary, save the canvas' layers to prepare for fading
 23          *      3. Draw view's content
 24          *      4. Draw children
 25          *      5. If necessary, draw the fading edges and restore layers
 26          *      6. Draw decorations (scrollbars for instance)
 27          */

源码注释中提到步骤2和5能够跳过,那咱们接下来主要分析剩下4步:

第一步:绘制背景

主要逻辑在方法drawBackground(canvas);中,分析一下源码:

 1  /**
 2      * Draws the background onto the specified canvas.
 3      *
 4      * @param canvas Canvas on which to draw the background
 5      */
 6     private void drawBackground(Canvas canvas) {
      //获取xml中经过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable
7 final Drawable background = mBackground; 8 if (background == null) { 9 return; 10 } 11 //根据layout过程肯定的View位置来设置背景的绘制区域 12 setBackgroundBounds(); 13 14 // Attempt to use a display list if requested. 15 if (canvas.isHardwareAccelerated() && mAttachInfo != null 16 && mAttachInfo.mHardwareRenderer != null) { 17 mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode); 18 19 final RenderNode renderNode = mBackgroundRenderNode; 20 if (renderNode != null && renderNode.isValid()) { 21 setBackgroundRenderNodeProperties(renderNode); 22 ((DisplayListCanvas) canvas).drawRenderNode(renderNode); 23 return; 24 } 25 } 26 27 final int scrollX = mScrollX; 28 final int scrollY = mScrollY; 29 if ((scrollX | scrollY) == 0) { 30 background.draw(canvas); 31 } else { 32 canvas.translate(scrollX, scrollY); 33 background.draw(canvas);//调用Drawable的draw()方法来完成背景的绘制工做 34 canvas.translate(-scrollX, -scrollY); 35 } 36 }

简要分析:

这里会先获得一个mBGDrawable对象,而后根据layout过程肯定的视图位置来设置背景的绘制区域,以后再调用Drawable的draw()方法来完成背景的绘制工做。那么这个mBGDrawable对象是从哪里来的呢?其实就是在XML中经过android:background属性设置的图片或颜色。固然你也能够在代码中经过setBackgroundColor()、setBackgroundResource()等方法进行赋值

二、对View内容的绘制

从代码中看出就是调用onDraw(canvas)

这里就是一个空方法,须要子类去实现,由于每一个子视图的所要绘制的内容都不同

三、对当前View的全部子view进行绘制,若是没有则不绘制

dispatchDraw(canvas);

这里是个空方法,对单个view而言是没有子view的,也就没有实现,这里能够看一下ViewGroup中该方法的实现:
因为代码较长,这里只抽取主要的逻辑代码:

 1 @Override
 2     protected void dispatchDraw(Canvas canvas) {
 3         ......
 4         final int childrenCount = mChildrenCount;
 5         final View[] children = mChildren;
 6         ......
 7         for (int i = 0; i < childrenCount; i++) {
 8             ......
 9             if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
10                 more |= drawChild(canvas, child, drawingTime);
11             }
12         }
13         ......
14         // Draw any disappearing views that have animations
15         if (mDisappearingChildren != null) {
16             ......
17             for (int i = disappearingCount; i >= 0; i--) {
18                 ......
19                 more |= drawChild(canvas, child, drawingTime);
20             }
21         }
22         ......
23     }

能够看到ViewGroup确实重写了dispatchDraw方法,从第17行能够看出会循环遍历子view,而后调用drawChild方法绘制子view,而drawChild方法内部其实就是调用draw方法,至此咱们搞清楚了ViewGroup已经为咱们重写了dispatchDraw的功能实现,因此通常不用重写该方法,可是能够重载父类方法实现具体的功能

到这里咱们能够明白,若是咱们要让ViewGroup绘制的话,就能够在两个地方进行:一、将绘制逻辑添加到重写的dispatchDraw方法中,可是在该方法中首先要调用超类的该方法,即super.dispatchDraw(canvas);而后才能执行其余的绘制逻辑

二、直接重写onDraw方法,将绘制逻辑添加在该方法内

对于方法2,针对ViewGroup有一点要注意:

The second is using the same onDraw() callback as we saw before with View. Anything we draw here will be drawn before the child views, and thus will show up underneath them. 首先绘制,位于子view的下面 This can be helpful for drawing any type of dynamic backgrounds or selector states.适合绘制任何类型的动态背景和选择器状态

 

If you wish to put code in the onDraw() of a ViewGroup, you must also remember to enable drawing callbacks with setWillNotDraw(false). Otherwise your onDraw() method will never be triggered. This is because ViewGroups have self-drawing disabled by default.

调用onDraw以前须要调用setWillNotDraw(false)方法使能drawing回调,由于ViewGroup默认是禁止自绘的

 

四、而后就是第6步,也就是最后一步,对视图的滚动条进行绘制。咱们要明白其实全部的view都是有滚动条的,只是通常状况咱们都隐藏掉了

这一块不是重点,代码也比较多,就不作过多分析了

综上,其实这里的重点就是上面的第2点,对内容的绘制,也即调用onDraw。这里面会有不少的绘制逻辑,绘制的方式主要是借助Canvas这个类,它会做为参数传入到onDraw()方法中,供给每一个视图使用

这里咱们先总结一下draw的整个过程,而后再着重来研究一下onDraw的实现:
这里引用工匠若水的博文:

  • 若是该View是一个ViewGroup,则须要递归绘制其所包含的全部子View。

  • View默认不会绘制任何内容,真正的绘制都须要本身在子类中实现。

  • View的绘制是借助onDraw方法传入的Canvas类来进行的

  • 区分View动画和ViewGroup布局动画,前者指的是View自身的动画,能够经过setAnimation添加,后者是专门针对ViewGroup显示内部子视图时设置的动画,能够在xml布局文件中对ViewGroup设置layoutAnimation属性(譬如对LinearLayout设置子View在显示时出现逐行、随机、下等显示等不一样动画效果)

  • 在获取画布剪切区(每一个View的draw中传入的Canvas)时会自动处理掉padding,子View获取Canvas不用关注这些逻辑,只用关心如何绘制便可。

  • 默认状况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,可是你也能够重载ViewGroup.getChildDrawingOrder()方法提供不一样顺序。

对于onDraw的简单实用,这里贴上一个样例:

 1 public class MyView extends View {
 2 
 3     private Paint mPaint;
 4 
 5     public MyView(Context context, AttributeSet attrs) {
 6         super(context, attrs);
 7         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 8     }
 9 
10     @Override
11     protected void onDraw(Canvas canvas) {
12         mPaint.setColor(Color.YELLOW);
13         canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
14         mPaint.setColor(Color.BLUE);
15         mPaint.setTextSize(20);
16         String text = "Hello View";
17         canvas.drawText(text, 0, getHeight() / 2, mPaint);
18     }
19 }

至此,咱们便将测量,布局,绘制三个大的自定义view的步骤详细分析了一遍

 下面来分析一下invalidate方法:注:该方法只能在UI线程中调用
View的invalidate方法最终都会归于调用invalidateInternal方法,这里贴上源码:

 1 void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
 2             boolean fullInvalidate) {
 3         ......
 4             // Propagate the damage rectangle to the parent view.
 5             final AttachInfo ai = mAttachInfo;
 6             final ViewParent p = mParent;
 7             if (p != null && ai != null && l < r && t < b) {
 8                 final Rect damage = ai.mTmpInvalRect;
 9                 //设置刷新区域
10                 damage.set(l, t, r, b);
11                 //传递调运Parent ViewGroup的invalidateChild方法
12                 p.invalidateChild(this, damage);
13             }
14             ......
15     }

第12行会调用父类的invalidateChild方法,View的invalidate(invalidateInternal)方法实质是将要刷新区域直接传递给了父ViewGroup的invalidateChild方法,在invalidate中,调用父View的invalidateChild,这是一个从当前向上级父View回溯的过程,每一层的父View都将本身的显示区域与传入的刷新Rect作交集

这里查看一下invalidateChild的源码:

 1 public final void invalidateChild(View child, final Rect dirty) {
 2         ViewParent parent = this;
 3         final AttachInfo attachInfo = mAttachInfo;
 4         ......
 5         do {
 6             ......
 7             //循环层层上级调运,直到ViewRootImpl会返回null
 8             parent = parent.invalidateChildInParent(location, dirty);  9             ......
10         } while (parent != null);
11     }

这个过程最后传递到ViewRootImpl的invalidateChildInParent方法结束,因此咱们看下ViewRootImpl的invalidateChildInParent方法

1    @Override
2     public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
3         ......
4         //View调运invalidate最终层层上传到ViewRootImpl后最终触发了该方法
5  scheduleTraversals(); 6         ......
7         return null;
8     }

这个ViewRootImpl类的invalidateChildInParent方法直接返回了null,也就是上面ViewGroup中说的,层层上级传递到ViewRootImpl的invalidateChildInParent方法结束了那个do while循环。看见这里调运的scheduleTraversals这个方法吗?scheduleTraversals会经过Handler的Runnable发送一个异步消息,调运doTraversal方法,而后最终调用performTraversals()执行重绘。开头背景知识介绍说过的,performTraversals就是整个View数开始绘制的起始调运地方,因此说View调运invalidate方法的实质是层层上传到父级,直到传递到ViewRootImpl后触发了scheduleTraversals方法,而后整个View树开始从新按照上面分析的View绘制流程进行重绘任务

到此View的invalidate方法原理就分析完成了

对于非U线程须要使用postInvalidate方法:

这里不作具体分析了,只要明白一点就好,最后该方法的调用最终会回到UI线程中调用invalidate方法

接下来总结一下:这里也是参考工匠若水的博文

invalidate系列方法请求重绘View树(也就是draw方法),若是View大小没有发生变化就不会调用layout过程,而且只绘制那些“须要重绘的”View,也就是哪一个View(View只绘制该View,ViewGroup绘制整个ViewGroup)请求invalidate系列方法,就绘制该View。

常见的引发invalidate方法操做的缘由主要有:

  • 直接调用invalidate方法.请求从新draw,但只会绘制调用者自己。
  • 触发setSelection方法。请求从新draw,但只会绘制调用者自己。
  • 触发setVisibility方法。 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE\VISIBLE 转换为GONE状态时会间接调用requestLayout和invalidate方法,同时因为View树大小发生了变化,因此会请求measure过程以及draw过程,一样只绘制须要“从新绘制”的视图。
  • 触发setEnabled方法。请求从新draw,但不会从新绘制任何View包括该调用者自己。
  • 触发requestFocus方法。请求View树的draw过程,只绘制“须要重绘”的View。
相关文章
相关标签/搜索