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