前言
之前已經(jīng)和大家聊了onLayout的流程梦碗,本文將會繼續(xù)聊一聊onDraw中做了什么?本文將集中關(guān)注軟件渲染蓖救,關(guān)于Canvas的api源碼解析暫時不會在本文聊洪规,會專門開一個Skia源碼解析進行分析。
如果遇到問題可以來http://www.reibang.com/p/a4fb6a02ad53中討論
正文
performTravel的方法走完onMeasure和onLayout流程后會走到下面這段代碼段循捺。
文件:/frameworks/base/core/java/android/view/ViewRootImpl.java
if (mFirst) {
if (sAlwaysAssignFocus || !isInTouchMode()) {
if (mView != null) {
if (!mView.hasFocus()) {
mView.restoreDefaultFocus();
} else {
...
}
}
} else {
View focused = mView.findFocus();
if (focused instanceof ViewGroup
&& ((ViewGroup) focused).getDescendantFocusability()
== ViewGroup.FOCUS_AFTER_DESCENDANTS) {
focused.restoreDefaultFocus();
}
}
}
final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
if (regainedFocus) {
mLostWindowFocus = false;
} else if (!hasWindowFocus && mHadWindowFocus) {
mLostWindowFocus = true;
}
if (changedVisibility || regainedFocus) {
boolean isToast = (mWindowAttributes == null) ? false
: (mWindowAttributes.type == WindowManager.LayoutParams.TYPE_TOAST);
...
}
mFirst = false;
mWillDrawSoon = false;
mNewSurfaceNeeded = false;
mActivityRelaunched = false;
mViewVisibility = viewVisibility;
mHadWindowFocus = hasWindowFocus;
if (hasWindowFocus && !isInLocalFocusMode()) {
final boolean imTarget = WindowManager.LayoutParams
.mayUseInputMethod(mWindowAttributes.flags);
if (imTarget != mLastWasImTarget) {
mLastWasImTarget = imTarget;
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null && imTarget) {
imm.onPreWindowFocus(mView, hasWindowFocus);
imm.onPostWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
}
}
在進入onDraw的流程之前斩例,會先處理焦點。這個過程中可以分為2大步驟:
- 1.如果是第一次渲染从橘,則說明之前的寬高都是都為0.在requestFocus方法中會有這個判斷把整個焦點集中攔截下來:
private boolean canTakeFocus() {
return ((mViewFlags & VISIBILITY_MASK) == VISIBLE)
&& ((mViewFlags & FOCUSABLE) == FOCUSABLE)
&& ((mViewFlags & ENABLED_MASK) == ENABLED)
&& (sCanFocusZeroSized || !isLayoutValid() || hasSize());
}
而在每一次onMeasure之前念赶,都會嘗試集中一次焦點的遍歷。其中requestFocusNoSearch方法中恰力,如果沒有測量過就會直接返回false叉谜。因為每一次更換焦點或者集中焦點都可能伴隨著如背景drawable,statelistDrawable等切換踩萎。沒有測量過就沒有必要做這無用功(詳情請看View的繪制流程(三) onMeasure
)停局。
因此此時為了彌補之前拒絕焦點的行為,會重新進行一次restoreDefaultFocus的行為進行requestFocus處理香府。
- 2.如果存在窗體焦點董栽,同時不是打開了FLAG_LOCAL_FOCUS_MODE標志(這是一種特殊情況,一般打上這個標志位只有在startingWindow的快照中才會有企孩,startingWindow具體是什么可以看看WMS在Activity啟動中的職責 添加窗體(三))锭碳。
則會調(diào)用InputMethodManager的onPostWindowFocus方法啟動帶了android.view.InputMethod這個action的軟鍵盤服務。詳細的這里暫時不展開討論勿璃。
onDraw流程
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
reportNextDraw();
}
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
} else {
if (isViewVisible) {
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
- 1.判斷到如果是第一次調(diào)用draw方法擒抛,則會調(diào)用reportNextDraw推汽。
private void reportNextDraw() {
if (mReportNextDraw == false) {
drawPending();
}
mReportNextDraw = true;
}
void drawPending() {
mDrawsNeededToReport++;
}
能看到實際上就是設置mReportNextDraw為true。我們回顧一下前兩個流程mReportNextDraw參與了標志位的判斷歧沪。在執(zhí)行onMeasure和onLayout有兩個大前提民泵,一個是mStop為false,一個是mReportNextDraw為true槽畔。只要滿足其一就會執(zhí)行。
這么做的目的只有一個胁编,保證調(diào)用一次onDraw方法厢钧。為什么會這樣呢?performDraw是整個Draw流程的入口嬉橙。然而在這個入口早直,必須要保證cancelDraw為false以及newSurface為false。
注意市框,如果是第一次渲染因為會添加進新的Surface霞扬,此時newSurface為true(可以看View的繪制流程(二) 繪制的準備)。所以會走到下面的分之枫振,如果串口可見則調(diào)用scheduleTraversals執(zhí)行下一次Loop的繪制流程喻圃。否則判斷是否有需要執(zhí)行的LayoutTransitions layout動畫就執(zhí)行了。
因此第一次是不會走到onDraw粪滤,是從第二次Looper之后View的繪制流程才會執(zhí)行onDraw斧拍。
我們繼續(xù)關(guān)注performDraw的邏輯。
ViewRootImpl performDraw
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
} else if (mView == null) {
return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
boolean usingAsyncReport = false;
if (mReportNextDraw && mAttachInfo.mThreadedRenderer != null
&& mAttachInfo.mThreadedRenderer.isEnabled()) {
usingAsyncReport = true;
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> {
pendingDrawFinished();
});
}
try {
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
}
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();
}
mAttachInfo.mPendingAnimatingRenderNodes.clear();
}
if (mReportNextDraw) {
mReportNextDraw = false;
if (mWindowDrawCountDown != null) {
try {
mWindowDrawCountDown.await();
} catch (InterruptedException e) {
Log.e(mTag, "Window redraw count down interrupted!");
}
mWindowDrawCountDown = null;
}
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.setStopped(mStopped);
}
if (mSurfaceHolder != null && mSurface.isValid()) {
SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished);
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
} else if (!usingAsyncReport) {
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.fence();
}
pendingDrawFinished();
}
}
}
我們把整個流程抽象出來實際上就是可以分為如下幾個步驟:
對于軟件渲染:
- 1.調(diào)用draw方法杖小,遍歷View的層級肆汹。
- 2.如果Surface是生效的,則在SurfaceHolder.Callback的surfaceRedrawNeededAsync回調(diào)中調(diào)用pendingDrawFinished予权。
- 3.如果是強制同步渲染昂勉,則會直接調(diào)用pendingDrawFinished。
對于硬件渲染:
- 1.調(diào)用draw方法扫腺,遍歷View的層級岗照。
- 2.通過監(jiān)聽mThreadedRenderer的setFrameCompleteCallback回調(diào)執(zhí)行pendingDrawFinished方法。
我們先關(guān)注軟件渲染的流程斧账。也就是draw和pendingDrawFinished谴返。
ViewRootImpl draw
private boolean draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!surface.isValid()) {
return false;
}
if (!sFirstDrawComplete) {
synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {
mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating) {
curScrollY = mScroller.getCurrY();
} else {
curScrollY = mScrollY;
}
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
}
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
}
return false;
}
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
mAttachInfo.mTreeObserver.dispatchOnDraw();
int xOffset = -mCanvasOffsetX;
int yOffset = -mCanvasOffsetY + curScrollY;
final WindowManager.LayoutParams params = mWindowAttributes;
final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
if (surfaceInsets != null) {
xOffset -= surfaceInsets.left;
yOffset -= surfaceInsets.top;
dirty.offset(surfaceInsets.left, surfaceInsets.right);
}
...
mAttachInfo.mDrawingTime =
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
boolean useAsyncReport = false;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
mInvalidateRootRequested = false;
mIsAnimating = false;
if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
mHardwareYOffset = yOffset;
mHardwareXOffset = xOffset;
invalidateRoot = true;
}
if (invalidateRoot) {
mAttachInfo.mThreadedRenderer.invalidateRoot();
}
dirty.setEmpty();
final boolean updated = updateContentDrawBounds();
if (mReportNextDraw) {
mAttachInfo.mThreadedRenderer.setStopped(false);
}
if (updated) {
requestDrawWindow();
}
useAsyncReport = true;
final FrameDrawingCallback callback = mNextRtFrameCallback;
mNextRtFrameCallback = null;
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
} else {
if (mAttachInfo.mThreadedRenderer != null &&
!mAttachInfo.mThreadedRenderer.isEnabled() &&
mAttachInfo.mThreadedRenderer.isRequested() &&
mSurface.isValid()) {
try {
mAttachInfo.mThreadedRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
}
mFullRedrawNeeded = true;
scheduleTraversals();
return false;
}
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
return useAsyncReport;
}
大致上完成了如下流程:
1.如果surface無效則直接返回
- sFirstDrawHandlers這個存儲著runnable靜態(tài)對象。實際上是在ActivityThread啟動后調(diào)用attach方法通過addFirstDrawHandler添加進來的目的只是為了啟動jit模式咧织。
3.scrollToRectOrFocus 處理滑動區(qū)域或者焦點區(qū)域嗓袱。如果發(fā)生了滑動則回調(diào)TreeObserver.dispatchOnScrollChanged。接下來則通過全局的mScroller通過computeScrollOffset判斷是否需要滑動動畫习绢。如果需要執(zhí)行動畫渠抹,則調(diào)用DeocView的onRootViewScrollYChanged蝙昙,進行Y軸上的動畫執(zhí)行。
4.通過ViewTreeObserver的dispatchOnDraw開始分發(fā)draw開始繪制的監(jiān)聽者梧却。
5.判斷是否存在surface面上偏移量奇颠,有就矯正一次臟區(qū),把偏移量添加上去放航。
接下來則會進入到硬件渲染和軟件渲染的分支烈拒。但是進一步進行調(diào)用draw的流程有幾個前提條件:臟區(qū)不為空,需要執(zhí)行動畫广鳍,輔助服務發(fā)生了焦點變化
- 6.如果ThreadedRenderer不為空且可用荆几。ThreadedRenderer通過onPreDraw回調(diào)到ViewRootImpl,更新mHardwareYOffset赊时,mHardwareXOffset吨铸。如果這兩個參數(shù)發(fā)生了變化,則說明整個發(fā)生了硬件繪制的區(qū)域變化祖秒,需要從頭遍歷一次所有的區(qū)域設置為無效區(qū)域诞吱,mThreadedRenderer.invalidateRoot。
最后調(diào)用ThreadedRenderer.draw方法執(zhí)行硬件渲染繪制竭缝。并且設置通過registerRtFrameCallback設置進來的callback設置到ThreadedRenderer中房维。
- 7.如果此時ThreadedRenderer不可用但是不為空,說明此時需要對ThreadedRenderer進行初始化抬纸,調(diào)用scheduleTraversals在下一輪的繪制流程中才進行硬件渲染握巢。
8.如果以上情況都不滿足,說明是軟件渲染松却,則調(diào)用drawSoftware進行軟件渲染暴浦。
9.如果不許要draw方法遍歷全局的View樹,則判斷是否需要執(zhí)行滑動動畫晓锻,需要則調(diào)用scheduleTraversals進入下一輪的繪制歌焦。
本文先拋開硬件渲染,來看看軟件渲染drawSoftware中做了什么砚哆。還有scrollToRectOrFocus滑動中做了什么独撇?
ViewRootImpl scrollToRectOrFocus
boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
final Rect ci = mAttachInfo.mContentInsets;
final Rect vi = mAttachInfo.mVisibleInsets;
int scrollY = 0;
boolean handled = false;
if (vi.left > ci.left || vi.top > ci.top
|| vi.right > ci.right || vi.bottom > ci.bottom) {
final View focus = mView.findFocus();
if (focus == null) {
return false;
}
View lastScrolledFocus = (mLastScrolledFocus != null) ? mLastScrolledFocus.get() : null;
if (focus != lastScrolledFocus) {
rectangle = null;
}
if (focus == lastScrolledFocus && !mScrollMayChange && rectangle == null) {
} else {
mLastScrolledFocus = new WeakReference<View>(focus);
mScrollMayChange = false;
if (focus.getGlobalVisibleRect(mVisRect, null)) {
if (rectangle == null) {
focus.getFocusedRect(mTempRect);
if (mView instanceof ViewGroup) {
((ViewGroup) mView).offsetDescendantRectToMyCoords(
focus, mTempRect);
}
} else {
mTempRect.set(rectangle);
}
if (mTempRect.intersect(mVisRect)) {
if (mTempRect.height() >
(mView.getHeight()-vi.top-vi.bottom)) {
}
else if (mTempRect.top < vi.top) {
scrollY = mTempRect.top - vi.top;
} else if (mTempRect.bottom > (mView.getHeight()-vi.bottom)) {
scrollY = mTempRect.bottom - (mView.getHeight()-vi.bottom);
} else {
scrollY = 0;
}
handled = true;
}
}
}
}
if (scrollY != mScrollY) {
if (!immediate) {
if (mScroller == null) {
mScroller = new Scroller(mView.getContext());
}
mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY);
} else if (mScroller != null) {
mScroller.abortAnimation();
}
mScrollY = scrollY;
}
return handled;
}
能看到在這個過程中實際上就是處理兩個區(qū)域mVisibleInsets可見區(qū)域以及mContentInsets內(nèi)容區(qū)域。
實際上這個過程就是從根部節(jié)點開始尋找焦點躁锁,然后整個畫面定格在焦點處纷铣。因為mVisibleInsets一般是屏幕中出去過掃描區(qū)的大小,但是內(nèi)容區(qū)域就不一定了战转,可能內(nèi)容會超出屏幕大小搜立,因此會通過mScroller滑動定位。
計算原理如下槐秧,分為2個情況:
- 1.可視區(qū)域的頂部比起獲得了焦點的view的頂部要低啄踊,說明這個view在屏幕外了忧设,需要向下滑動:
scrollY = mTempRect.top - vi.top;
- 2.如果焦點view的底部比起可視區(qū)域要比可視區(qū)域的低,說明需要向上滑動,注意滑動之后需要展示view颠通,因此滑動的距離要減去view的高度:
scrollY = mTempRect.bottom - (mView.getHeight()-vi.bottom);
稍微變一下如下:
scrollY = mTempRect.bottom +vi.bottom - mView.getHeight();
ViewRootImpl drawSoftware
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
final Canvas canvas;
int dirtyXOffset = xoff;
int dirtyYOffset = yoff;
if (surfaceInsets != null) {
dirtyXOffset += surfaceInsets.left;
dirtyYOffset += surfaceInsets.top;
}
try {
dirty.offset(-dirtyXOffset, -dirtyYOffset);
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
canvas = mSurface.lockCanvas(dirty);
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
} finally {
dirty.offset(dirtyXOffset, dirtyYOffset); // Reset to the original value.
}
try {
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
try {
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;
}
}
return true;
}
這里面的邏輯和上面硬件渲染邏輯有點相似:
- 1.同樣還是根據(jù)全局的surface的偏移量對整個dirty區(qū)域進行偏移
- 2.通過Surface.lockCanvas方法映射一個Canvas對象址晕,之后所有的繪制行為都在這個Canvas對象上。關(guān)于這個方法顿锰,詳細的原理可以看看我寫的SurfaceView和TextureView 源碼淺析(上)谨垃。
- 3.獲得Canvas后,由于上面是對整個surface的偏移硼控,因此Canvas作為surface映射出來的繪制對象也需要進行一次偏移乘客。
- 4.調(diào)用DecorView的draw方法,開始對整個View樹遍歷淀歇。
- 5.遍歷完整個View樹后,說明所有的信息已經(jīng)會知道Canvas了匈织,就可以通過surface.unlockCanvasAndPost浪默,把記錄在SkCanvas中的像素數(shù)據(jù)發(fā)送到SF中渲染到屏幕中。關(guān)于第五點詳細的可以閱讀SurfaceView和TextureView 源碼淺析(上)缀匕。后續(xù)的步驟可以閱讀我寫的SF系列文章纳决。
我們繼續(xù)來要來看看draw方法做了什么。
DecorView draw
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
能看到整個DecorView實際上就是調(diào)用了父類的draw方法后乡小,專門給menu欄的drawable繪制到Canvas中阔加。
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;
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) {
if (!dirtyOpaque) onDraw(canvas);
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
onDrawForeground(canvas);
drawDefaultFocusHighlight(canvas);
return;
}
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
if (drawTop) {
canvas.saveUnclippedLayer(left, top, right, top + length);
}
if (drawBottom) {
canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
}
if (drawLeft) {
canvas.saveUnclippedLayer(left, top, left + length, bottom);
}
if (drawRight) {
canvas.saveUnclippedLayer(right - length, top, right, bottom);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
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);
}
大致上draw方法分為如下幾個步驟:
- 1.首先在draw方法中先校驗mPrivateFlags中打開的標志位。還記得上一篇文章聊過的PFLAG_DIRTY_MASK的掩碼實際上控制的是dirty以及透明兩個標志位满钟。
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
首先校驗PFLAG_DIRTY_OPAQUE也就是透明標志位是否開啟了胜榔,開啟了dirtyOpaque則為true。同時打開PFLAG_DRAWN標志位湃番,說明該View已經(jīng)調(diào)用過了draw方法了夭织。
2.如果dirtyOpaque為false說明不是透明則調(diào)用drawBackground,進行背景的繪制吠撮。
-
3.校驗是否有橫豎方向的邊緣陰影需要繪制尊惰,如果不需要則以此執(zhí)行如下三個流程:
- 1.執(zhí)行該View重寫的onDraw流程,進行繪制
- 2.dispatchDraw 把繪制行為分發(fā)到子View中
- 3.判斷是否有overlay泥兰,有則繪制每一個View的浮層的dispatchDraw
- 4.onDrawForeground 繪制View的前景drawable
- 5.drawDefaultFocusHighlight 繪制默認的焦點高亮弄屡。
-
4.如果需要繪制上下左右四個方向的滑輪,則執(zhí)行如下幾個步驟:
- 1.計算滑輪的上下左右四個方向鞋诗,根據(jù)是橫向還是豎向計算其長度
- 2.把這一塊內(nèi)容作為Canvas的非裁剪區(qū)域繪制到外面區(qū)域
- 3.執(zhí)行3.1以及3.2的步驟
- 4.根據(jù)繪制的上下左右四個方向膀捷,對滑輪進行旋轉(zhuǎn)
- 5.執(zhí)行3.3-3.5的步驟
我們關(guān)注核心行為3.1onDraw以及3.2dispatchDraw 以及drawBackground中完成了什么事情.
View drawBackground
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mThreadedRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
能看到這里面繪制的邏輯分為硬件渲染和軟件渲染:
硬件渲染首先會通過getDrawableRenderNode方法獲取一個drawable渲染的renderNode,接著調(diào)用canvas的drawRenderNode削彬。從之前我分析的TextureView一文中可以了解到硬件渲染担孔,Canvas實質(zhì)上就是DisplayListCanvas江锨。
軟件渲染則是調(diào)用draable的draw方法,把像素繪制到canvas智商糕篇。
getDrawableRenderNode
private RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) {
if (renderNode == null) {
renderNode = RenderNode.create(drawable.getClass().getName(), this);
}
final Rect bounds = drawable.getBounds();
final int width = bounds.width();
final int height = bounds.height();
final DisplayListCanvas canvas = renderNode.start(width, height);
canvas.translate(-bounds.left, -bounds.top);
try {
drawable.draw(canvas);
} finally {
renderNode.end(canvas);
}
renderNode.setLeftTopRightBottom(bounds.left, bounds.top, bounds.right, bounds.bottom);
renderNode.setProjectBackwards(drawable.isProjected());
renderNode.setProjectionReceiver(true);
renderNode.setClipToBounds(false);
return renderNode;
}
能看到在繪制一個硬件渲染的drawable對象時候啄育,會先生成一個RenderNode,調(diào)用start之后獲取Drawable對象對應DisplayListCanvas拌消,在調(diào)用drawable的draw方法挑豌,把信息繪制到DisplayListCanvas,最后返回renderNode墩崩。
再把這個drawable對應的renderNode添加到當前View的Canvas中氓英。
ViewGroup dispatchDraw
View默認是留下一個onDraw的空方法。我們看看dispatchDraw中做了什么鹦筹。
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean buildCache = !isHardwareAccelerated();
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
controller.start();
mGroupFlags &= ~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;
if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());
}
}
int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
if (clipToPadding) {
clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
// We will draw our child's animation, let's reset the flag
mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
boolean more = false;
final long drawingTime = getDrawingTime();
if (usingRenderNodeProperties) canvas.insertReorderBarrier();
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
int transientIndex = transientCount != 0 ? 0 : -1;
final ArrayList<View> preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
while (transientIndex >= 0) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
break;
}
}
if (preorderedList != null) preorderedList.clear();
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
if (usingRenderNodeProperties) canvas.insertInorderBarrier();
if (clipToPadding) {
canvas.restoreToCount(clipSaveCount);
}
flags = mGroupFlags;
if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
invalidate(true);
}
if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
mLayoutAnimationController.isDone() && !more) {
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
final Runnable end = new Runnable() {
@Override
public void run() {
notifyAnimationListener();
}
};
post(end);
}
}
這個過程中做了如下幾件事情:
- 1.判斷是否打開了FLAG_RUN_ANIMATION標志位铝阐,且允許Layout動畫。首先遍歷該viewGroup中所有子View中所有的可見的子View铐拐,并且bindLayoutAnimation設置好每一個子View對應的Layout動畫徘键。
private void bindLayoutAnimation(View child) {
Animation a = mLayoutAnimationController.getAnimationForView(child);
child.setAnimation(a);
}
- LayoutAnimationController 控制Layout動畫的控制者調(diào)用start啟動動畫,并且回調(diào)監(jiān)聽遍蟋。
3.判斷是否被padding裁剪內(nèi)容區(qū)域吹害,默認是開啟的。這個情況下虚青,則會滑動的區(qū)域它呀,padding區(qū)域,以及viewgroup的區(qū)域棒厘,進行裁剪纵穿,而不是統(tǒng)統(tǒng)都畫到Canvas中。
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
4.判斷是否打開了硬件加速奢人。如果沒有政恍,buildOrderedChildList對當前該ViewGroup下所有子View進行z軸上的插入排序,從而得知誰將繪制在更加更加上方达传。這個過程中z軸上的數(shù)值越小篙耗,越先調(diào)用drawChild方法繪制到canvas中,也就是層級越低宪赶,被其他子View覆蓋在其上宗弯。在處理每一個孩子對應的drawChild方法之前,會先處理通過addTransientView添加進來的臨時View搂妻。這種方式十分少見蒙保,你可以看成臨時動畫一樣的效果,不參加view的onmeasure欲主,onLayout邓厕,但是會繪制出來逝嚎。需要手動的remove掉。
5.繪制通過addDisappearingView添加的消失臨時View详恼。
6.如果FLAG_NOTIFY_ANIMATION_LISTENER补君,F(xiàn)LAG_ANIMATION_DONE標志位都關(guān)閉了,同時Layout動畫也完成了昧互,所有的drawChild都返回了false挽铁,則在下一個Looper中開始時機調(diào)用notifyAnimationListener,通知監(jiān)聽者本次動畫已經(jīng)完成敞掘。
整個核心都是drawChild方法叽掘,我們來看看drawChild做了什么?
drawChild
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
核心調(diào)用了view的draw方法玖雁。但是注意了更扁,這個draw和上面那個draw方法不太一樣。
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
boolean more = false;
final boolean childHasIdentityMatrix = hasIdentityMatrix();
final int parentFlags = parent.mGroupFlags;
if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) {
parent.getChildTransformation().clear();
parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
final Animation a = getAnimation();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
} else {
if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) != 0) {
// No longer animating: clear out old animation matrix
mRenderNode.setAnimationMatrix(null);
mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
if (!drawingWithRenderNode
&& (parentFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
final Transformation t = parent.getChildTransformation();
final boolean hasTransform = parent.getChildStaticTransformation(this, t);
if (hasTransform) {
final int transformType = t.getTransformationType();
transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
}
}
}
concatMatrix |= !childHasIdentityMatrix;
// Sets the flag as early as possible to allow draw() implementations
// to call invalidate() successfully when doing animations
mPrivateFlags |= PFLAG_DRAWN;
if (!concatMatrix &&
(parentFlags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS |
ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN &&
canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) &&
(mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) {
mPrivateFlags2 |= PFLAG2_VIEW_QUICK_REJECTED;
return more;
}
mPrivateFlags2 &= ~PFLAG2_VIEW_QUICK_REJECTED;
if (hardwareAcceleratedCanvas) {
// Clear INVALIDATED flag to allow invalidation to occur during rendering, but
// retain the flag's value temporarily in the mRecreateDisplayList flag
mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
mPrivateFlags &= ~PFLAG_INVALIDATED;
}
RenderNode renderNode = null;
Bitmap cache = null;
int layerType = getLayerType(); // TODO: signify cache state with just 'cache' local
if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
if (layerType != LAYER_TYPE_NONE) {
// If not drawing with RenderNode, treat HW layers as SW
layerType = LAYER_TYPE_SOFTWARE;
buildDrawingCache(true);
}
cache = getDrawingCache(true);
}
if (drawingWithRenderNode) {
renderNode = updateDisplayListIfDirty();
if (!renderNode.isValid()) {
renderNode = null;
drawingWithRenderNode = false;
}
}
int sx = 0;
int sy = 0;
if (!drawingWithRenderNode) {
computeScroll();
sx = mScrollX;
sy = mScrollY;
}
final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
final boolean offsetForScroll = cache == null && !drawingWithRenderNode;
int restoreTo = -1;
if (!drawingWithRenderNode || transformToApply != null) {
restoreTo = canvas.save();
}
if (offsetForScroll) {
canvas.translate(mLeft - sx, mTop - sy);
} else {
if (!drawingWithRenderNode) {
canvas.translate(mLeft, mTop);
}
if (scalingRequired) {
if (drawingWithRenderNode) {
// TODO: Might not need this if we put everything inside the DL
restoreTo = canvas.save();
}
// mAttachInfo cannot be null, otherwise scalingRequired == false
final float scale = 1.0f / mAttachInfo.mApplicationScale;
canvas.scale(scale, scale);
}
}
float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
if (transformToApply != null
|| alpha < 1
|| !hasIdentityMatrix()
|| (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
if (transformToApply != null || !childHasIdentityMatrix) {
int transX = 0;
int transY = 0;
if (offsetForScroll) {
transX = -sx;
transY = -sy;
}
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
if (!childHasIdentityMatrix && !drawingWithRenderNode) {
canvas.translate(-transX, -transY);
canvas.concat(getMatrix());
canvas.translate(transX, transY);
}
}
if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
if (alpha < 1) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA;
} else {
mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA;
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
if (!drawingWithDrawingCache) {
final int multipliedAlpha = (int) (255 * alpha);
if (!onSetAlpha(multipliedAlpha)) {
if (drawingWithRenderNode) {
renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
} else if (layerType == LAYER_TYPE_NONE) {
canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),
multipliedAlpha);
}
} else {
mPrivateFlags |= PFLAG_ALPHA_SET;
}
}
}
} else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
onSetAlpha(255);
mPrivateFlags &= ~PFLAG_ALPHA_SET;
}
if (!drawingWithRenderNode) {
if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
if (offsetForScroll) {
canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
} else {
if (!scalingRequired || cache == null) {
canvas.clipRect(0, 0, getWidth(), getHeight());
} else {
canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
}
}
}
if (mClipBounds != null) {
// clip bounds ignore scroll
canvas.clipRect(mClipBounds);
}
}
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
}
} else if (cache != null) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
Paint cachePaint = parent.mCachePaint;
if (cachePaint == null) {
cachePaint = new Paint();
cachePaint.setDither(false);
parent.mCachePaint = cachePaint;
}
cachePaint.setAlpha((int) (alpha * 255));
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
} else {
int layerPaintAlpha = mLayerPaint.getAlpha();
if (alpha < 1) {
mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
}
canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
if (alpha < 1) {
mLayerPaint.setAlpha(layerPaintAlpha);
}
}
}
if (restoreTo >= 0) {
canvas.restoreToCount(restoreTo);
}
if (a != null && !more) {
if (!hardwareAcceleratedCanvas && !a.getFillAfter()) {
onSetAlpha(255);
}
parent.finishAnimatingView(this, a);
}
if (more && hardwareAcceleratedCanvas) {
if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
invalidate(true);
}
}
mRecreateDisplayList = false;
return more;
}
上面這個方法做了一個很重要的事情赫冬,那就是繪制每一個View的緩存浓镜。在這個過程中有兩個比較重要的標志位:
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
drawingWithRenderNode判斷是否需要通過硬件繪制RenderNode。
final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
drawingWithDrawingCache是否繪制view中的緩存由2點決定面殖,一個是關(guān)閉硬件渲染,另一個是緩存的bitmap不為空哭廉。
做到的事情如下:
1.首先判斷View中是否通過setAnimation設置了一個變換矩陣Transformation動畫到View中脊僚。如果有,則調(diào)用applyLegacyAnimation方法確定整個變化動畫刷新的范圍在這個View范圍內(nèi)遵绰;transformToApply設置為父容器的Transformation泵三。如果沒有設置Animation珊蟀,則判斷是否關(guān)閉了硬件渲染且打開了FLAG_SUPPORT_STATIC_TRANSFORMATIONS。也就是說當前的View的父容器是否存在一個靜態(tài)的變換矩陣,存在則更新到transformToApply中查乒。
2.如果mLayerType為LAYER_TYPE_SOFTWARE或者關(guān)閉了硬件渲染,說明是一個軟件渲染绿渣,則調(diào)用buildDrawingCache構(gòu)建一個繪制的緩存速种,通過getDrawingCache獲取這個緩存bitmap到cache中。
3.如果drawingWithRenderNode為true哭当,說明在使用硬件渲染猪腕。則調(diào)用updateDisplayListIfDirty更新DisplayList中的臟區(qū)。
4.drawingWithRenderNode為false钦勘,則調(diào)用computeScroll計算滑動區(qū)域陋葡,賦值給sx和sy。
5.如果存在一個變換矩陣或者關(guān)閉硬件渲染彻采,則調(diào)用canvas的save方法保存當前的狀態(tài)腐缤。如果offsetForScroll為true(說明此時是軟件渲染同時沒有緩存)捌归,則調(diào)用canvas的translate方法參數(shù)第四步驟中計算出來平移距離。
橫向平移:mLeft - sx
縱向平移: mTop - sy
這么做可以把繪制的原點移動到平移 平移后的位置岭粤,之后所有的繪制都是基于這個點進行的
6.offsetForScroll為false惜索,說明此時可能需要繪制緩存或者是硬件渲染,只做了兩件事情:平移當前的畫布的繪制原點绍在,如果需要則對整個畫布進行伸縮门扇。
7.如果transformToApply不為空,前提下偿渡。發(fā)現(xiàn)打開了硬件渲染臼寄,則調(diào)用RenderNode.setAnimationMatrix方法設置動畫的矩陣。如果關(guān)閉溜宽,則先回退經(jīng)過平移的Canvas原點吉拳,先對動畫矩陣進行合并后在進行滑動的移動。
8.如果alpha小于1适揉,且判斷到drawingWithDrawingCache關(guān)閉的留攒。則說明可以在當前的繪制結(jié)果中進行透明度處理。判斷onSetAlpha為false嫉嘀,如果是硬件渲染則調(diào)用renderNode.setAlpha炼邀,如果是軟件渲染則調(diào)用canvas.saveLayerAlpha。如果繪制緩存是打開剪侮。onSetAlpha如果為true說明整個透明是由子View決定的拭宁,因此先打開PFLAG_ALPHA_SET標志位,等待后續(xù)的處理瓣俯。
9.如果父容器打開了FLAG_CLIP_CHILDREN標志位且當前的View沒有緩存杰标。說明當前的View繪制的結(jié)果需要被父容器裁剪了:
如果進行了滑動,則裁剪區(qū)域如下:
canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
如果沒有緩存彩匕,則直接裁剪當前的View
canvas.clipRect(0, 0, getWidth(), getHeight());
有緩存:
canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
如果需要根據(jù)邊緣進行裁剪:
canvas.clipRect(mClipBounds);
10.drawingWithDrawingCache如果是關(guān)閉腔剂,且drawingWithRenderNode是打開的,則調(diào)用DisplayListCanvas.drawRenderNode(renderNode) 方法驼仪。參數(shù)中的renderNode是updateDisplayListIfDirty方法生成一個新的DisplayListCanvas掸犬。通過drawRenderNode把結(jié)果繪制到父容器的DisplayListCanvas。
11.drawingWithDrawingCache關(guān)閉绪爸,drawingWithRenderNode也是關(guān)閉的登渣。說明此時是直接進行繪制。如果打開了PFLAG_SKIP_DRAW標志位說明需要直接掉過當前的View毡泻,直接調(diào)用dispatchDraw分發(fā)View的繪制命令胜茧。如果沒有打開,則調(diào)用draw方法。就會繼續(xù)調(diào)用onDraw后并且dispatchDraw分發(fā)View的繪制方法呻顽。
12.drawingWithDrawingCache是打開的雹顺,同時cache緩存不為空,則把cache中的結(jié)果繪制到Canvas中廊遍。
- 13.當所有子View都繪制結(jié)束之后嬉愧,則調(diào)用canvas.restoreToCount方法一層層的恢復繪制狀態(tài)。主要還是恢復繪制原點喉前。調(diào)用父容器的finishAnimatingView清空所有的Animation以及disappearingAnimation没酣,回調(diào)onAnimationEnd。
在這幾點中卵迂,除去Canvas操作(關(guān)于Canvas操作裕便,我會專門開一個Skia源碼解析專題進行分析)有幾個比較重要的方法:
- 1.buildDrawingCache 構(gòu)建一個繪制緩存對象
- 2.updateDisplayListIfDirty 硬件渲染更新臟區(qū)
buildDrawingCache
public void buildDrawingCache(boolean autoScale) {
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
mDrawingCache == null : mUnscaledDrawingCache == null)) {
try {
buildDrawingCacheImpl(autoScale);
} finally {
}
}
}
能看到實際上就是調(diào)用buildDrawingCacheImpl.
buildDrawingCacheImpl
private void buildDrawingCacheImpl(boolean autoScale) {
mCachingFailed = false;
int width = mRight - mLeft;
int height = mBottom - mTop;
final AttachInfo attachInfo = mAttachInfo;
final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;
if (autoScale && scalingRequired) {
width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
}
final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;
final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
final long drawingCacheSize =
ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
if (width > 0 && height > 0) {
Log.w(VIEW_LOG_TAG, getClass().getSimpleName() + " not displayed because it is"
+ " too large to fit into a software layer (or drawing cache), needs "
+ projectedBitmapSize + " bytes, only "
+ drawingCacheSize + " available");
}
destroyDrawingCache();
mCachingFailed = true;
return;
}
boolean clear = true;
Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;
if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
Bitmap.Config quality;
if (!opaque) {
// Never pick ARGB_4444 because it looks awful
// Keep the DRAWING_CACHE_QUALITY_LOW flag just in case
switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
case DRAWING_CACHE_QUALITY_AUTO:
case DRAWING_CACHE_QUALITY_LOW:
case DRAWING_CACHE_QUALITY_HIGH:
default:
quality = Bitmap.Config.ARGB_8888;
break;
}
} else {
quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
}
if (bitmap != null) bitmap.recycle();
try {
bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
width, height, quality);
bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
if (autoScale) {
mDrawingCache = bitmap;
} else {
mUnscaledDrawingCache = bitmap;
}
if (opaque && use32BitCache) bitmap.setHasAlpha(false);
} catch (OutOfMemoryError e) {
if (autoScale) {
mDrawingCache = null;
} else {
mUnscaledDrawingCache = null;
}
mCachingFailed = true;
return;
}
clear = drawingCacheBackgroundColor != 0;
}
Canvas canvas;
if (attachInfo != null) {
canvas = attachInfo.mCanvas;
if (canvas == null) {
canvas = new Canvas();
}
canvas.setBitmap(bitmap);
attachInfo.mCanvas = null;
} else {
canvas = new Canvas(bitmap);
}
if (clear) {
bitmap.eraseColor(drawingCacheBackgroundColor);
}
computeScroll();
final int restoreCount = canvas.save();
if (autoScale && scalingRequired) {
final float scale = attachInfo.mApplicationScale;
canvas.scale(scale, scale);
}
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN;
if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
}
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
} else {
draw(canvas);
}
canvas.restoreToCount(restoreCount);
canvas.setBitmap(null);
if (attachInfo != null) {
attachInfo.mCanvas = canvas;
}
}
- 1.在創(chuàng)建繪制的緩存bitmap之前,如果當前View的寬高其中之一小于等于0见咒,或者當前View需要內(nèi)存大于最大允許的緩存View大小偿衰。
這個過程中,View繪制后的緩存計算方法如下:
projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
能看到這個過程中判斷是否需要透明且關(guān)閉32的緩存改览,一個像素就會2位下翎,否則則是4位。
如果計算出來的結(jié)果比MAXIMUM_DRAWING_CACHE_SIZE大則銷毀繪制緩存宝当。
private static final int MAXIMUM_DRAWING_CACHE_SIZE = 480 * 800 * 4; // ARGB8888
能看到每一個View最大只能是由寬480视事,高800且是ARGB8888模式內(nèi)存大小。
2.如果View的大小發(fā)生了變化庆揩,則調(diào)用Bitmap.createBitmap的方法創(chuàng)建一個對應View大小的Bitmap俐东。
3.如果attachInfo不為空,則判斷是否存在一個全局的Canvas盾鳞,如果不存在就創(chuàng)建一個新的Canvas犬性,并把bitmap設置到Canvas中瞻离。
4.如果需要進行伸縮腾仅,則伸縮緩存bitmap。如果需要滑動套利,則移動整個Canvas的繪制原點推励。
5.如果PFLAG_SKIP_DRAW 打開了,則直接調(diào)用dispatchDraw肉迫,繼續(xù)分發(fā)繪制流程验辞。關(guān)閉了則調(diào)用draw方法,先調(diào)用onDraw后調(diào)用dispatchDraw喊衫。
能看到在上面draw方法中因為檢測存在繪制緩存而跳過的流程跌造,在這個方法中都進行處理了。
updateDisplayListIfDirty
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
if (!canHaveDisplayList()) {
return renderNode;
}
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.isValid()
|| (mRecreateDisplayList)) {
if (renderNode.isValid()
&& !mRecreateDisplayList) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
return renderNode; // no work needed
}
mRecreateDisplayList = true;
int width = mRight - mLeft;
int height = mBottom - mTop;
int layerType = getLayerType();
final DisplayListCanvas canvas = renderNode.start(width, height);
try {
if (layerType == LAYER_TYPE_SOFTWARE) {
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
} else {
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
if (debugDraw()) {
debugDrawFocus(canvas);
}
} else {
draw(canvas);
}
}
} finally {
renderNode.end(canvas);
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
- 1.如果canHaveDisplayList為 false也就是mThreadedRenderer為null,則直接返回壳贪。
如果PFLAG_DRAWING_CACHE_VALID關(guān)閉陵珍,或者renderNode是無效的,或者mRecreateDisplayList是false违施。則進入到RenderNode的Canvas繪制中互纯。
2.如果renderNode是有效的,且不需要進行重新構(gòu)建整個硬件渲染的DisplayList(mRecreateDisplayList為false)磕蒲,說明不是第一次繪制了已經(jīng)有繪制結(jié)果了留潦,則調(diào)用dispatchGetDisplayList。
-
3.接下來的情景說明是第一次繪制辣往,renderNode還是屬于無效狀態(tài)兔院。其初始化流程如下:
- 1.renderNode.start(width, height) 調(diào)用start方法創(chuàng)建一個全新的DisplayListCanvas
- 2.如果LayerType是LAYER_TYPE_SOFTWARE,就算是Layer的模式是軟件渲染模式排吴,如果打開了硬件渲染模式秆乳,還是會把當前View對應繪制緩存bitmap通過setBitmap的方式設置到DisplayListCanvas中。
- 3.如果不是LAYER_TYPE_SOFTWARE钻哩,調(diào)用computeScroll進行滑動計算后屹堰。把整個RenderNode的繪制原點退回到滑動之前的狀態(tài),并且打上PFLAG_DRAWN和PFLAG_DRAWING_CACHE_VALID兩個標志位街氢。
- 4.如果打開PFLAG_SKIP_DRAW扯键,則直接調(diào)用dispatchDraw,分發(fā)繪制流程珊肃。
- 5.沒有打開PFLAG_SKIP_DRAW荣刑,則直接調(diào)用draw方法,先回調(diào)onDraw再調(diào)用dispatchDraw進行分發(fā)伦乔。
4.最后調(diào)用renderNode.end(canvas) 結(jié)束整個RenderNode的繪制厉亏。
我們來看看非第一次繪制時候dispatchGetDisplayList做了什么?
ViewGroup dispatchGetDisplayList
protected void dispatchGetDisplayList() {
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
recreateChildDisplayList(child);
}
}
final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View child = mTransientViews.get(i);
if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
recreateChildDisplayList(child);
}
}
if (mOverlay != null) {
View overlayView = mOverlay.getOverlayView();
recreateChildDisplayList(overlayView);
}
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size();
for (int i = 0; i < disappearingCount; ++i) {
final View child = disappearingChildren.get(i);
recreateChildDisplayList(child);
}
}
}
能看到實際上很簡單烈和,對4種View進行recreateChildDisplayList處理爱只。
- 1.所有的可見子View或者帶著動畫的子View
- 2.mTransientViews 通過addTransientView添加進來的臨時View
- 3.overlayView 每一個View的浮層
- 4.mDisappearingChildren 通過addDisappearingView 添加進來的當View移除時候需要的動畫View。
ViewGroup recreateChildDisplayList
private void recreateChildDisplayList(View child) {
child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
child.mPrivateFlags &= ~PFLAG_INVALIDATED;
child.updateDisplayListIfDirty();
child.mRecreateDisplayList = false;
}
能看到這個過程實際上就是調(diào)用了子View的updateDisplayListIfDirty方法招刹。
pendingDrawFinished
當一切都處理完畢之后恬试,就會調(diào)用pendingDrawFinished。如果mDrawsNeededToReport計數(shù)為0疯暑,則說明所有需要繪制的命令全部完成了训柴。最后調(diào)用reportDrawFinished。
void pendingDrawFinished() {
if (mDrawsNeededToReport == 0) {
throw new RuntimeException("Unbalanced drawPending/pendingDrawFinished calls");
}
mDrawsNeededToReport--;
if (mDrawsNeededToReport == 0) {
reportDrawFinished();
}
}
reportDrawFinished
private void reportDrawFinished() {
try {
mDrawsNeededToReport = 0;
mWindowSession.finishDrawing(mWindow);
} catch (RemoteException e) {
// Have fun!
}
}
能看到最后會調(diào)用reportDrawFinished妇拯,通知WindowSession已經(jīng)finishDrawing幻馁。
public void finishDrawing(IWindow window) {
if (WindowManagerService.localLOGV) Slog.v(
TAG_WM, "IWindow finishDrawing called for " + window);
mService.finishDrawingWindow(this, window);
}
WMS finishDrawingWindow
void finishDrawingWindow(Session session, IWindow client) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
if (win != null && win.mWinAnimator.finishDrawingLocked()) {
if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
win.getDisplayContent().pendingLayoutChanges |=
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
}
win.setDisplayLayoutNeeded();
mWindowPlacerLocked.requestTraversal();
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
會調(diào)用WindowState的setDisplayLayoutNeeded。設置DisplayContent的mLayoutNeeded為true。
調(diào)用WindowSurfacePlacer的requestTraversal仗嗦。而這個方法會在AnimationHandler 窗體動畫的handler中調(diào)用performSurfacePlacement预麸。而這里的邏輯可以閱讀WMS在Activity啟動中的職責 計算窗體的大小
總結(jié)
到這列就完成了onDraw的解析。從onMeasure儒将,onLayout吏祸,onDraw四個流程已經(jīng)過了一遍。但是還沒有仔細聊聊硬件渲染钩蚊,但是沒關(guān)系贡翘,從軟件渲染也能一窺整個核心流程了。
老規(guī)矩砰逻,先來一副時序圖:
整個onDraw的入口依次執(zhí)行了如下的方法:
- 1.執(zhí)行該View的background 背景的繪制
- 2.執(zhí)行該View重寫的onDraw流程鸣驱,進行繪制
- 3.dispatchDraw 把繪制行為分發(fā)到子View中
- 4.判斷是否有overlay,有則繪制每一個View的浮層的dispatchDraw
- 5.onDrawForeground 繪制View的前景drawable
- 6.drawDefaultFocusHighlight 繪制默認的焦點高亮蝠咆。
每一次進行一次onDraw之前對dirtyOpaque標志位進行判斷踊东,實際上就是判斷是否是透明的,是透明的就不會調(diào)用該View的onDraw方法刚操。
其中dispatchDraw進行繪制行為分發(fā)后闸翅,就會調(diào)用drawChild的方法會每一個子View的draw方法。同時會通過buildOrderedChildList插入排序計算z軸上的繪制的順序菊霜,保證z軸上會根據(jù)大小順序調(diào)用drawChild進行覆蓋繪制坚冀。
這個過程中,軟件渲染過程中會伴隨著繪制一個Bitmap的緩存鉴逞。每一個View都能夠申請到的緩存最大數(shù)值就是:
(寬/高)480 * (寬/高)800 * 4(ARGB8888)
如果超出這個數(shù)值就不會出現(xiàn)緩存记某,或者直到OOM了也會銷毀緩存。
當執(zhí)行完畢之后构捡,必定會調(diào)用WMS的finishDrawingWindow液南,告訴WMS ViewRootImpl已經(jīng)完成了繪制工作。這個方法會設置DisplayContent的mLayoutNeeded為true勾徽。這樣就能告訴WMS滑凉,當下一輪WMS的relayoutWindow對窗體進行重新測量的時候,允許遍歷DisplayContent所有的內(nèi)容窗體(詳細的可以看看我寫的WMS在Activity啟動中的職責 計算窗體的大小)捂蕴。
本文已經(jīng)涉及到不少關(guān)于硬件渲染的邏輯譬涡,下一篇就來聊聊硬件繪制的原理闪幽。