自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Android源碼進(jìn)階之深入理解View的繪制流程(Draw)機(jī)制

開發(fā) 前端
三大工作流程始于ViewRootImpl#performTraversals,在這個(gè)方法內(nèi)部會(huì)分別調(diào)用performMeasure,performLayout,performDraw三個(gè)方法來(lái)分別完成測(cè)量,布局,繪制流程。那么我們現(xiàn)在先從performDraw方法看起。

[[426758]]

前言

前幾篇文章,講述了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

  1. private void performDraw() { 
  2.     //... 
  3.     final boolean fullRedrawNeeded = mFullRedrawNeeded; 
  4.     try { 
  5.         draw(fullRedrawNeeded); 
  6.     } finally { 
  7.         mIsDrawing = false
  8.         Trace.traceEnd(Trace.TRACE_TAG_VIEW); 
  9.     } 

里面又調(diào)用了ViewRootImpl#draw方法,我們來(lái)看看ViewRootImpl#draw:

  1. private void draw(boolean fullRedrawNeeded) { 
  2.     ... 
  3.     //獲取mDirty,該值表示需要重繪的區(qū)域 
  4.     final Rect dirty = mDirty; 
  5.     if (mSurfaceHolder != null) { 
  6.         // The app owns the surface, we won't draw. 
  7.         dirty.setEmpty(); 
  8.         if (animating) { 
  9.             if (mScroller != null) { 
  10.                 mScroller.abortAnimation(); 
  11.             } 
  12.             disposeResizeBuffer(); 
  13.         } 
  14.         return
  15.     } 
  16.     //如果fullRedrawNeeded為真,則把dirty區(qū)域置為整個(gè)屏幕,表示整個(gè)視圖都需要繪制 
  17.     //第一次繪制流程,需要繪制所有視圖 
  18.     if (fullRedrawNeeded) { 
  19.         mAttachInfo.mIgnoreDirtyState = true
  20.         dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); 
  21.     } 
  22.     //... 
  23.     if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { 
  24.                 return
  25.         } 

根據(jù)fullRedrawNeeded來(lái)判斷是否需要重置dirty區(qū)域,最后調(diào)用了ViewRootImpl#drawSoftware方法,并把相關(guān)參數(shù)傳遞進(jìn)去,包括dirty區(qū)域,我們接著看該方法的源碼;

  1. private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, 
  2.             boolean scalingRequired, Rect dirty) { 
  3.     // Draw with software renderer. 
  4.     final Canvas canvas; 
  5.     try { 
  6.         final int left = dirty.left
  7.         final int top = dirty.top
  8.         final int right = dirty.right
  9.         final int bottom = dirty.bottom; 
  10.         //鎖定canvas區(qū)域,由dirty區(qū)域決定 
  11.         canvas = mSurface.lockCanvas(dirty); 
  12.         // The dirty rectangle can be modified by Surface.lockCanvas() 
  13.         //noinspection ConstantConditions 
  14.         if (left != dirty.left || top != dirty.top || right != dirty.right 
  15.                 || bottom != dirty.bottom) { 
  16.             attachInfo.mIgnoreDirtyState = true
  17.         } 
  18.         canvas.setDensity(mDensity); 
  19.     }  
  20.     try { 
  21.         if (!canvas.isOpaque() || yoff != 0 || xoff != 0) { 
  22.             canvas.drawColor(0, PorterDuff.Mode.CLEAR); 
  23.         } 
  24.         dirty.setEmpty(); 
  25.         mIsAnimating = false
  26.         attachInfo.mDrawingTime = SystemClock.uptimeMillis(); 
  27.         mView.mPrivateFlags |= View.PFLAG_DRAWN; 
  28.         try { 
  29.             canvas.translate(-xoff, -yoff); 
  30.             if (mTranslator != null) { 
  31.                 mTranslator.translateCanvas(canvas); 
  32.             } 
  33.             canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); 
  34.             attachInfo.mSetIgnoreDirtyState = false
  35.             //正式開始繪制 
  36.             mView.draw(canvas); 
  37.         } 
  38.     }  
  39.     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方法,因此,我們直接看它的源碼

  1. public void draw(Canvas canvas) { 
  2.     ....  
  3.     // 1. 繪制本身View背景 
  4.     if (!dirtyOpaque) { 
  5.         drawBackground(canvas); 
  6.     } 
  7.     if (!verticalEdges && !horizontalEdges) { 
  8.         // Step 3, draw the content 
  9.         // 2. 繪制內(nèi)容,默認(rèn)空實(shí)現(xiàn) 需復(fù)寫 
  10.         if (!dirtyOpaque) onDraw(canvas); 
  11.         // 3. 繪制 children  
  12.         dispatchDraw(canvas); 
  13.         drawAutofilledHighlight(canvas); 
  14.         // 4. 分發(fā)Draw (單一View空實(shí)現(xiàn),ViewGroup見下面分析) 
  15.         if (mOverlay != null && !mOverlay.isEmpty()) { 
  16.             mOverlay.getOverlayView().dispatchDraw(canvas); 
  17.         } 
  18.         // 5. 繪制裝飾 (前景色,滾動(dòng)條) 
  19.         onDrawForeground(canvas); 
  20.         return
  21.     } 
  22.     .... 

可以看到,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、繪制背景

  1. //繪制背景 
  2. private void drawBackground(Canvas canvas) { 
  3.     final Drawable background = mBackground; 
  4.     if (background == null) { 
  5.         return
  6.     } 
  7.     // 根據(jù)在 layout 過(guò)程中獲取的 View 的位置參數(shù),來(lái)設(shè)置背景的邊界 
  8.     setBackgroundBounds(); 
  9.     // 先嘗試用HWUI繪制 
  10.     if (canvas.isHardwareAccelerated() && mAttachInfo != null 
  11.             && mAttachInfo.mThreadedRenderer != null) { 
  12.         mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode); 
  13.         final RenderNode renderNode = mBackgroundRenderNode; 
  14.         if (renderNode != null && renderNode.isValid()) { 
  15.             setBackgroundRenderNodeProperties(renderNode); 
  16.             ((DisplayListCanvas) canvas).drawRenderNode(renderNode); 
  17.             return
  18.         } 
  19.     } 
  20.     final int scrollX = mScrollX; 
  21.     final int scrollY = mScrollY; 
  22.     if ((scrollX | scrollY) == 0) { 
  23.         //調(diào)用 Drawable 的 draw 方法來(lái)進(jìn)行背景的繪制 
  24.         background.draw(canvas); 
  25.     } else { 
  26.         // 若 mScrollX 和 mScrollY 有值,則對(duì) canvas 的坐標(biāo)進(jìn)行偏移平移畫布 
  27.         canvas.translate(scrollX, scrollY); 
  28.         //調(diào)用 Drawable 的 draw 方法來(lái)進(jìn)行背景的繪制 
  29.         background.draw(canvas); 
  30.         canvas.translate(-scrollX, -scrollY); 
  31.     } 

2、繪制View的內(nèi)容

  1. // 繪制View本身內(nèi)容,空實(shí)現(xiàn),子類必須復(fù)寫 
  2. 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)看看;

  1. @Override 
  2. protected void dispatchDraw(Canvas canvas) { 
  3.     ... 
  4.     //  遍歷子View 
  5.     final int childrenCount = mChildrenCount; 
  6.     ... 
  7.     for (int i = 0; i < childrenCount; i++) { 
  8.         while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { 
  9.             final View transientChild = mTransientViews.get(transientIndex); 
  10.             if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || 
  11.                     transientChild.getAnimation() != null) { 
  12.                 more |= drawChild(canvas, transientChild, drawingTime); 
  13.             } 
  14.             transientIndex++; 
  15.             if (transientIndex >= transientCount) { 
  16.                 transientIndex = -1; 
  17.             } 
  18.         } 
  19.         final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); 
  20.         final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); 
  21.         if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { 
  22.             // 調(diào)用 drawChild 方法,進(jìn)行子元素繪制 
  23.             more |= drawChild(canvas, child, drawingTime); 
  24.         } 
  25.     } 
  26.     .... 

4、分發(fā)Draw

  1. @Override 
  2. protected void dispatchDraw(Canvas canvas) { 
  3.     ... 
  4.     // 1. 遍歷子View 
  5.     final int childrenCount = mChildrenCount; 
  6.     ... 
  7.     for (int i = 0; i < childrenCount; i++) { 
  8.         while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { 
  9.             final View transientChild = mTransientViews.get(transientIndex); 
  10.             if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || 
  11.                     transientChild.getAnimation() != null) { 
  12.                 more |= drawChild(canvas, transientChild, drawingTime); 
  13.             } 
  14.             transientIndex++; 
  15.             if (transientIndex >= transientCount) { 
  16.                 transientIndex = -1; 
  17.             } 
  18.         } 
  19.         final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); 
  20.         final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); 
  21.         if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { 
  22.             // 調(diào)用 drawChild 方法,進(jìn)行子元素繪制 
  23.             more |= drawChild(canvas, child, drawingTime); 
  24.         } 
  25.     } 
  26.     .... 

5、繪制View

所謂的繪制裝飾,就是指View除了背景、內(nèi)容、子View的其余部分,例如滾動(dòng)條等,我們看View#onDrawForeground

  1. public void onDrawForeground(Canvas canvas) { 
  2.     //繪制指示器 
  3.     onDrawScrollIndicators(canvas); 
  4.     //繪制滾動(dòng)條 
  5.     onDrawScrollBars(canvas); 
  6.     final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null
  7.     if (foreground != null) { 
  8.         if (mForegroundInfo.mBoundsChanged) { 
  9.             mForegroundInfo.mBoundsChanged = false
  10.             final Rect selfBounds = mForegroundInfo.mSelfBounds; 
  11.             final Rect overlayBounds = mForegroundInfo.mOverlayBounds; 
  12.             if (mForegroundInfo.mInsidePadding) { 
  13.                 selfBounds.set(0, 0, getWidth(), getHeight()); 
  14.             } else { 
  15.                 selfBounds.set(getPaddingLeft(), getPaddingTop(), 
  16.                         getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); 
  17.             } 
  18.             final int ld = getLayoutDirection(); 
  19.             Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(), 
  20.                     foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld); 
  21.             foreground.setBounds(overlayBounds); 
  22.         } 
  23.         //調(diào)用 Drawable 的 draw 方法,繪制前景色 
  24.         foreground.draw(canvas); 
  25.     } 

到目前為止,View的繪制流程也講述完畢了;

總結(jié)

其實(shí)繪制這塊還是很重要的,下次還是要繼續(xù)講解下;

學(xué)如逆水行舟,不進(jìn)則退;心似平原走馬,易放難收;

一起加油老鐵們

本文轉(zhuǎn)載自微信公眾號(hào)「Android開發(fā)編程」

【編輯推薦】

 

責(zé)任編輯:姜華 來(lái)源: Android開發(fā)編程
相關(guān)推薦

2021-09-16 06:44:04

Android進(jìn)階流程

2021-10-15 09:19:17

AndroidSharedPrefe分析源碼

2021-09-17 06:55:50

AndroidLayoutView

2021-09-08 06:51:52

AndroidRetrofit原理

2021-08-24 07:53:28

AndroidActivity生命周期

2021-09-24 08:10:40

Java 語(yǔ)言 Java 基礎(chǔ)

2021-09-15 07:31:33

Android窗口管理

2021-09-18 06:56:01

JavaCAS機(jī)制

2021-09-10 07:31:54

AndroidAppStartup原理

2017-05-03 17:00:16

Android渲染機(jī)制

2021-08-17 13:41:11

AndroidView事件

2022-10-11 07:43:34

AndroidSyncGradle 構(gòu)建

2024-12-30 08:02:40

2014-07-15 17:17:31

AdapterAndroid

2017-08-08 09:15:41

前端JavaScript頁(yè)面渲染

2017-01-13 22:42:15

iosswift

2016-10-26 20:49:24

ReactJavascript前端

2021-02-17 11:25:33

前端JavaScriptthis

2017-07-12 14:58:21

AndroidInstant Run

2011-07-18 14:38:44

子查詢外部查詢
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)