亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

深入淺析Android中View的繪制流程

發布時間:2020-11-23 17:10:53 來源:億速云 閱讀:221 作者:Leah 欄目:移動開發

深入淺析Android中View的繪制流程?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

從performDraw說起

三大工作流程始于ViewRootImpl#performTraversals,在這個方法內部會分別調用performMeasure,performLayout,performDraw三個方法來分別完成測量,布局,繪制流程。那么我們現在先從performDraw方法看起,ViewRootImpl#performDraw:

private void performDraw() {
 //...
 final boolean fullRedrawNeeded = mFullRedrawNeeded;
 try {
  draw(fullRedrawNeeded);
 } finally {
  mIsDrawing = false;
  Trace.traceEnd(Trace.TRACE_TAG_VIEW);
 }

 //省略...
}

里面又調用了ViewRootImpl#draw方法,并傳遞了fullRedrawNeeded參數,而該參數由mFullRedrawNeeded成員變量獲取,它的作用是判斷是否需要重新繪制全部視圖,如果是第一次繪制視圖,那么顯然應該繪制所以的視圖,如果由于某些原因,導致了視圖重繪,那么就沒有必要繪制所有視圖。我們來看看ViewRootImpl#draw:

private void draw(boolean fullRedrawNeeded) {
 ...
 //獲取mDirty,該值表示需要重繪的區域
 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區域置為整個屏幕,表示整個視圖都需要繪制
 //第一次繪制流程,需要繪制所有視圖
 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;
  }
}

這里省略了一部分代碼,我們只看關鍵代碼,首先是先獲取了mDirty值,該值保存了需要重繪的區域的信息,關于視圖重繪,后面會有文章專門敘述,這里先熟悉一下。接著根據fullRedrawNeeded來判斷是否需要重置dirty區域,最后調用了ViewRootImpl#drawSoftware方法,并把相關參數傳遞進去,包括dirty區域,我們接著看該方法的源碼:

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區域,由dirty區域決定
  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;
}

可以看書,首先是實例化了Canvas對象,然后鎖定該canvas的區域,由dirty區域決定,接著對canvas進行一系列的屬性賦值,最后調用了mView.draw(canvas)方法,前面分析過,mView就是DecorView,也就是說從DecorView開始繪制,前面所做的一切工作都是準備工作,而現在則是正式開始繪制流程。

View的繪制

由于ViewGroup沒有重寫draw方法,因此所有的View都是調用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);

  // 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);

  // we're done...
  return;
 }
 ...
}

可以看到,draw過程比較復雜,但是邏輯十分清晰,而官方注釋也清楚地說明了每一步的做法。我們首先來看一開始的標記位dirtyOpaque,該標記位的作用是判斷當前View是否是透明的,如果View是透明的,那么根據下面的邏輯可以看出,將不會執行一些步驟,比如繪制背景、繪制內容等。這樣很容易理解,因為一個View既然是透明的,那就沒必要繪制它了。接著是繪制流程的六個步驟,這里先小結這六個步驟分別是什么,然后再展開來講。

繪制流程的六個步驟:
1、對View的背景進行繪制
2、保存當前的圖層信息(可跳過)
3、繪制View的內容
4、對View的子View進行繪制(如果有子View)
5、繪制View的褪色的邊緣,類似于陰影效果(可跳過)
6、繪制View的裝飾(例如:滾動條)
其中第2步和第5步是可以跳過的,我們這里不做分析,我們重點來分析其它步驟。

Skip 1:繪制背景

這里調用了View#drawBackground方法,我們看它的源碼:

private void drawBackground(Canvas canvas) {

 //mBackground是該View的背景參數,比如背景顏色
 final Drawable background = mBackground;
 if (background == null) {
  return;
 }

 //根據View四個布局參數來確定背景的邊界
 setBackgroundBounds();

 ...

 //獲取當前View的mScrollX和mScrollY值
 final int scrollX = mScrollX;
 final int scrollY = mScrollY;
 if ((scrollX | scrollY) == 0) {
  background.draw(canvas);
 } else {
  //如果scrollX和scrollY有值,則對canvas的坐標進行偏移,再繪制背景
  canvas.translate(scrollX, scrollY);
  background.draw(canvas);
  canvas.translate(-scrollX, -scrollY);
 }
}

可以看出,這里考慮到了view的偏移參數,scrollX和scrollY,繪制背景在偏移后的view中繪制。

Skip 3:繪制內容

這里調用了View#onDraw方法,View中該方法是一個空實現,因為不同的View有著不同的內容,這需要我們自己去實現,即在自定義View中重寫該方法來實現。

Skip 4: 繪制子View

如果當前的View是一個ViewGroup類型,那么就需要繪制它的子View,這里調用了dispatchDraw,而View中該方法是空實現,實際是ViewGroup重寫了這個方法,那么我們來看看,ViewGroup#dispatchDraw:

protected void dispatchDraw(Canvas canvas) {
 boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
 final int childrenCount = mChildrenCount;
 final View[] children = mChildren;
 int flags = mGroupFlags;

 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;
   }
  }
  int childIndex = customOrder &#63; getChildDrawingOrder(childrenCount, i) : i;
  final View child = (preorderedList == null)
    &#63; children[childIndex] : preorderedList.get(childIndex);
  if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
   more |= drawChild(canvas, child, drawingTime);
  }
 }
 //省略...

}

源碼很長,這里簡單說明一下,里面主要遍歷了所以子View,每個子View都調用了drawChild這個方法,我們找到這個方法,ViewGroup#drawChild:

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
  return child.draw(canvas, this, drawingTime);
}

可以看出,這里調用了View的draw方法,但這個方法并不是上面所說的,因為參數不同,我們來看看這個方法,View#draw:

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

 //省略...

 if (!drawingWithDrawingCache) {
  if (drawingWithRenderNode) {
   mPrivateFlags &= ~PFLAG_DIRTY_MASK;
   ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
  } else {
   // Fast path for layouts with no backgrounds
   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) {
   // no layer paint, use temporary paint to draw bitmap
   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 {
   // use layer paint to draw the bitmap, merging the two alphas, but also restore
   int layerPaintAlpha = mLayerPaint.getAlpha();
   mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
   canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
   mLayerPaint.setAlpha(layerPaintAlpha);
  }
 }

}

我們主要來看核心部分,首先判斷是否已經有緩存,即之前是否已經繪制過一次了,如果沒有,則會調用draw(canvas)方法,開始正常的繪制,即上面所說的六個步驟,否則利用緩存來顯示。
這一步也可以歸納為ViewGroup繪制過程,它對子View進行了繪制,而子View又會調用自身的draw方法來繪制自身,這樣不斷遍歷子View及子View的不斷對自身的繪制,從而使得View樹完成繪制。

Skip 6 繪制裝飾

所謂的繪制裝飾,就是指View除了背景、內容、子View的其余部分,例如滾動條等,我們看View#onDrawForeground:

public void onDrawForeground(Canvas canvas) {
 onDrawScrollIndicators(canvas);
 onDrawScrollBars(canvas);

 final Drawable foreground = mForegroundInfo != null &#63; 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);
  }

  foreground.draw(canvas);
 }
}

看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

永年县| 容城县| 兖州市| 芦山县| 仁寿县| 乐昌市| 姜堰市| 武乡县| 南投市| 尼木县| 永春县| 通州区| 平度市| 泽州县| 吉水县| 正宁县| 济宁市| 盐山县| 故城县| 黄山市| 会同县| 定西市| 贵德县| 宁蒗| 中卫市| 菏泽市| 来凤县| 泗水县| 张家界市| 来宾市| 松滋市| 仙居县| 福清市| 泽州县| 永平县| 南溪县| 武陟县| 临猗县| 竹溪县| 焉耆| 榆中县|