一直以来我们都是在用Andriod自带的控件,比如TextView,ImageView等来构建我们的应用,有的时候并不能满足我们的需要,接下来就来研究一下如何自定义我们自己的控件,当然自己造新的*,自然先要搞懂系统自带的*的实现原理,下面将从源码的角度深入分析view的绘制流程,进而来分析一下如何自定义新的控件:
下面首先从源码的角度来分析一下view的绘制流程:
先看一下view树的结构图:
自定义控件(View的绘制流程源码解析)
自定义控件(View的绘制流程源码解析)
可以看到android中任何一个布局或控件都是直接或间接继承于view实现的,对于我们后面要自定义的控件自然也不例外,所以想要自定义控件首先必须得先搞明白android自带的view布局和控件到底是如何绘制并显示到屏幕上的。
1、既然要把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的绘制流程,下面就是绘制流程图:
自定义控件(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来实现自己的测量逻辑

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

下面看一下View的onMeasure源码:

 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触发的,子类必须重写以提供精确而有效的测量

当重写该方法时必须调用setMeasuredDimension(int, int)来存储这个view测量的宽和高,否则会报异常,调用超类的onMeasure方法可以正常运行,不过是按照系统即超类的测量方式进行的自定义控件(View的绘制流程源码解析)

对于非ViewGroup的view,通过调用上面默认的onMeasure就可以实现view的测量,也可以重载onMeasure并调用setMeasuredDimension来设置任意大小的布局,但建议一般不这样做,后面会分析原因

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

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

 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树结构的遍历过程:
自定义控件(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

分类:

技术点:

相关文章: