Read The Fucking Source Code
引言
Android自定义控件涉及View的绘制分发流程
源码版本(Android Q — API 29)
本文涉及Android绘制流程
1. 顶层视角预览 draw
2. draw
2.1 View和ViewGroup的区别
View:View不执行dispatchDraw(子View的绘制)。
ViewGroup:ViewGroup执行dispatchDraw(子View的递归绘制)。
2.2 View和ViewGroup的汇总
2.2.1 View的 draw 过程(紫色代表可重写)
2.2.2 ViewGroup的 draw 过程(紫色代表可重写)
2.2.3 draw 各个过程的含义
2.3 draw自顶向下
2.3.1 最顶层(DecorView)分发的draw
2.3.1.1 我们来看ViewRootImpl中的performDraw()方法。
private void performDraw() {
//代码省略……
try {
//绘制分发
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
}
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
//代码省略……
}
2.3.1.2 我们来看ViewRootImpl中的draw()方法。
private boolean draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
//代码省略……
//绘制分发
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
//代码省略……
}
2.3.1.3 我们来看ViewRootImpl中的drawSoftware()方法。
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
//代码省略……
//canvas获取
canvas = mSurface.lockCanvas(dirty);
//代码省略……
//绘制分发,终于到了DecorView的绘制分发
mView.draw(canvas);
//代码省略……
}
2.3.2 ViewGroup分发的draw
2.3.2.1 我们来看ViewGroup中的dispatchDraw()方法。
@Override
protected void dispatchDraw(Canvas canvas) {
//代码省略……
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) {
//遍历子View绘制
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
//代码省略……
}
2.3.2.2 我们来看ViewGroup中的drawChild()方法。
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
//递归遍历子View的绘制
return child.draw(canvas, this, drawingTime);
}
2.3.3 View分发的draw
2.3.3.1 我们来看View中的draw()方法。
@CallSuper
public void draw(Canvas canvas) {
//代码省略……
// Step 1, draw the background, if needed
int saveCount;
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
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(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);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
//代码省略……
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
//代码省略……
drawAutofilledHighlight(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);
if (debugDraw()) {
debugDrawFocus(canvas);
}
}
2.3.3.2 View中的drawBackground()方法:绘制背景。
2.3.3.3 我们来看View中的onDraw()方法。
/**
* //需要具体控件自己实现,比如TextView
* Implement this to do your drawing.
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {
}
2.3.3.4 我们来看View中的dispatchDraw()方法。
/**
* //由draw调用以绘制子视图。这可能会被派生类重写,以便在绘制其子对象之前(但在绘制其自己的视图之后)获得控制权
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {
}
2.3.3.5 View中的drawAutofilledHighlight()方法:绘制自动填充视图的高亮。
2.3.3.6 View中的ViewOverlay::dispatchDraw()方法:当覆盖层存在时,则绘制覆盖层。
2.3.3.7 我们来看View中的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);
}
}
2.3.3.8 View中的drawDefaultFocusHighlight方法:绘制默认焦点的高亮。
2.3.3.9 View中的debugDrawFocus方法:实就是开发者选项中的显示布局边界的效果。
2.4 问题思考
ViewGroup 为什么无法通过 onDraw 方法绘制自定义内容?如果我希望重写 onDraw 来在 ViewGroup 进行一些绘制操作,怎样才能看到效果?
- ViewGroup 无法显示绘制内容是因为默认设置的标记位关闭执行了它的 drawBackground 和 onDraw 方法。ViewGroup 一般就是管理布局相关的事项,不处理自身的绘制有助于提高执行效率。
- 解决思路:调用 View.setWillNotDraw(false);为 ViewGroup 设置背景图。
网友评论