「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」
相关文章:
Android View的绘制流程(一)-绘制流程以及Activity视图介绍
Android View的绘制流程(二)-Activity视图-DecorView
Android View的绘制流程(三)-Activity视图-WindowManager
Android View的绘制流程(四)-Activity视图-ViewRootImpl
Android View的绘制流程(五)-Measure
Android View的绘制流程(六)-Layout
Android View的绘制流程(七)-Draw
上一篇文章把 View的绘制流程中的布局(Layout)的逻辑通过分析源码中的主要内容介绍了一下,主要方法调用是onLayout方法以及layoutChildren,下面我们介绍下View的绘制流程中最后一个步骤绘制Draw。
Draw
Draw 的入口也是在 ViewRootImpl 中,执行 ViewRootImpl#performTraversals 中会执行 ViewRootIml#performDraw:
private void performDraw() {
...
//fullRedrawNeeded,它的作用是判断是否需要重新绘制全部视图
draw(fullRedrawNeeded);
...
}
然后是执行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;
}
}
上面方法最终调用drawSoftware,然后在 drawSoftware 会执行到 mView.draw(canvas)。
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
//锁定canvas区域,由dirty区域决定
//这个canvas就是我们想在上面绘制东西的画布
canvas = mSurface.lockCanvas(dirty);
...
//画布支持位图的密度,和手机分辨率相关
canvas.setDensity(mDensity);
...
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
...
canvas.translate(-xoff, -yoff);
...
//正式开始绘制
mView.draw(canvas);
...
//提交需要绘制的东西
surface.unlockCanvasAndPost(canvas);
}
mView.draw(canvas) 开始真正的绘制:
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);
}
// 如果可以跳过2和5步
final int viewFlags = mViewFlags;
//判断是否有绘制衰退边缘的标示
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
// 如果没有绘制衰退边缘只需要3,4,6步
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;
}
/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
if (!dirtyOpaque) 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;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
// 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);
}
可以看到,draw过程比较复杂,但是逻辑还是很清晰的,重点看上面一开始有一个标记位:dirtyOpaque,这个标记位的意思是判断当前view是否是透明的,如果是透明的则不进行下一步的绘制,很简单,透明的绘制也看不到,接着是绘制流程主要分为6个步骤,但是主要是个步骤,接下来也会一一的说明:
1):绘制View的背景
2):保存图层信息(不是重点)
3):绘制View的内容
4):对子View进行绘制(和measure,layout的流程一样)
5):绘制阴影(不是重点)
6):绘制View的其他部分\
这里说明一下:ViewGroup子类默认情况下就是不执行 onDraw 方法的,在 ViewGroup 源码中的 initViewGroup() 方法中设置了一个标记,源码如下:
private void initViewGroup() {
// ViewGroup doesn't draw by default
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
......
}
看第二行注释也可以看出,ViewGroup 默认情况下是不会 draw 的。第四行调用 setFlags 方法设置标记 WILL_NOT_DRAW,在回到 View 中 draw 方法看第2行代码:
1 final int privateFlags = mPrivateFlags;
2 final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
3 (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
setFlags 方法就是对 View中mPrivateFlags 值进行相应改变,我们设置标记 WILL_NOT_DRAW 那么 dirtyOpaque 得到的值就为 true,从而 if (!dirtyOpaque) 不成立,也就不会执行onDraw 方法。
下面针对以上几个步骤,重点看下源码
绘制背景
private void drawBackground(Canvas canvas) {
//获取背景的Drawable,没有就不需要绘制
final Drawable background = mBackground;
if (background == null) {
return;
}
//确定背景Drawable边界
setBackgroundBounds();
...
//如果有偏移量先偏移画布再将drawable绘制上去
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);
//把画布的原点移回去,drawable在屏幕上的位置不动
canvas.translate(-scrollX, -scrollY);
}
}
绘制内容
public void onDraw(Canvas c) {
super.onDraw(c);
mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent,
mStatusColorViewState.view, mNavigationColorViewState.view);
}
super也是一个空实现,需要自己实现,根据不同组件的实现方式,自己实现
绘制子View
DecorView 绘制完成后,开始绘制子 View,所以 ViewGroup 的绘制需要绘制子 View, ViewGroup的dispatchDraw 的流程是先启动第一次加到布局中的动画,然后确定绘制区域,遍历绘制 View,遍历 View 的时候优先绘制渲染的 mTransientViews,绘制 View 调用到ViewGroup的drawChild:
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
//ViewGroup是否有设置子View入场动画,如果有绑定到View
// 启动动画控制器
...
//指定修改区域
int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
// 不让子view绘制在pandding里面,也就是去除padding
if (clipToPadding) {
clipSaveCount = canvas.save();
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
...
for (int i = 0; i < childrenCount; i++) {
//先取mTransientViews中的View,mTransientViews中的View通过addTransientView添加,它们只是容器渲染的一个item
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 ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
...
}
绘制子View:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
//View中有两个draw方法
//这个多参数的draw用于view绘制自身内容
return child.draw(canvas, this, drawingTime);
}
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
...
//主要判断是否有绘制缓存,如果有,直接使用缓存,如果没有,调用 draw(canvas)方法
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);
}
}
}
上面源码的主要内容和之前分析measure和layout差不多, 首先判断是否已经有缓存,即之前是否已经绘制过一次了,如果没有,则会调用 draw(canvas) 方法,开始正常的绘制,否则利用缓存来显示。代码中科院看出,它对子 View 进行了绘制,而子 View 又会调用自身的 draw 方法来绘制自身,这样不断遍历子 View 及子 View 的不断对自身的绘制,从而使得 View 树完成绘制。
对于自定义 View ,如果需要绘制东西的话,直接重新 onDraw 就可以了。
绘制其他部分
就是指 View 除了背景、内容、子 View 的其余部分。
public void onDrawForeground(Canvas canvas) {
//绘制滑动指示
onDrawScrollIndicators(canvas);
//绘制ScrollBar
onDrawScrollBars(canvas);
//获取前景色的Drawable,绘制到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和5就不细说了,主要是绘制类似阴影,边缘色之类的
上面的内容把View绘制流程中的Draw介绍完了,其实主题的思想和measure以及layout差不多,熟悉自定义View的同学,对于绘制的draw方法都是不陌生的,各种draw类型的方法,形状等等,这些源码大体了解下过程,实际开发过程中,还是要实践,多操作才会生巧,写出来的才是真东西。
下一篇文章总结下View绘制过程,以上有介绍有误的地方还请指正,欢迎留言