Android View 的繪制 的基本過程是: 1. Measure 測量, 即确定View 的大小 2. Layout 布局,即确定View 的擺放位置 3. Draw, 畫View
首先看Measure 關于Measure 放方法有:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) final方法 , 不能重寫 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 可以重寫, 若想影響測量 課在次方法中做處理 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) 最終決定 View 大小的方法, 該不放不能重寫, 且必須在 onMeasure 調用
在ViewGroup 中還有一些 關于Measure的方法: protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) 循環周遊 調用 measureChild 方法 protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) 在這個方法中 會調用子View 的 measure 方法去測量子類
ok 上面說了一些 Measure的相關方法, 用到最多的一班還隻是 onMeasure 方法,自己改變View 的測量的大小 需要有一點需要注意的是 在onMeasure 中必須要調用 setMeasuredDimension 方法不然會報錯
下面來看看 View 預設的onMeasure 方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
隻是的簡單的調用了 setMeasuredDimension 方法, 我在onMeasure 方法中需要計算好大小, 再調用 setMeasuredDimension 方法 我們看到上面 哪些關于 Measure方法參數 都是int 行, 但是他們 不是一個 簡單的 寬度高低, 還有有些其他的東西再裡面: 例如: widthMeasureSpec 這裡有兩個資料, 前3位代表, 測量模式, 後面資料代表寬度: 測量模式有:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;// 移動的位數
private static final int MODE_MASK = 0x3 << MODE_SHIFT;// mask 0011 向右移動30位
public static final int UNSPECIFIED = 0 << MODE_SHIFT;// 0000 向右移動30位
public static final int EXACTLY = 1 << MODE_SHIFT;// 0001向右移動30位
public static final int AT_MOST = 2 << MODE_SHIFT;// 0010向右移動30位
// 向要知道 是什麼模式可以調用:
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
// 擷取具體尺寸
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
// 自己 做一個這個int 資料, 傳入 尺寸 和 模式就好了
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
}
具體的繪制過程隻能很籠統的說一下: 在RootView 中, 會去調用 ViewGroup的measure方法: 然後在View Group 在onMeasure 中 又會依次調用子View 的measure 方法 ViewGroup周遊完後, 也會計算處自己的大小最後調用 setMeasuredDimension 方法 最後子View 調用 onMeasure setMeasuredDimension 方法
View 的大小 是子View 和父View共同決定, 我們在xml 中給出的width height 知識期望值, 在别的部落格上看到一張表表示的還不錯: :
父視圖能力尺寸 | 子視圖期望尺寸 | 子視圖最終允許尺寸 |
EXACTLY + Size1 | EXACTLY + Size2 | EXACTLY + Size2 |
EXACTLY + Size1 | fill_parent/match_parent | EXACTLY +Size1 |
EXACTLY + Size1 | wrap_content | AT_MOST +Size1 |
AT_MOST +Size1 | EXACTLY + Size2 | EXACTLY +Size2 |
AT_MOST +Size1 | fill_parent/match_parent | AT_MOST +Size1 |
AT_MOST +Size1 | wrap_content | AT_MOST +Size1 |
UNSPECIFIED+Size1 | EXACTLY + Size2 | EXACTLY + Size2 |
UNSPECIFIED+Size1 | fill_parent/match_parent | UNSPECIFIED+0 |
UNSPECIFIED+Size1 | wrap_content | UNSPECIFIED+0 |
ok 下面說說layout 方法:
關于Layout的方法有: public void layout(int l, int t, int r, int b) 這個方法是View 的方法 protected abstract void onLayout(boolean changed,int l, int t, int r, int b); ViewGroup的方法且 ViewGroup 必須實作該方法
public void layout(int l, int t, int r, int b)中得 l、t、r、b是指view的左left、上top、右right、底bottom的位置
View 的具體位置是有 ViewGroup中得 onLayout 方法決定 在ViewGroup中的onLayout方法需要依次的周遊子View 調用他們的Layout方法 下面可以看看 LinearLayout 的onLayout方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical();
} else {
layoutHorizontal();
}
}
void layoutVertical() {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
final int width = mRight - mLeft;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + mBottom - mTop - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (mBottom - mTop - 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) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
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);
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;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
在Layout中做的就是 計算好位置, 然後調用子ViewLayout方法
下面在來看看Draw 的過程, 當測量完了, 寬高弄好了, 位置也确定了, 最後就是 畫View 了 draw 相關的方法有; public void draw(Canvas canvas) protected void onDraw(Canvas canvas)
View 的Draw 方法中有6個步驟
/*
* 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)
*/
在第三步 Draw view's content 調用onDraw方法,子類中實作onDraw方法。 在第四步,Draw children步驟使用的dispatchDraw方法,這個方法在ViewGroup中有實作。 View或ViewGroup的子類不用再重載ViewGroup中該方法,因為它已經有了預設而且标準的view系統流程。dispatchDraw()内部for循環調用drawChild()分别繪制每一個子視圖,而drawChild()内部又會調用draw()函數完成子視圖的内部繪制工作
ok 上面基本就是 繪制View 的一個大概的粗擦的流程 寫的不是很好, 但是謝了總歸會加深寫印象, 一個對源碼有更深的探究時, 還有在重新寫一遍 産考一下文章: http://blog.csdn.net/xyz_lmn/article/details/20385049