1 繪制流程
View的繪制流程從ViewRootImpl的requestLayout()開始
2 measure流程
- ViewGroup:每個ViewGroup必須復(fù)寫onMeasure案狠,并且在onMeasure中measureChild服傍,并在measureChild結(jié)束之后,調(diào)用setMeasuredDimension設(shè)置自身的寬高骂铁。
final void measure(int widthMeasureSpec, int heightMeasureSpec){
onMeasure(widthMeasureSpec,heightMeasureSpec);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
//measureChildren
省略測量子布局的代碼
setMeasuredDimension(參數(shù));
}
- View:每個View也必須復(fù)寫onMeasure吹零,并且在onMeasure中設(shè)置自身寬高绩社。
final void measure(int widthMeasureSpec, int heightMeasureSpec){
//省略其它代碼
onMeasure(widthMeasureSpec,heightMeasureSpec);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
影響View寬高的因素
即ViewGroup先根據(jù)每個child的MarginLayoutParams(繼承自ViewGroup.LayoutParams稽穆,包括android:layout_width、android:layout_height以及各方向的margin)九孩,結(jié)合自身的MeasureSpec(32位的int類型名段,包含測量模式與測量大小,其中測量模式分為無約束UNSPECIFIED泣懊、精確值EXACTLY以及最大值A(chǔ)T _MOST三種)和padding伸辟,得到需要傳給各個child的約束條件MeasureSpec,然后child根據(jù)MeasureSpec馍刮,minWidth屬性以及background的最小寬高了信夫,確定出child自身的寬高。
當(dāng)某個View的measure()方法返回時卡啰,它以及它的所有子節(jié)點的getMeasuredWidth()和getMeasuredHeight()方法的值就已經(jīng)設(shè)置好了静稻。
3 layout流程
public void layout(int l, int t, int r, int b){
//省略其它代碼
setFrame(l, t, r, b);
onLayout(changed, l, t, r, b);
}
protected boolean setFrame(int left, int top, int right, int bottom)
layout方法確定view自身的位置,通過setFrame()設(shè)置四個頂點的坐標(biāo)匈辱。然后調(diào)用onLayout()確定所有子元素的位置振湾。
所以在自定義view中,對于ViewGroup亡脸,需要重寫onLayout()方法押搪,并調(diào)用child.layout()對子元素進行布局。如果是View浅碾,由于沒有子View,無需重寫該方法大州。
注意:view可以重寫layout和onLayout,而ViewGroup的只能重寫onLayout垂谢,其layout是final類型厦画。
4 draw流程
可以看一下,draw事件是如何從上往下傳遞滥朱,逐個繪制的:
5 多次measure根暑、layout和draw
measure力试、layout和draw的流程有可能反反復(fù)復(fù)多次,見下圖购裙。
- ViewGroup導(dǎo)致的重繪
一個父View可能對其子View調(diào)用多次measure()方法懂版。舉個例子:父節(jié)點可能首先會通過一次沒有明確尺寸約束(unspecified dimensions)的測量過程來獲取每個子View想獲得的視圖大小。如果最后得到的數(shù)值過大或者過小躏率,那么父節(jié)點會再次對其子View調(diào)用measure()方法躯畴,并使用實際的計算結(jié)果作為輸入?yún)?shù)(即如果子View不同意首次測量結(jié)果,父View會進行第二次帶約束條件的測量)薇芝。
我們知道蓬抄,頂層View為繼承自FrameLayout的DecorView,看看FrameLayout的measure方法:
// FrameLayout的onMeasure函數(shù)夯到,DecorView的onMeasure會調(diào)用這個函數(shù)嚷缭。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
.....
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
......
}
}
........
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
........
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
當(dāng)然,不可能每次都執(zhí)行兩次measure耍贾,View的Measure函數(shù)中有相關(guān)機制阅爽,只有在FLAG_FORCE_LAYOUT標(biāo)志位為1或者widthMeasureSpec、heightMeasureSpec和上次的不一樣時才會重新measure荐开。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
......
// 當(dāng)FLAG_FORCE_LAYOUT位為1時付翁,就是當(dāng)前視圖請求一次布局操作
//或者當(dāng)前widthSpec和heightSpec不等于上次調(diào)用時傳入的參數(shù)的時候
//才進行從新繪制。
if (forceLayout || !matchingSize &&
(widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec)) {
......
onMeasure(widthMeasureSpec, heightMeasureSpec);
......
}
......
}
- ViewRootImpl導(dǎo)致的重繪
Activity加載的時候晃听,newSurface是true百侧。
private void performTraversals() {
......
boolean newSurface = false;
//TODO:決定是否讓newSurface為true,導(dǎo)致后邊是否讓performDraw無法被調(diào)用,而是重新scheduleTraversals
if (!hadSurface) {
if (mSurface.isValid()) {
// If we are creating a new surface, then we need to
// completely redraw it. Also, when we get to the
// point of drawing it we will hold off and schedule
// a new traversal instead. This is so we can tell the
// window manager about all of the windows being displayed
// before actually drawing them, so it can display then
// all at once.
newSurface = true;
.....
}
}
......
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
......
performDraw();
}
} else { //newSurface為true能扒,會重新scheduleTraversals
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
......
}