Android源碼進(jìn)階之深入理解View的繪制流程(Draw)機(jī)制
前言
前幾篇文章,講述了measure,layout流程等,接下來(lái)將詳細(xì)分析繪制流程。
測(cè)量流程決定了View的大小,布局流程決定了View的位置,那么繪制流程將決定View的樣子,一個(gè)View該顯示什么由繪制流程完成;
那我們就開始開車了;
一、performDraw
三大工作流程始于ViewRootImpl#performTraversals,在這個(gè)方法內(nèi)部會(huì)分別調(diào)用performMeasure,performLayout,performDraw三個(gè)方法來(lái)分別完成測(cè)量,布局,繪制流程。那么我們現(xiàn)在先從performDraw方法看起;
performDraw
- private void performDraw() {
- //...
- final boolean fullRedrawNeeded = mFullRedrawNeeded;
- try {
- draw(fullRedrawNeeded);
- } finally {
- mIsDrawing = false;
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
- }
里面又調(diào)用了ViewRootImpl#draw方法,我們來(lái)看看ViewRootImpl#draw:
- private void draw(boolean fullRedrawNeeded) {
- ...
- //獲取mDirty,該值表示需要重繪的區(qū)域
- final Rect dirty = mDirty;
- if (mSurfaceHolder != null) {
- // The app owns the surface, we won't draw.
- dirty.setEmpty();
- if (animating) {
- if (mScroller != null) {
- mScroller.abortAnimation();
- }
- disposeResizeBuffer();
- }
- return;
- }
- //如果fullRedrawNeeded為真,則把dirty區(qū)域置為整個(gè)屏幕,表示整個(gè)視圖都需要繪制
- //第一次繪制流程,需要繪制所有視圖
- if (fullRedrawNeeded) {
- mAttachInfo.mIgnoreDirtyState = true;
- dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
- }
- //...
- if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
- return;
- }
- }
根據(jù)fullRedrawNeeded來(lái)判斷是否需要重置dirty區(qū)域,最后調(diào)用了ViewRootImpl#drawSoftware方法,并把相關(guān)參數(shù)傳遞進(jìn)去,包括dirty區(qū)域,我們接著看該方法的源碼;
- 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;
- //鎖定canvas區(qū)域,由dirty區(qū)域決定
- 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;
- }
- canvas.setDensity(mDensity);
- }
- try {
- if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
- canvas.drawColor(0, PorterDuff.Mode.CLEAR);
- }
- dirty.setEmpty();
- mIsAnimating = false;
- attachInfo.mDrawingTime = SystemClock.uptimeMillis();
- 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);
- }
- }
- return true;
- }
實(shí)例化了Canvas對(duì)象,然后鎖定該canvas的區(qū)域,由dirty區(qū)域決定,接著對(duì)canvas進(jìn)行一系列的屬性賦值,最后調(diào)用了mView.draw(canvas)方法,
mView就是DecorView,也就是說(shuō)從DecorView開始繪制;
二、draw源碼詳解
由于ViewGroup沒(méi)有重寫draw方法,因此所有的View都是調(diào)用View#draw方法,因此,我們直接看它的源碼
- public void draw(Canvas canvas) {
- ....
- // 1. 繪制本身View背景
- if (!dirtyOpaque) {
- drawBackground(canvas);
- }
- if (!verticalEdges && !horizontalEdges) {
- // Step 3, draw the content
- // 2. 繪制內(nèi)容,默認(rèn)空實(shí)現(xiàn) 需復(fù)寫
- if (!dirtyOpaque) onDraw(canvas);
- // 3. 繪制 children
- dispatchDraw(canvas);
- drawAutofilledHighlight(canvas);
- // 4. 分發(fā)Draw (單一View空實(shí)現(xiàn),ViewGroup見下面分析)
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().dispatchDraw(canvas);
- }
- // 5. 繪制裝飾 (前景色,滾動(dòng)條)
- onDrawForeground(canvas);
- return;
- }
- ....
- }
可以看到,draw過(guò)程比較復(fù)雜,但是邏輯十分清晰。首先來(lái)看一開始的標(biāo)記位dirtyOpaque,
該標(biāo)記位的作用是判斷當(dāng)前View是否是透明的,如果View是透明的,那么根據(jù)下面的邏輯可以看出,將不會(huì)執(zhí)行一些步驟,比如繪制背景、繪制內(nèi)容等;
繪制流程的五個(gè)步驟:
- 對(duì)View的背景進(jìn)行繪制;
- 繪制View的內(nèi)容;
- 對(duì)View的子View進(jìn)行繪制(如果有子View);
- 分發(fā)Draw;
繪制View的裝飾(例如:前景色,滾動(dòng)條);
1、繪制背景
- //繪制背景
- private void drawBackground(Canvas canvas) {
- final Drawable background = mBackground;
- if (background == null) {
- return;
- }
- // 根據(jù)在 layout 過(guò)程中獲取的 View 的位置參數(shù),來(lái)設(shè)置背景的邊界
- setBackgroundBounds();
- // 先嘗試用HWUI繪制
- 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) {
- //調(diào)用 Drawable 的 draw 方法來(lái)進(jìn)行背景的繪制
- background.draw(canvas);
- } else {
- // 若 mScrollX 和 mScrollY 有值,則對(duì) canvas 的坐標(biāo)進(jìn)行偏移平移畫布
- canvas.translate(scrollX, scrollY);
- //調(diào)用 Drawable 的 draw 方法來(lái)進(jìn)行背景的繪制
- background.draw(canvas);
- canvas.translate(-scrollX, -scrollY);
- }
- }
2、繪制View的內(nèi)容
- // 繪制View本身內(nèi)容,空實(shí)現(xiàn),子類必須復(fù)寫
- protected void onDraw(Canvas canvas) {
- }
這里調(diào)用了View#onDraw方法,View中該方法是一個(gè)空實(shí)現(xiàn),因?yàn)椴煌腣iew有著不同的內(nèi)容,這需要我們自己去實(shí)現(xiàn),即在自定義View中重寫該方法來(lái)實(shí)現(xiàn);
3、子View進(jìn)行繪制
當(dāng)前的View是一個(gè)ViewGroup類型,那么就需要繪制它的子View,這里調(diào)用了dispatchDraw,而View中該方法是空實(shí)現(xiàn),實(shí)際是ViewGroup重寫了這個(gè)方法,那么我們來(lái)看看;
- @Override
- protected void dispatchDraw(Canvas canvas) {
- ...
- // 遍歷子View
- final int childrenCount = mChildrenCount;
- ...
- 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) {
- // 調(diào)用 drawChild 方法,進(jìn)行子元素繪制
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- ....
- }
4、分發(fā)Draw
- @Override
- protected void dispatchDraw(Canvas canvas) {
- ...
- // 1. 遍歷子View
- final int childrenCount = mChildrenCount;
- ...
- 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) {
- // 調(diào)用 drawChild 方法,進(jìn)行子元素繪制
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- ....
- }
5、繪制View
所謂的繪制裝飾,就是指View除了背景、內(nèi)容、子View的其余部分,例如滾動(dòng)條等,我們看View#onDrawForeground
- public void onDrawForeground(Canvas canvas) {
- //繪制指示器
- onDrawScrollIndicators(canvas);
- //繪制滾動(dòng)條
- onDrawScrollBars(canvas);
- final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
- if (foreground != null) {
- if (mForegroundInfo.mBoundsChanged) {
- mForegroundInfo.mBoundsChanged = false;
- final Rect selfBounds = mForegroundInfo.mSelfBounds;
- final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
- if (mForegroundInfo.mInsidePadding) {
- selfBounds.set(0, 0, getWidth(), getHeight());
- } else {
- selfBounds.set(getPaddingLeft(), getPaddingTop(),
- getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
- }
- final int ld = getLayoutDirection();
- Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
- foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
- foreground.setBounds(overlayBounds);
- }
- //調(diào)用 Drawable 的 draw 方法,繪制前景色
- foreground.draw(canvas);
- }
- }
到目前為止,View的繪制流程也講述完畢了;
總結(jié)
其實(shí)繪制這塊還是很重要的,下次還是要繼續(xù)講解下;
學(xué)如逆水行舟,不進(jìn)則退;心似平原走馬,易放難收;
一起加油老鐵們
本文轉(zhuǎn)載自微信公眾號(hào)「Android開發(fā)編程」
【編輯推薦】