在上一篇文章【從源碼角度分析Activity竿报、Window铅乡、View的關(guān)系】中講到了View的加載流程,最終會(huì)調(diào)用ViewRootImpl的invalidate()方法烈菌。如果對(duì)View的加載流程不熟悉的阵幸,可以先去了解下,然后再和這篇文章結(jié)合芽世,這樣理解的會(huì)比較體系化挚赊。下面開始今天的View的繪制流程之源碼之旅。
1济瓢、ViewRootImpl的invalidate()
void invalidate() {
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//1
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
在上面的代碼中荠割, invalidate() 中有一個(gè)mWillDrawSoon標(biāo)識(shí)位,在進(jìn)行繪制流程的時(shí)候mWillDrawSoon=true旺矾,它的作用:在我們調(diào)用invalidate()蔑鹦,其未執(zhí)行完成時(shí),再次調(diào)用invalidate()是無效的箕宙。也就是說在同一時(shí)間我們多次調(diào)用invalidate()嚎朽,只會(huì)執(zhí)行一次。在上面的代碼可以發(fā)現(xiàn)任務(wù)轉(zhuǎn)移到了注釋1的位置柬帕,其中mTraversalRunnable是一個(gè)Runnable對(duì)象哟忍,mChoreographer是一個(gè)Choreographer對(duì)象,先進(jìn)入Choreographer的postCallback()方法看下做了什么事陷寝?
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException("action must not be null");
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
}
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
在上面代碼中锅很,通過一系列的跳轉(zhuǎn),我們可以發(fā)現(xiàn)凤跑,最后會(huì)在postCallbackDelayedInternal()方法內(nèi)部通過mHandler發(fā)送消息處理爆安,這里調(diào)用的是mTraversalRunnable的run方法去執(zhí)行,并未啟動(dòng)一個(gè)新的工作線程饶火,從這里可以知道鹏控,ViewRootImpl的invalidate()是同步執(zhí)行的。現(xiàn)在已經(jīng)知道怎么啟動(dòng)的了肤寝,那么我們回到mTraversalRunnable的run方法。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
//繪制流程開始執(zhí)行
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//執(zhí)行遍歷
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
2抖僵、ViewRootImpl的performTraversals()方法
由于該方法的代碼量實(shí)在是太大了鲤看,這里就截取關(guān)鍵性的代碼,一些細(xì)節(jié)可能會(huì)忽略掉耍群,但是不會(huì)影響對(duì)整體繪制流程的理解义桂。
private void performTraversals() {
//省略代碼....
//在這里獲取的是根布局的WidthMeasureSpec和HeightMeasureSpec找筝,
//根布局的參數(shù)是從window中得到的,看了文章開頭提的那篇文章就可以發(fā)現(xiàn)
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//省略代碼....
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//省略代碼....
performLayout(lp, mWidth, mHeight);
//省略代碼....
performDraw();
//省略代碼....
}
在performTraversals() 對(duì)于繪制流程主要的就是上面的三個(gè)方法了慷吊。接下來對(duì)這個(gè)三個(gè)方法進(jìn)行逐個(gè)分析袖裕。
3、 performMeasure()
對(duì)View進(jìn)行測量溉瓶,內(nèi)部主要調(diào)用了View的measure()方法急鳄,源碼如下:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//對(duì)View進(jìn)行測量
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
//......1
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
//......2
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
從上面代碼可發(fā)現(xiàn),View的measure方法是不可重寫的堰酿。在注釋1疾宏,通過MeasureSpec調(diào)整widthMeasureSpec和heightMeasureSpec;而這個(gè)MeasureSpec是什么呢触创?
A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode.
上面是Google官方文檔對(duì)MeasureSpec的定義坎藐。大概意思:MeasureSpec內(nèi)部封裝了父View對(duì)子View的布局規(guī)格要求,每一個(gè)MeasureSpec都代表一個(gè)寬度和高度的規(guī)格哼绑,由一個(gè)尺寸和一個(gè)模式組成岩馍。
MeasureSpec的三種模式:
- UNSPECIFIED: 父View對(duì)子View不施加任何要求,子View想要多大就多大抖韩。
- EXACTLY :父View對(duì)子View設(shè)定了確切的尺寸蛀恩,不管子View想要多大,都會(huì)是父View給予的大小
- AT_MOST:子View可以達(dá)到它自己想要的大小帽蝶,但是不會(huì)超過父View所剩余的空間赦肋。
我們回到繪制流程中,繼續(xù)跟蹤代碼励稳,在注釋2佃乘,調(diào)用了onMeasure()并且把widthMeasureSpec和heightMeasureSpec作為參數(shù)傳入。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
可以發(fā)現(xiàn)onMeasure()方法很簡單驹尼,只是調(diào)用了setMeasuredDimension()方法趣避;首先,看下setMeasuredDimension()方法中的getDefaultSize(getSuggestedMinimumWidth())新翎;寬度和高度的設(shè)置都是同一個(gè)原理程帕,所以這里就選一個(gè)寬度來分析。
//measureSpec也就是從父View傳遞過來的widthMeasureSpec
//size是getSuggestedMinimumWidth()返回的值
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
//獲取父View的測量模式
int specMode = MeasureSpec.getMode(measureSpec);
//獲取父View的寬的尺寸大小
int specSize = MeasureSpec.getSize(measureSpec);
//根據(jù)父View的測量模式返回一個(gè)默認(rèn)的尺寸
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
//這里先判斷是否設(shè)置了背景地啰,如果沒有就返回mMinWidth (即:android:minWidth)
//如果有愁拭,則返回背景的最小寬度
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
//保存measuredWidth和measuredHeight
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
到這里,View的基本的measure就已經(jīng)完成了亏吝,但是岭埠,根視圖是DecorView,是一個(gè)ViewGroup,那么對(duì)于ViewGroup而言惜论,其測量又是怎樣的呢许赃?ViewGroup也是繼承自View,不過由于它的特殊性馆类,它存放的是一個(gè)個(gè)子View混聊,所以并不是直接對(duì)ViewGroup進(jìn)行measure,而是通過遍歷的方式對(duì)子View進(jìn)行measure乾巧,等遍歷完所有子View后確認(rèn)自身大小句喜。在ViewGroup中measure的方法有measureChildWithMargins()、measureChildren()卧抗、measureChild()藤滥;measureChildren()這個(gè)方法遍歷時(shí)直接調(diào)用measureChild()對(duì)子View進(jìn)行measure。measureChild()和measureChildWithMargins()的區(qū)別是在于有沒有把margin和padding作為子視圖的大小社裆。對(duì)measureChildren()這個(gè)方法拙绊,里面的實(shí)現(xiàn)比較簡單,通過傳入父View的WidthMeasureSpec泳秀、HeightMeasureSpec和自身的LayoutParams結(jié)合标沪,生成自己的childWidthMeasureSpec;
下面我們看下measureChildWithMargins()這個(gè)方法:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//子View的LayoutParams
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//根據(jù)自身的LayoutParams和父view的MeasureSpec生成自生的childMeasureSpec
//同時(shí)計(jì)算了Padding 和 Margin值嗜傅,已使用的尺寸
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//這里會(huì)回調(diào)到我們之前分析的View的onMeasure()方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//獲取父View的Size和Mode
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//根據(jù)父View不同的模式金句,對(duì)子View進(jìn)行不同的調(diào)整
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
對(duì)于繪制流程的第一步到這里就結(jié)束了,這里對(duì)前面的信息再回顧下吕嘀,通過performMeasure()進(jìn)行測量時(shí)违寞,然后調(diào)用View的measure(),然后又切換onMeasure()偶房;對(duì)于ViewGroup趁曼,是通過遍歷測量子View來確定自身的最終大小的。在測量過程中棕洋,View的大小是有父View的測量規(guī)格和View自身的LayoutParams決定的挡闰。在實(shí)際開發(fā)中,如果我們想要獲取View的寬度掰盘,就必須在onMeasure()后獲取才有效摄悯。
寫的有點(diǎn)飄了~~~補(bǔ)充下查克拉,繼續(xù)
4愧捕、performLayout()
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
//............省略代碼
final View host = mView;
if (host == null) {
return;
}
//............省略代碼
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//............省略代碼
}
public void layout(int l, int t, int r, int b) {
//已進(jìn)行Measure的會(huì)跳過這里
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) {
//........1
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);
}
}
在上面的代碼中可以發(fā)現(xiàn)奢驯,任務(wù)轉(zhuǎn)移到到了注釋1的 onLayout(changed, l, t, r, b);l和t是左上角坐標(biāo)次绘,r和b是右下角坐標(biāo)叨橱。接下我們看下onLayout源碼:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
這里竟然是空實(shí)現(xiàn)典蜕,那么我們就找一個(gè)具體的來分析断盛,下面選了RelativeLayout中的onLayout罗洗。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// The layout has actually already been performed and the positions
// cached. Apply the cached values to the children.
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
}
}
}
RelativeLayout中的onLayout的實(shí)現(xiàn),首先獲取子View的總數(shù)钢猛,然后for循環(huán)遍歷伙菜,得到每個(gè)子View的LayoutParams ,再獲取子View的左上角坐標(biāo)命迈,和右下角坐標(biāo)贩绕,開始layout。
Layout過程總結(jié):layout在實(shí)現(xiàn)方面也比較靈活壶愤,因?yàn)閷?duì)于每一個(gè)ViewGroup而言淑倾,絕大多數(shù)時(shí)候,其內(nèi)容都是不同的征椒,通過遍歷每一個(gè)子View去layout娇哆,而相應(yīng)參數(shù)在measure過程已經(jīng)保存了,會(huì)傳遞到layout這個(gè)過程勃救。
5碍讨、 performDraw()
接下來看繪制流程的最后一個(gè),繪制(Draw)蒙秒。首先進(jìn)入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);
}
//..........代碼省略
}
從上面的代碼中,找到關(guān)鍵點(diǎn)draw()晕讲,接著往下看
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface; //原始圖片的處理緩沖區(qū)
if (!surface.isValid()) {
return;
}
//.........省略代碼
//開始繪制
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
在draw()方法里只選擇了關(guān)鍵性的代碼作為參考覆获,把任務(wù)切換到了drawSoftware()方法。
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
//.......1
canvas = mSurface.lockCanvas(dirty);
// The dirty rectangle can be modified by Surface.lockCanvas()
//noinspection ConstantConditions
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not lock surface", e);
// Don't assume this is due to out of memory, it could be
// something else, and if it is something else then we could
// kill stuff (or ourself) for no reason.
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
}
try {
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight());
//canvas.drawARGB(255, 255, 0, 0);
}
// If this bitmap's format includes an alpha channel, we
// need to clear it before drawing so that the child will
// properly re-composite its drawing on a transparent
// background. This automatically respects the clip/dirty region
// or
// If we are applying an offset, we need to clear the area
// where the offset doesn't appear to avoid having garbage
// left in the blank areas.
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
if (DEBUG_DRAW) {
Context cxt = mView.getContext();
Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
", metrics=" + cxt.getResources().getDisplayMetrics() +
", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
}
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
//.......2
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
try {
//.......3
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not unlock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
//noinspection ReturnInsideFinallyBlock
return false;
}
if (LOCAL_LOGV) {
Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
}
}
return true;
}
在上面的代碼中瓢省,注釋1處弄息,利用mSurface.lockCanvas()鎖定dirty區(qū)域,獲取Canvas净捅。然后在注釋2處疑枯,把canvas作為參數(shù)傳入View的draw()方法進(jìn)行繪制。注釋3蛔六,繪制完成后回收surface荆永。接下來切換到View的draw():
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);
drawAutofilledHighlight(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);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
//.........省略代碼
}
上面代碼中的注釋已經(jīng)很明確了,
step1:進(jìn)行背景的繪制国章,
step2 & step5:保存圖層具钥,繪制邊緣液兽、漸變效果掌动,
step3:繪制view的內(nèi)容宁玫,在這個(gè)地方,不同的view欧瘪,內(nèi)容也不一樣(子View重寫),這也就是我們自定義View重寫的onDraw()佛掖,
step4:同樣也是繪制子View妖碉,不同的View需要各自重寫,
step6:繪制裝飾(前臺(tái)芥被、滾動(dòng)條)欧宜,
step7:繪制默認(rèn)焦點(diǎn)到畫布上。
完篇