View簡介
View體系是整個Android中很大分量的一部分,也是最直覺的展現(使用者界面控件).View是整個View體系中所有控件的基類,其子類控件包括有ViewGroup、TextureView、SurfaceView、ImageView等.
安卓中的坐标系
說到View,不得不說一下Android的坐标系
1.螢幕坐标系
螢幕坐标系是以手機螢幕的左上角頂點為原點(0,0),向右為x軸正方向,向下為y軸正方向的坐标系

以下的兩個擷取坐标的方法就是基于螢幕坐标系的
getRawX(); //擷取子View左上角距離螢幕X軸的距離
getRawY(); //擷取子View左上角距離螢幕Y軸的距離
2.View的坐标系
我們知道,ViewGroup是可以多級嵌套使用的,也就是說,一個控件可以最外面是一個ViewGroup A,A中呢又有一層ViewGroup B,而B中有一個View C.那麼C的坐标系呢,就是相對于其父控件ViewGroup B的.

圖畫的有點醜,但是應該還算清晰(如果有好用的畫圖軟體的話,勞駕推薦一波).
以下擷取坐标值的方法就是基于View的坐标系的,那麼在上圖這個例子中,就是子控件C相對于父控件B的坐标系的坐标值的擷取方法
getLeft(); //ViewC的左上角相對父控件B的x軸的距離
getRight(); //ViewC的右下角相對父控件B的x軸的距離
getTop(); //ViewC的左上角相對父控件B的y軸的距離
getBottom(); //ViewC的右下角相對父控件B的y軸的距離
好了,基礎的簡介就到這裡,我們開始進入源碼分析
首先呢,我們看View的聲明:
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource
先看看Drawable.Callback
public interface Callback {
void invalidateDrawable(@NonNull Drawable who);//當drawable需要重繪時執行,這個點的drawable将不可用(或者最少一部分不可用)
void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);//drawable通過調用該方法來排程下一幀的動畫
void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);//可以取消之前通過scheduleDrawable方法排程的某一幀
}
三個方法都是drawable的動畫的排程方法
再看KeyEvent.Callback
public interface Callback {
boolean onKeyDown(int keyCode, KeyEvent event);
boolean onKeyLongPress(int keyCode, KeyEvent event);
boolean onKeyUp(int keyCode, KeyEvent event);
boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
}
KeyEvent.Callback中的前三個方法我們比較常見,分别是Down,Up,LongPress觸摸事件的回調,而onKeyMultiple()方法是在快速多次觸發down/up事件的情況下調用
最後AccessibilityEventSource
public interface AccessibilityEventSource {
public void sendAccessibilityEvent(int eventType);
public void sendAccessibilityEventUnchecked(AccessibilityEvent event);
}
這個接口是和輔助功能相關的,在這裡我們先不管它
View的繪制過程
view能夠精準的展示到手機螢幕上,是需要經過精準的計算和繪制的.其繪制過程會經曆三個步驟:Measure,Layout,Draw.整個View的繪制是從ViewRootImpl類的performsTraversals()方法開始的,有興趣的朋友可以下載下傳源碼自己看一下。
Measure
我們先來看measure,和measure相關的有三個方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec);
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
private void setMeasureDimension(int measuredWidth, int measuredHeight);
從函數聲明來看,measure()被final修飾符修飾,無法被子類重寫;onMeasure()可以被子類重寫,我們寫的自定義View通過重寫該方法重新擷取View的測量值,如果不重寫的話,擷取的是預設的測量值通過setMeasureDimension()方法儲存測量值.最後一個方法是儲存測量值的
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;//強制計算
...
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);//需要計算
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
...
}
measure()方法裡的代碼并不算特别多,前面的一些代碼是配置擷取的代碼省略掉,來看最關鍵的邏輯,如果強制計算或需要計算,将執行onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);//擷取view的測量模式
int specSize = MeasureSpec.getSize(measureSpec);//擷取view的測量大小
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
這裡出現了specMode,那我們就簡單介紹一下,MeasureSpec類中MeasureSpecMode有三種
public static final int UNSPECIFIED ;//未指定子view的大小,子view可以為任意大小
public static final int EXACTLY;//子view最多是父view指定的大小
public static final int AT_MOST;//父view指定了子view的大小
getDefaultSize方法中根據測量模式,傳回不同的size大小,當如果是UNSPECIFIED時,傳回的是系統建議的最小寬高;如果是AT_MOST和EXACTLY,則傳回的測量大小specSize.
是以measure的整個流程就是首先measure()擷取儲存的參數,再根據設定來判斷是否強制測量或忽略之前的計算結果并重新測量,onMeasure()方法則是實際測量的操作,可以被子類重寫,預設實作是根據測量模式擷取測量大小或預設最小寬高值并儲存.
Layout
public void layout(int l, int t, int r, int b) {
...
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//判斷布局是否發生改變
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {//判斷布局是否發生改變或強制重新計算
onLayout(changed, l, t, r, b);
...
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
看來layout()和前面的measure()類似,根據布局是否發生改變或強制重新計算來判斷是否執行onLayout()方法,我們繼續看onLayout()方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
看到是空實作的,那麼如果子類沒有重寫onLayout()方法呢?難道不執行布局了嗎?我們往回看,發現在調用onLayout之前有根據是否是光學布局模式來調用setOpticalFrame()和setFrame()
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
setOpticalFrame()内部調用的也是setFrame(),我們繼續來看setFrame()
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
...
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
...
}
return changed;
}
mRenderNode.setLeftTopRightBottom(mLeft,mTop,mRight,mBottom)方法,實質上已經将四個參數儲存下來了,記錄了view的布局資訊.
Draw
在測量和布局之後,我們來看看繪制
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* 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 繪制view内容
* 4. Draw children 繪制子view
* 5. If necessary, draw the fading edges and restore layers 如果有必要的話,繪制漸變邊緣和還原圖層
* 6. Draw decorations (scrollbars for instance) 繪制裝飾(例如滑動條)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case) 一般情況下省略第2-5步
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content 繪制view内容
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children 繪制子view
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground 覆寫是内容的一部分,并在前景繪制的下方繪制
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars) 繪制修飾(前景色,滑動條)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight 繪制預設焦點高亮
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
}
draw(Canvas canvas)方法比較長,大概120行左右,這裡隻剪取了其中的關鍵代碼,從源代碼上來看,繪制流程已經很清楚了,需要注意的是,繪制本身是調用onDraw()方法,繪制子view是調用dispatchDraw()方法.
單個view的繪制流程就是以上,ViewGroup的話用的比較多,而且種類很多,也比較複雜,之後應該會根據單獨的類來一個個分析,比如“LinearLayout的繪制流程”這樣的标題