主要說下視圖的繪製,不關注啓動流程。
一個應用啓動,首先會啓動一個主Activity,然後開始加載視圖進行繪製。而繪製會從跟視圖ViewRootImpl.java 的performTraversals()方法開始,從上到下遍歷整個視圖樹,每個view控件負責繪製自己,而viewGroup還需要負責通知自己的子View進行繪製操作。
視圖繪製的過程可以分爲三個步驟:
測量(Measure)
佈局(Layout)
繪製(Draw)
performTraversals的核心代碼
private void performTraversals(){
//....
int childWithMeasureSpec=getRootMesureSpec(mWidth,lp.width);
int childHeightMeasureSpec=getRootMeasureSpec(mHeight,lp.height);
//....
//執行測量流程
performMeasure(childWithMeasureSpec,childHeightMeasureSpec);
//.....
//執行佈局流程
performLayout(lp,desiredWindowWidth,desiredWindowHeight);
//....
//....執行繪製流程
performDraw();
}
首先我們來說下Measure中的MeasureSpec:
MeasureSpec表示的是一個32位的整型值,它的高2位表示測量模式SpecMode,低30位表示某種測量模式下的規格大小SpecSize.
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* 不指定測量模式(通常用於系統內部,應用開發很少用)。
父視圖沒有限制子視圖的大小,子視圖可以是想要的任何尺寸
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
*精確測量模式,當該視圖的layout_width或者layout_height
指定爲具體指或者match_parent 的時候生效
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* 最大值測量模式,當視圖的layout_width或者layout_height
指定爲wrap_content時生效。此時子視圖的尺寸可以是不超過父視圖允許的最大尺寸的任何尺寸
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
我們在來看看測量的具體:
//在viewGroup中遍歷子view 然後在調用子view的自身測量
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
//注意 childWidthMeasureSpec 這裏爲了獲取子view的測量模式,傳遞參數是父view的測量模式
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
//這一系列的調用最終是到onMeasure()方法了 這個方法在我們自定義view的時候
//自定義測量時需要重寫此方法
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) {
//.......
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
//.....
}
//......
}
//.......
}
//設置測量後的結果
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
//通過傳入的測量spec 來獲取最終大小
public static int getDefaultSize(int size, int measureSpec) {
int result = 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;
}
至此measure的流程大致走完了。具體的還要去看源碼再細細品嚐。
接下來我們來看Layout:
ViewRootImpl.java
//執行佈局流程
performLayout(lp,desiredWindowWidth,desiredWindowHeight);
(上面貼過的代碼)
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
//.....
try {
//.....
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//.....
}finally{
//.....
}
}
public void layout(int l, int t, int r, int b) {
//.....
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);
}else{
//.....
}
}
//空方法,子類如果是ViewGroup類型,則重寫這個方法,實現ViewGroup中所有view控件佈局流程
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
Layout的流程具體實現還要在onLayout裏面。
最後一個Draw
private boolean draw(boolean fullRedrawNeeded){
//....
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
//...
mView.draw(canvas);
//...
}
public void draw(Canvas canvas) {
//步驟一:繪製view的背景.....
if (!dirtyOpaque) {
drawBackground(canvas);
}
//步驟二:如果需要的話,保存Canvas的圖層,爲fading做準備
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
if (drawTop) {
canvas.saveUnclippedLayer(left, top, right, top + length);
}
if (drawBottom) {
canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
}
if (drawLeft) {
canvas.saveUnclippedLayer(left, top, left + length, bottom);
}
if (drawRight) {
canvas.saveUnclippedLayer(right - length, top, right, bottom);
}
}
//步驟三:繪製view的內容
onDraw(canvas);
//步驟四: 繪製view的子view
dispatchDraw(canvas);
//步驟五:如果需要的話繪製view的fading邊緣並恢復圖層
if (drawTop) {
//...
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
//...
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
//....
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
//...
canvas.drawRect(right - length, top, right, bottom, p);
}
//.....
canvas.restoreToCount(saveCount);
// 步驟六:繪製view的裝飾(例如滾動條)
onDrawForeground(canvas);
}
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
//......
}
到此整個view的創建到繪製流程就結束了。最重要的還是在繪製那塊,比如我們需要的一些動效和一些特殊形狀都需要在這裏面實現。