主要記錄學習《Android開發(fā)藝術(shù)探索》
1.
View的繪制過程:
ActivityThread(handlerResumeActivity())--->WindwowManagerImpl(addView())--->WindowManageGlobal(addView())--->ViewRootImpl(requestLayout())--->ViewRootImpl(scheduleTraversals())--->ViewRootImpl(doTraversal())--->ViewRootImpl(preformTraversals());
ViewRoot對應ViewRootImpl類,它是連接WindowManager和DecorView的紐帶,在ActivityThread中,當Activity對象被創(chuàng)建完畢后,會將DecorView 添加到
Window中,同時會創(chuàng)建ViewRootImpl對象,并將ViewRootImpl對象和DecorView建立關(guān)聯(lián)
root=new ViewRootImpl(view.getContext(),display);
root.setView(view,panelParentView);
View的繪制流程從ViewRootImpl的 performTraversals()開始
依次調(diào)用以下三個方法
1.performMeasure()
此方法調(diào)用View的measure()的方法--->調(diào)用View的onMeasure()方法
2.performLayout()
此方法調(diào)用View的layout()的方法--->
調(diào)用setFrame(l, t, r, b)確定自身在父控件的位置
調(diào)用View的onLayout()方法(ViewGroup)會確定子控件在自身位置
3.performDraw()
Measure過程結(jié)束后可通過getMeasuredWidth和getMeasuredHeight方法獲取View的測量寬高。
Layout過程結(jié)束后可通過getTop,getBottom,getLeft,getRight來拿到View的四個點的坐標,并可通過getWidth和getHeight方法獲取到View的最終寬高。
Draw過程結(jié)束后View的內(nèi)容才最終顯示在屏幕上。
DecorView是一個FrameLayout 內(nèi)部一般包含一個LinearLayout,這個LinearLayout里面有上下兩個部分(具體情況和Android版本和主題有關(guān))上面是標題欄,下面是內(nèi)容欄(內(nèi)容欄為FrameLayout),內(nèi)容欄的id為content。
MeasureSpec 是一個32位的int值,高2位代表SpecMode,低30位代表SpecSize。
SpecMode 指測量模式共有三類
1.UNSPECIFIED:表示父容器對View不做任何限制,要多大給多大。
2.EXACTLY:表示父容器已檢測出View的所需確切大小,這時候View的最終大小就是SpecSize所指定的值。對應LayoutParams中match_parent和具體的數(shù)值這兩種模式
3.AT_MOST:指定了一個可用大小的即SpecSize,View的大小不能大于這個值。具體要看不同的View的具體實現(xiàn)。它對應于LayoutParams中的wrap_content.
DecorView的MeasureSpec有屏幕尺寸和自身的LayoutParams共同決定。
普通的View的MeasureSpec需要父容器和自身的LayoutParams一起來決定。
View的measure過程是由ViewGroup測量過程傳遞過來的。
getChildMeasureSpec()
測量規(guī)則如下:
parentSize為父控件去除padding的可使用大小。
1.若View指定了確切的尺寸childSize。View的MeasureSpec就是(EXACTLY,childSize)
2.若View是match_parent的
2.1父容器的SpecMode為EXACTLY 。View的MeasureSpec就是(EXACTLY, parentSize)
2.2父容器的SpecMode為AT_MOST。View的MeasureSpec就是(AT_MOST,parentSize)
3.若View是wrap_content的
3.1父容器的SpecMode為EXACTLY 。View的MeasureSpec就是(AT_MOST, parentSize)
3.2父容器的SpecMode為AT_MOST。View的MeasureSpec就是(AT_MOST,parentSize)
2
View的工作流程
1)measure過程:
1.View的measure過程
View的measure方法是final無法重寫,但是View的measure方法會調(diào)用View的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);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
// AT_MOST和EXACTLY兩種模式?jīng)]有區(qū)別大小都為父控件可用大小
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
// 若沒有設(shè)置背景就為mMinWidth,若設(shè)置了背景就為背景大小和mMinWidth的中最大值
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
public int getMinimumHeight() { return mMinHeight;}
從getDefaultSize方法的實現(xiàn)來看,直接繼承View的自定義控件需要重寫onMesasure的方法。否則的在布局中使用wrap_content相當于使用match_parent。
private int mWidth;
private int mHeight;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode==MeasureSpec.AT_MOST && heightSpecMode==MeasureSpec.AT_MOST)
{
setMeasuredDimension(mWidth,mHeight);
}else if (widthSpecMode==MeasureSpec.AT_MOST)
{
setMeasuredDimension(mWidth,heightSpecSize);
}else if (heightSpecMode==MeasureSpec.AT_MOST)
{
setMeasuredDimension(widthSpecSize,mHeight);
}
}
我們只需要給View設(shè)定一個默認的內(nèi)部寬高(mWidth和mHeight)并在wrap_content時進行設(shè)置此寬高即可,至于如何設(shè)定,可根據(jù)具體情況設(shè)置
2.ViewGroup的measure過程
ViewGroup除了測量自身外還會遍歷去調(diào)用所有子元素的measure方法,各個子元素再去遞歸去執(zhí)行這個過程。ViewGroup是一個抽象類并沒有重寫onMeasure方法,但它提供了一個measureChildren的方法
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);
}
}
}
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);
}
ViewGroup并沒有定義測量的具體過程,ViewGroup是一個抽象類,其測量過程需要各個子類自己實現(xiàn)。不做統(tǒng)一實現(xiàn)是因為不同的ViewGroup子類具有不同的布局特性,測量細節(jié)各不相同。
獲取view的測量寬高
1.在onWindowFocusChanged方法中View已經(jīng)初始化完畢,當Activity的窗口失去焦點或得到焦點的時候均會被調(diào)用
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus)
{
int width=view.getMeasuredWidth();
int height=view.getMeasuredHeight();
}
}
- post方法可以把一個Runnable投遞到消息隊列的尾部,Looper取出消息調(diào)用Runnable的時候View已經(jīng)初始化好了。
view.post(new Runnable() {
@Override
public void run() {
int width=view.getMeasuredWidth();
int height=view.getMeasuredHeight();
Log.d(TAG, "run: " +width);
}
});
3.ViewTreeObserver當View樹的狀態(tài)發(fā)生改變或者View樹內(nèi)部的View的可見性發(fā)生改變是 onGlobalLayout方法會回調(diào)。
ViewTreeObserver observer=view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width=view.getMeasuredWidth();
int height=view.getMeasuredHeight();
}
});
2)layout過程:
layout方法確定自身在父控件的中位置,若為ViewGroup要重新來onLayout確定子控件的位置。
layout方法通過調(diào)用setFrame確定mLeft,mTop,mBottom,mRight這四個值,四個頂點確定,那么View自身在父控件的位置就確定了。
layout在View和ViewGroup一般不用重新。
但自定義ViewGroup的時候要重寫onLayout方法 來確定子View的擺放位置,onLayout一般也是遍歷子View并調(diào)用子View的layout方法來確定擺放位置。
3)draw過程
繪制背景 drawBackground(canvas);
繪制自己 onDraw(canvas);
繪制children dispatchDraw(canvas);
繪制裝飾 onDrawForeground(canvas);
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
* 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
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
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
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(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);
// we're done...
return;
}
/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// 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
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
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);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
// 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);
}
設(shè)置不繪制自身,View默認關(guān)閉,ViewGroup默認開啟。
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}