View的draw流程
先看下view的draw()方法(ViewGroup并没有重写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);
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;
}
.......
}
首先根据draw方法的源码注释(省略了部分源码),draw总共有6步:
- 绘制背景
- 保存Canvas图层信息(如果有必要的话)
- 绘制View的内容
- 绘制子View
- 绘制保存的Canvas图层信息(如果有必要的话)
- 绘制View的装饰(比如滑动条)
其中,第二步和第五步可以跳过,所以我们重点看其他几步.
步骤一:绘制背景 drawBackground(canvas)
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mThreadedRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) { // 1
background.draw(canvas); // 2
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
在此方法中background是作为一个Drawable对象的,代码注释1处,表明绘制背景时要先考虑偏移,如果偏移值不为0,需要在偏移之后再绘制背景,然后会调用Drawable的draw()方法
步骤三:绘制view内容 onDraw(canvas)
protected void onDraw(Canvas canvas) {
}
然后会发现这个方法是个空方法,这意味着,不同的view有不同的实现,自定义view的时候需要我们自己来实现
步骤四:绘制子view dispatchDraw(canvas);
protected void dispatchDraw(Canvas canvas) {
}
在View中,这个方法也是一个空实现,但是在ViewGroup中重写了这个方法(因为一个纯粹的view是没有子view的,只有viewgroup才有子view)
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) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
......
}
上述代码省略了部分源码,可从上述源码中看出ViewGroup的dispatchDraw()方法中,遍历了所有子view,并调用了drawChild()方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
然后又调用了子view的draw方法(此draw方法与文章开头的draw方法不是一个方法),这个方法源码有点长,这里省略了大部分代码,重点关注下computeScroll()这个方法,之前在view的滑动方法一篇中提到过得
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
.....
int sx = 0;
int sy = 0;
if (!drawingWithRenderNode) {
computeScroll();
sx = mScrollX;
sy = mScrollY;
}
......
}
步骤六:绘制装饰 onDrawForeground(Canvas canvas)
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);
}
}
在这个方法中会依次调用onDrawScrollIndicators和onDrawScrollbars绘制滑动条的指示器和滑动条,最后绘制View的前景色