一 開始的開始
相信很多小伙伴刻获,在去面試的時候郎仆,都會遇到面試官一臉正經的問你只祠,簡單的說下View的繪制流程。其實一遇到這個問題扰肌,我是奔潰的抛寝,因為完全無法講出第一句話,很多人都是一句曙旭,哦盗舰,view的繪制流程就是onMeasure()->onLayout()->onDraw()。onMeasure()方法就是計算View的大小桂躏,onLayout()就是計算view所在的位置钻趋,onDraw()就是開始繪制。balabala剂习,這個時候蛮位,面試官就會面帶微笑,說了句:哦鳞绕,然后呢失仁?
二 View繪制的開始
view的繪制,是從viewRootImp的performTraversals()開始的们何,performTraversal()的源碼很長萄焦,其實,我們只要知道以下的三個流程即可:
private void performTraversals() {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}
好了冤竹,我們接下來來看看performMeasur(childWidthMeasureSpec,childHeightMeasureSpec)的代碼
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
從這里我們可以看出拂封,ViewRootImp中的performMeasure方法,最終是回調到View中的measure()方法中贴见。
我們接下來看performLayout方法
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (host == null) {
return;
}
....
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
....
mInLayout = false;
}
在這段代碼中烘苹,我們省去了絕大部分頂層view的layout操作,只保留了繪制流程相關的代碼片部。從這段代碼我們可以看出performLayout最終會調用到view的layout方法镣衡,并把父view的left top right bottom傳給view中。至于view的layout方法是怎樣的档悠,我們后面再講廊鸥。
接下來,我們來看performDraw方法
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
} else if (mView == null) {
return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
我們主要看draw()方法辖所,這個方法最后其實調用的就是
public final void dispatchOnDraw() {
if (mOnDrawListeners != null) {
mInDispatchOnDraw = true;
final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
int numListeners = listeners.size();
for (int i = 0; i < numListeners; ++i) {
listeners.get(i).onDraw();
}
mInDispatchOnDraw = false;
}
}
這個方法是用來干嘛的惰说?其實就是通知已經注冊了OnDrawListener的監(jiān)聽者,view已經要開始繪制了缘回。其實吆视,也就是到了onDraw()方法中了典挑。
通過源碼,一步一步追蹤啦吧,我們可以發(fā)現您觉,view的這個繪制流程其實是這樣的
ViewRootImp->PerformTrasvel()->PerformMeasure()->measure()->onMeasure()
->performLayout()->onLayout()
->performDraw()->draw()->dispatchDraw()->onDraw()
三 View的measure流程
1 View的measure(int widthMeasureSpec, int heightMeasureSpec)
我們通過看注釋,知道了measure的作用是授滓,計算這個view應該是多大琳水,因為父布局提供了傳入制定的寬高參數。具體的測量工作其實是放在onMeasure(int般堆,int)方法中去執(zhí)行的在孝,onMeasure(int,int)方法能夠也必須被繼承(當我們重寫view)的時候淮摔。
好了私沮,那么問題來了,什么是widthMeasureSpec 和heighMeasureSpec噩咪?
我們回到ViewRootImp的performTrasvel()方法中
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
也即是顾彰,width和heigh的MeasureSpec是通過getRootMeasureSpec(int,int)方法中獲取的,
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
核心的計算方法就是MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);這個方法胃碾。
其實這個方法的作用就是涨享,MeasureSpec通過SpecMode和SpecSize來判斷一個view應該是多大。那么仆百,什么是specMode厕隧?
SepcMode一共有三類,除了我們上面看到的MeasureSpec.EXACTLY外俄周,還有MeasureSpec.UNSPECIFIED和MeasureSpec.AT_MOST
MeasureSpec.UNSPECIFIED: 父布局沒有指定任何的約束大小吁讨,子布局想多大就多大;
MeasureSpec.EXACTLY: 父布局已經指定了一個確切的大小峦朗,子布局的大小就是SpecMode的大薪ㄉァ;
MeasureSpec.AT_MOST: 子布局可以想多大就多大波势,但是不能超過SpecSize的值
2 View的onMeasure(int widthMeasureSpec, int heightMeasureSpec)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
這么簡單的幾行代碼翎朱,其實暗藏了三個重要的方法,我們從內到外進行剖析:
1 getSuggestedMinimumWidth()
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
這個方法的作用尺铣,就是獲取這個view被建議的最小的寬度拴曲。如果這個view被我們設置了background,那么凛忿,就取最小寬度和background中最小寬度兩者之間最大的那一個數字澈灼。獲取getSuggestedMinimumHeight()方法也是同理,這里不單獨在做分析
2 getDefaultSize(int size, int measureSpec)
我們下面來看一下源碼:
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;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
這段代碼也很簡單,首先view會去獲取父view傳進來的measureSpec叁熔,獲取specSize和specMode兩個值委乌。當當前的specMode是MeasureSpec.UNSPECIFIED,則返回我們上一點分析的者疤,minimumWidth的長度福澡,如果是 MeasureSpec.AT_MOST和MeasureSpec.EXACTLY,那么其實就是specSize的大小驹马。
3 ViewGroup的measure流程###
ViewGroup其實沒有onMeasure()方法,ViewGroup的measure流程除秀,最后就是走的measureChildren(int,int)方法中糯累,我們接下來看這個方法
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);
}
measureChildren的思想就是,取出一個個子元素册踩,然后過去layoutParams泳姐,然后創(chuàng)建children的MeasureSpec,并把這個spec發(fā)給child暂吉,讓child去回調measure方法胖秒,這就回到了我們第一點開始分析的內容了。
三 View的Layout流程
view的layout流程慕的,簡單概括阎肝,就是,指定一個view的位置和它的真正的大小肮街。這就是我們在上面所說的风题,measure過程并不能真正得到view的實際大小,要在layout的過程中才可以被確定嫉父。好了沛硅,接下來繼續(xù)看源碼:
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);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
整個layout的流程就是,通過setOpticalFrame或者setFrame方法绕辖,穿進去l摇肌,t,r仪际,b围小,這樣我們的view的位置就可以確定了。接下來就回調onLayout()方法弟头,由子view去決定當前的layout要怎么控制吩抓。
三 View的draw流程
draw流程比較簡單,這里不大篇幅的上代碼赴恨,根據注釋疹娶,我們可以得到view的draw流程主要是有5步:
1 畫背景
2 如果有需要的話,保存當前畫布
3 畫view自己的內容
4 如果有子View伦连,畫出子view
5 畫一些裝飾(decorations)view雨饺,比如前景色钳垮、srcollbars