編輯:關於Android編程
在上篇文章中Android視圖的繪制流程(上)——View的測量對View的Measure過程進行了詳細的說明。對於在View的繪制的整個過程中,在對View的大小進行測量以後,便開始確定View的位置並且將其繪制到屏幕上。也就是View的Layout與Draw過程。那麼就來看一下是如何實現這兩個過程的。
上文提到View的繪制流程是從ViewRoot的performTraversals方法開始,那麼在View完成測量以後,在performTraversals方法中對performLayout進行調用。在performLayout中可以找到下面這行代碼。
host.layout(0, 0, host.getMeasuredWidth(),host.getMeasuredHeight())
上面這行代碼中的host指的就是DecorView,對於這個DecorView我們都知道它是一個繼承自FrameLayout的ViewGroup。這個layout方法也就是ViewGroup中的layout方法。下面就來看一下ViewGroup中的這個layout方法。
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
從ViewGroup的layout方法我們可以看出它是一個final類型的,也就是說在ViewGroup中的layout方法是不能被子類重寫的。ViewGroup中的layout方法中又調用父類的layout方法,也就是View的layout方法。下面就來看一下View的layout方法。
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
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);
......
}
......
}
對於setOpticalFrame實質上也是調用setFrame方法,而setFrame的作用是將View的位置分別保存到mLeft,mTop,mBottom,mRight變量當中。之後在判斷是否需要重新布局,如果需要重新布局的話,便調用onLayout方法。
其實在View的Layout過程當中,在View的layout方法是確定View的自身位置,而在View的onLayout方法中則是確定View子元素的位置。所以在這可以看到對於View的onLayout是一個空方法,沒有完成任何事情。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
而ViewGroup的onLayout方法則是一個抽象方法,通過具體的ViewGroup實現類來完成對子元素的Layout過程。
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
下面依然通過一個具體的ViewGroup,來看一下FrameLayout的onLayout方法實現過程,對於FrameLayout的onLayout方法的實現是非常簡單的,所以就以FrameLayout為例進行說明。下面來看一下FrameLayout的onLayout方法。
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
在FrameLayout的onLayout方法中只是調用了layoutChildren方法,從這個方法名便可以看出它的功能就是為FrameLayout的子元素進行布局。下面就來看一下這個layoutChildren方法。
void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
對於這段代碼的邏輯也很簡單。通過遍歷FrameLayout內所有的子元素,然後獲取到View測量後的寬和高,在根據子View的Gravity屬性來決定子View在父控件中四個頂點的位置。最後調用子View的layout方法來完成View的整個測量過程。
在通過ViewRoot的performTraversals方法完成對View樹的整個布局以後,下面便開始將View繪制到手機屏幕上。對於View的Draw過程在ViewRoot的performTraversals方法中通過調用performDraw方法來完成的。在performDraw方法中最終會通過創建一個Canvas對象,並調用View的draw方法,來完成View的繪制。
@CallSuper
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;
}
......
}
在這裡從宏觀上來對Draw過程進行一下分析。在注釋中可以看出對於View的繪制過程分為六步進行的。其中第二步和第五步一般很少用到,可以忽略。剩下幾步則為:
繪制背景 drawBackground(canvas) 繪制自身 onDraw(canvas) 繪制 children dispatchDraw(canvas)繪制裝飾 onDrawForeground(canvas)
對於子View的繪制傳遞是通過dispatchDraw來進行的,在View中的dispatchDraw方法是由ViewGroup來實現的,並且遍歷調用所有子元素的draw方法,完成整個View樹的繪制過程。
對於View的繪制流程,總共分為三大步。分別是View的測量,布局與繪制。首先通過ViewRoot,對View樹根節點進行操作。依次向下遍歷,完成它們的Measure,Layout,Draw過程。從而使View展現在手機屏幕上。
什麼是代碼混淆 Java 是一種跨平台的、解釋型語言,Java 源代碼編譯成中間”字節碼”存儲於 class 文件中。由於跨平台的需要,Java
和MVC框架模式一樣,Model模型處理數據代碼不變在Android的App開發中,很多人經常會頭疼於App的架構如何設計:我的App需要應用這些設計架構嗎?MVC,MV
應用開發中經常會有從數據庫中讀取數據顯示,然後選中多條、全部記錄並且刪除的需求。在做定制系統聯系人的時候也遇到這樣的需求,下面寫個簡單的通過ListView和CheckB
有時候我們需要在游戲或應用中用一些符合我們樣式的提示框(AlertDialog),以下是我在開發一個小游戲中總結出來的.希望對大家有用.先上效果圖:下面是用到的背景圖或按