本文已参与「新人创作礼」活动,一起开启掘金创作之路。
之前的文章《View体系(六)View工作流程入口》提到View
的工作流程包括了measure
、layout
和draw
的过程,今天我们就来看一下View
的draw
流程是怎样的。
(注:文中源码基于
Android 12
)
View
的draw
流程很简单,源码里的注释官方也写的很清楚,我们看View
的draw
方法:
public void draw(Canvas canvas) {
...
// Step 1, draw the background, if needed
drawBackground(canvas);
...
// skip step 2 & 5 if possible (common case)
...
// Step 2, save the canvas' layers
...
// Step 3, draw the content
onDraw(canvas);
...
// Step 4, draw the children
dispatchDraw(canvas);
...
// Step 5, draw the fade effect and restore layers
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
...
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
总览
官方注释已经清楚的写了每一步的工作:
- 如果需要,则绘制背景(
drawBackground
) - 保存当前canvas层
- 绘制View的内容(
onDraw
) - 绘制子View(
dispatchDraw
) - 如果需要,则绘制View的褪色边缘,类似于阴影效果
- 绘制装饰,比如滚动条(
onDrawForeground
) - 绘制默认焦点高亮效果(
drawDefaultFocusHighlight
)
注释中说明了第2步和第5步可以跳过,这里就不展开讲解,在此重点分析其他步骤。
步骤1:绘制背景
进入View
的drawBackground
方法:
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
...
setBackgroundBounds(); //1
...
background.draw(canvas); //2
注释1处设置背景范围,注释2处通过Drawable
的draw
方法来绘制背景,关于Drawable
将在后面的文章详细讲解。看注释1的setBackgroundBounds
是如何设置背景范围的:
void setBackgroundBounds() {
if (mBackgroundSizeChanged && mBackground != null) {
mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop); //1
mBackgroundSizeChanged = false;
rebuildOutline();
}
}
看到注释1处通过View
的mRight、mLeft、mBottom、mTop
等参数调用mBackground.setBounds
方法来进行绘制范围的设置。
步骤2:保存当前canvas层
步骤3:绘制View的内容
步骤3调用了View
的onDraw
方法,这个方法是一个空实现,因为不同的View
有不同的内容,所以需要我们自己去实现,即在自定义View
时重写该方法来实现我们自己的绘制。
protected void onDraw(Canvas canvas) {
}
步骤4:绘制子View
步骤4调用了dispatchDraw
方法,这个方法也是个空实现:
protected void dispatchDraw(Canvas canvas) {
}
ViewGroup
重写了这个方法,我们看ViewGroup
的dispatchDraw
方法:
protected void dispatchDraw(Canvas canvas) {
...
for (int i = 0; i < childrenCount; i++) {
...
more |= drawChild(canvas, transientChild, drawingTime);
源码很长,这里只贴出关键代码,在dispatchDraw
方法中遍历子View
并调用drawChild
方法,我们继续看drawChild
方法:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
drawChild
方法实际就是调用了子View
的draw
方法对子View
进行绘制
步骤5:绘制View的阴影效果
步骤6:绘制装饰
步骤6调用了onDrawForeground
方法:
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
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);
}
foreground.draw(canvas);
}
}
这里主要是对ScrollBar
及其它装饰进行绘制。
步骤7:绘制默认焦点高亮效果
private void drawDefaultFocusHighlight(Canvas canvas) {
if (mDefaultFocusHighlight != null && isFocused()) {
if (mDefaultFocusHighlightSizeChanged) {
mDefaultFocusHighlightSizeChanged = false;
final int l = mScrollX;
final int r = l + mRight - mLeft;
final int t = mScrollY;
final int b = t + mBottom - mTop;
mDefaultFocusHighlight.setBounds(l, t, r, b);
}
mDefaultFocusHighlight.draw(canvas);
}
}
学习更多知识,请关注我的个人博客:droidYu