編輯:關於Android編程
在搜集Android view繪制流程的相關知識時,發現這裡面的流程還是有些復雜的,准備了好幾天,才敢提起筆來。下面就直入主題吧!
view繪制流程是從ViewRoot的performTraversals()方法中開始的,在該方法中會執行view繪制的三部曲,即:measure(測量視圖的大小),layout(確定視圖的位置)draw(繪制視圖的內容)。下面這張圖明確的展示了該過程:
1、measure的過程
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
可以看到該方法是final的,所以不需要子類重寫,裡面的實現主要就是調用了onMeasure。那麼傳入的兩個參數是什麼呢?那就涉及到MeasureSpec了,MeasureSpec由specMode(規格)和specSize(大小)組成,規格有三種,它跟大小對應關系如下:
private int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
這個函數傳入的參數是窗口大小和MATCH_PARENT,這就是為什麼根視圖總是鋪滿屏幕的原因。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
onMeasure裡面主要是使用setMeasuredDimension來設置視圖的大小,這樣就完成了一次measure的過程,當然,一個布局中一般都會包含多個子視圖,每個子視圖都需要經歷一次measure過程。
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);
}
}
}
裡面循環調用了measureChild,其實現為:
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
...
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
這裡面又調用到了view的measure方法,所以這其實是個遞歸調用,不斷的去測量設置子視圖的大小,直至全部測完。
public void layout(int l, int t, int r, int b) {
...
setFrame(l, t, r, b);
...
onLayout(changed, l, t, r, b);
...
}
主要是調用了setFrame(用來設置坐標)和onLayout方法,View裡面OnLayout是空實現,因為onLayout()過程是為了確定視圖在布局中的位置,而這個操作應該是由布局來完成的,即父視圖決定子視圖的顯示位置。而ViewGroup裡面的是抽象方法,也就是需要其子類去實現。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
void layoutVertical(int left, int top, int right, int bottom) {
...
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();
...
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
可以看到其實是遍歷子view,然後又去調用layout,這樣就不停的循環,直到遍歷完所有子view。由於view過程調用了setFrame方法,可以設置視圖的位置,就跟measure的功能重合了,所以這裡設置的話有可能會使之前measure的計算失效。
public void draw(Canvas canvas) {
...
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
final Drawable background = mBackground;
if (background != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
}
if ((scrollX | scrollY) == 0) {
ckground.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
}
...
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
...
// Step 4, draw the children
dispatchDraw(canvas);
...
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
return;
}
這其中最主要的是調用了onDraw和dispatchDraw方法。onDraw是一個空方法,需要子view自己去實現,而ViewGroup的dispatchDraw()方法主要是遍歷子view,然後調用drawChild方法,而drawChild又是調用的draw方法,這樣就又構成了一個循環調用。
總結一下:
1、這三個過程都是從上而下,從父到子的,即:先設置父視圖,然後遍歷子視圖,並對其設置。
2、自定義view時,我們可以重寫onMeasure(非必須)和onDraw方法,在onMeasure的實現裡調用setMeasuredDimension或者super.onMeasure來設置視圖大小。
3、自定義ViewGroup時,我們可以重寫onLayout(必須)方法,在裡面調用view的layout方法設置視圖的位置。
Android NDK學習筆記6-JNI對引用數據類型的操作
字符串操作JNI把Java字符串當成引用類型來處理,JNI提供了java字符串與C字符串之間相互轉換的必要函數。因為java字符串對象是不可變的,因此JNI不提供任何修改
拆解輪子之XRecyclerView
簡介這個輪子是對RecyclerView的封裝,主要完成了下拉刷新、上拉加載更多、RecyclerView頭部。在我的Material Design學習項目中使用到了項目
Android基礎入門教程——8.3.15 Paint API之——Typeface(字型)
Android基礎入門教程——8.3.15 Paint API之——Typeface(字型)標簽(空格分隔): Andro
手機qq如何退出登錄 手機qq如何完全退出
有時候關閉了手機qq還是能收到信息,手機qq如何完全退出呢?下面我們就一起來看看吧! 手機QQ推出登陸教程方法一、退出QQ程序 第一步:打開手機QQ 第二步