MyView(Context context) {//java new view才调用次构造 (context); init(, ); } MyView(Context context, AttributeSet attrs) {//layout里xml不带style时被调用 (context, attrs); init(attrs, ); } MyView(Context context, AttributeSet attrs, defStyle) {//layout里xml带style时被调用 (context, attrs, defStyle); init(attrs, defStyle); }
如下为转载:
java
View的绘制流程分为三个阶段:
Measure--测量
Layout--放置视图位置
Draw--绘制
View树的绘制流程是在ViewRootImpl类的performTraversals()方法(这个方法巨长)开始的。
每一个控件的实际宽高都是由父视图和自身决定的,实际测量是在OnMeasure()方法中进行,因此在View的子类须要重写OnMeasure()方法。
/**
* <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)
*/
//final方法,子类不可重写
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
//回调onMeasure()方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
这个方法的两个参数都是父View传过来的,每一个参数由两部分组成,高16位表示MODE,底16位表示SIZE。
MODE有三种类型分别为:
MeasureSpec.EXACTLY:肯定大小
MeasureSpec.AT_MOST:最大大小
MeasureSpec.UNSPECIFIED:不肯定的
SIZE表示父View的大小,对于子View大小是由父View和子View共同决定的。
对于系统Window类的DecorView对象的Mode通常都为MeasureSpec.EXACTLY,而Size分别对应屏幕宽高。
下面为onMeasure()源码:
/**
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overriden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
*
* <p>
* <strong>CONTRACT:</strong> When overriding this method, you
* <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
* measured width and height of this view. Failure to do so will trigger an
* <code>IllegalStateException</code>, thrown by
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
* </p>
*
* <p>
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
* </p>
*
* <p>
* If this method is overridden, it is the subclass's responsibility to make
* sure the measured height and width are at least the view's minimum height
* and width ({@link #getSuggestedMinimumHeight()} and
* {@link #getSuggestedMinimumWidth()}).
* </p>
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
*
* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
* @see android.view.View.MeasureSpec#getSize(int)
*/
//View的onMeasure默认实现方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
子类应该重写这个方法,重写时必须调用setMeasuredDimension(int, int)方法来保存测量的宽度和高度。测量高度和宽度默认是背景的高度和宽度,最小也是视图的最小宽度和高度。获取最小宽度和高度的方法是:getSuggestedMinimumWidth()和getSuggestedMinimumHeight()。
setMeasuredDimension(int,int)方法调用完毕View的测量工做就会结束。
下面看看getDefaultSize(int,int)方法的实现:
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
//经过MeasureSpec解析获取mode与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;
}
specMode等于AT_MOST或EXACTLY就返回specSize,这就是系统默认的规格。
getSuggestedMinimumHeight()和getSuggestedMinimumWidth()方法实现以下:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
最小宽度和高度是由View的Background尺寸与经过设置View的minXXX属性共同决定的。
setMeasuredDimension方法最终设置完成View的measure以后View的mMeasuredWidth和mMeasuredHeight成员才会有具体的数值,因此若是咱们自定义的View或者使用现成的View想经过getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程以后被调用才能返回有效值。
Measure原理总结:
经过上面分析能够看出measure过程主要就是从顶层父View向子View递归调用view.measure方法(measure中又回调onMeasure方法)的过程。具体measure核心主要有以下几点:
一、MeasureSpec(View的内部类)测量规格为int型,值由高16位规格模式specMode和低16位具体尺寸specSize组成。
二、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的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程以后被调用才能返回有效值。
2、递归Layout源码分析:
ViewRootImpl的performTraversals中measure执行完成之后会接着执行mView.layout,具体以下:
private void performTraversals() {
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
}
ViewGroup的layout()方法:
@Override
public final void layout(int l, int t, int r, int b) {
......
super.layout(l, t, r, b);
......
}
实质调用的仍是View的layout()方法:
public void layout(int l, int t, int r, int b) {
......
//实质都是调用setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量
//判断View的位置是否发生过变化,以肯定有没有必要对当前的View进行从新layout
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//须要从新layout
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//回调onLayout
onLayout(changed, l, t, r, b);
......
}
......
}
判断View的位置是否发生过变化,以肯定有没有必要对当前的View进行从新layout()。
对比上面View的layout和ViewGroup的layout方法能够发现,View的layout方法是能够在子类重写的,而ViewGroup的layout是不能在子类重写的,言外之意就是说ViewGroup中只能经过重写onLayout方法。那咱们接下来看下ViewGroup的onLayout方法,以下:
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
ViewGroup中onLayout()方法是抽象方法,ViewGroup的子类必须重写这个方法,因此在自定义ViewGroup控件中,Onlayout配合onMeasure方法一块儿使用能够实现自定义View的复杂布局。自定义View首先要调用OnMeasure进行测量,而后调用onLayout方法动态获取子View和子View的测量大小,而后进行Layout布局。重载onLayout的目的就是安排其Children在父View的位置,重载onLayout的一般作法就是写一个for()循环调用每个字视图的layout(l,t,r,b)方法,传入不一样的参数来肯定每个子视图的在父视图中显示的位置,onLayout方法中的四个参数分别为父视图推荐的位置。
LinearLayout源码分析:
public class LinearLayout extends ViewGroup {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
}
分析VERTICAL模式下:
layoutVertical源码分析:
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
//计算父窗口推荐的子View宽度
final int width = right - left;
//计算父窗口推荐的子View右侧位置
int childRight = width - mPaddingRight;
// Space available for child
//child可以使用空间大小
int childSpace = width - paddingLeft - mPaddingRight;
//经过ViewGroup的getChildCount方法获取ViewGroup的子View个数
final int count = getVirtualChildCount();
//获取Gravity属性设置
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
//依据majorGravity计算childTop的位置值
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
//计算出顶端的坐标
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
//重点!!!开始遍历
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//LinearLayout中其子视图显示的宽和高由measure过程来决定的,所以measure过程的意义就是为layout过程提供视图显示范围的参考值
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//获取子View的LayoutParams
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
//依据不一样的absoluteGravity计算childLeft位置
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
//经过垂直排列计算调运child的layout设置child的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
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属性是没有任何意义的
四、使用View的getWidth()和getHeight()方法来获取View测量的宽高,必须保证这两个方法在onLayout流程以后被调用才能返回有效值。
四View绘制流程第三步:递归draw源码分析:
draw的过程也是在ViewRootImpl的PerformTraversales()内部调用,其调用顺序在Measure()和layout()以后,这里的myView对于Activity来讲就是phoneWindow.DecorView,ViewRootImpl中的代码会建立一个Canvas对象,而后调用View的draw()方法来执行具体的绘制工做。
draw()源码分析:
public void draw(Canvas canvas) {
......
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
......
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
......
// Step 2, save the canvas' layers
......
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
......
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
......
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
......
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
......
}
draw方法经过调运drawBackground(canvas);方法实现了背景绘制:
源码:
private void drawBackground(Canvas canvas) {
//获取xml中经过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable
final Drawable background = mBackground;
......
//根据layout过程肯定的View位置来设置背景的绘制区域
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
rebuildOutline();
}
......
//调用Drawable的draw()方法来完成背景的绘制工做
background.draw(canvas);
......
}
对view的内容进行绘制:此处条用了onDraw();
/**
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {
}
该方法须要子类去实现。
对当前的全部子View进行绘制,若是当前的view没有子view就不须要绘制 。
下面为View中的draw()方法中的dispatchdRraw()方法源码:
/**
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {
}
这个是空方法,若是view包含子类才须要重写该方法。
ViewGroup的dispatchDraw方法源码(这也就是说对当前View的全部子view进行绘制,若是当前的View没有子view就不须要绘制的缘由,由于若是是view调用该方法是空的,而ViewGroup才有实现)以下:
@Override
protected void dispatchDraw(Canvas canvas) {
......
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
......
for (int i = 0; i < childrenCount; i++) {
......
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
......
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
......
for (int i = disappearingCount; i >= 0; i--) {
......
more |= drawChild(canvas, child, drawingTime);
}
}
......
}
ViewGroup重写了View的DispatchDraw()方法,该方法内部会遍历每一个子View,而后调用drawChild()方法,咱们能够看下ViewGroup的drawChild()方法,以下:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
能够看见drawChild()方法调用了View的draw()方法。因此说ViewGroup类已经为咱们重写了dispatchDraw()的功能实现,咱们通常不须要重载该方法,但能够重载父类函数实现具体的功能。
6、对view的滚动条进行绘制:
能够看到view的onDrawScrollBars()方法,因此咱们看下View的onDrawScrollBars(canvas);方法,以下:
/**
* <p>Request the drawing of the horizontal and the vertical scrollbar. The
* scrollbars are painted only if they have been awakened first.</p>
*
* @param canvas the canvas on which to draw the scrollbars
*
* @see #awakenScrollBars(int)
*/
protected final void onDrawScrollBars(Canvas canvas) {
//绘制ScrollBars分析不是咱们这篇的重点,因此暂时不作分析
......
}
能够看见其实任何一个View都是有(水平垂直)滚动条的,只是通常状况下没让它显示而已。到此,View的draw绘制部分源码分析完毕,咱们接下来进行一些总结。
Draw原理总结:
一、若是该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()方法提供不一样顺序。android