一、概述
在执行完 measure 流程、layout 流程
后,如果我们要将 View 展示在界面上,需要执行 draw 流程
。
与 measure 流程、layout 流程
类似, draw 流程
也分为以下两种场景。
View的类型 | draw 过程 |
---|---|
单一的View (如 ImageView) | 只绘制 View 自身 |
ViewGroup | 先绘制 ViewGroup 自身,然后遍历绘制子 View |
版本: Android SDK 29
关联文章:
- 《View系列 (一) — Android 坐标系》
- 《View系列 (二) — MeasureSpec 详解》
- 《View系列 (三) — Measure 流程详解》
- 《View系列 (四) — Layout 流程详解》
- 《View系列 (五) — Draw 流程详解》
二、单一 View 的 Draw 过程
1. 流程图
2. 源码分析
View.draw()
流程主要分为6步,(其中第2步和第5步可以跳过):
- 如果需要,则绘制背景。
- 保存当前canvas层。
- 绘制View的内容。
- 绘制子View。
- 如果需要, 则绘制View的褪色边缘, 这类似于阴影效果。
- 绘制装饰, 比如滚动条。
// View.class
public void draw(Canvas canvas) {
// ...省略代码...
/*
* 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
drawBackground(canvas);
// ...省略代码...
// Step 2, save the canvas' layers
// ...省略代码(保存当前canvas层)...
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
// ...省略代码 (绘制 fade effect)...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// ...省略代码...
}
drawBackground(canvas)
// View.class
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
// 设置背景的边界
setBackgroundBounds();
// ...省略代码...
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
// 如果有偏移值,先进行画布偏移
canvas.translate(scrollX, scrollY);
// 触发 Drawable.draw() 操作
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
onDraw(canvas)
// View.class
protected void onDraw(Canvas canvas) {
// empty function
}
dispatchDraw(canvas)
// View.class
protected void dispatchDraw(Canvas canvas) {
// empty function
}
onDrawForeground(Canvas canvas)
// View.class
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas); //绘制ScrollIndicator
onDrawScrollBars(canvas); //绘制ScrollBar
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);
}
// 触发Drawable.draw() 流程
foreground.draw(canvas);
}
}
三、ViewGroup 的 Draw 过程
1. 流程图
2. 源码分析
由于 ViewGroup 和 View 绘制流程相同,区别仅在于 View.dispatchDraw()
的分发不同,所以我们仅分析该方法。
// ViewGroup.class
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
// 1.获取 ViewGroup 内部的子 View 个数。
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
// ...省略涉及动画的逻辑代码...
// ...省略其它逻辑代码...
// 2.遍历子 View
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) {
// 3.触发子 View 的绘制流程
more |= drawChild(canvas, transientChild, drawingTime);
}
// ...省略代码...
}
// ...省略代码...
}
// ...省略代码...
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// 触发子 View的绘制流程
return child.draw(canvas, this, drawingTime);
}