Android高级进阶——View的工作原理(三)Draw过程
开篇:
前两篇已经详细的介绍了 Measure 以及 Layout 过程,就剩下一个 Draw 绘制过程了,Draw 其实也不是很复杂,但是想要彻底掌握绘制的技巧就需要了解 Canvas 的使用了,后续会再开几篇详细介绍 Canvas 的具体使用
【Android高级进阶——View的工作原理(三)Draw过程】老规矩,还是先给出 ViewRootImpl#performTraversals 方法
ViewRootImpl#performTraversals 方法
private void performTraversals() {
...
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...if (didLayout) {
performLayout(lp, mWidth, mHeight);
...if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0;
i < mPendingTransitions.size();
++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}performDraw();
...
不废话,直接看 performDraw 方法
ViewRootImpl #performDraw 方法
private void performDraw() {
...省略部分代码
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...省略部分代码
ViewRootImpl#draw() 方法
private void draw(boolean fullRedrawNeeded) {
...省略部分代码
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
...省略部分代码
mAttachInfo.mTreeObserver.dispatchOnDraw();
...省略部分代码
//根据是否开启了硬件加速,是否开启硬件加速,View 的绘制流程都是一样的,区别就是 Canvas 不同
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
//开启了硬件加速,则执行该方法,内部最终还是会执行到view 的 draw 方法
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
//未开启硬件加速,则执行该方法,直接调用 view 的 draw 方法
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
measure 和 layout 过程直接调用的就是 ViewRootImpl的performMeasure和performLayout方法,而draw调用的是ViewRootImpl的performDraw()方法,再由performDraw中的draw(boolean fullRedrawNeeded)方法来调用ViewTreeObserver中的dispatchOnDraw()方法,进行通知所有挂在view树上的view开始draw,随后通过 drawSoftware 方法调用 view 的 draw 方法开始绘制
ViewTreeObserver.dispatchOnDraw()
public final void dispatchOnDraw() {
if (mOnDrawListeners != null) {
mInDispatchOnDraw = true;
final ArrayList listeners = mOnDrawListeners;
int numListeners = listeners.size();
for (int i = 0;
i < numListeners;
++i) {
listeners.get(i).onDraw();
}
mInDispatchOnDraw = false;
}
}
ViewRootImpl#drawSoftware 方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
canvas = mSurface.lockCanvas(dirty);
//...省略部分代码
mView.draw(canvas);
//...省略部分代码
}
最终终于执行到了 View 的 draw() 方法了,这个方法很重要,我们要显示的内容都是在这个方法中实现的,没有实现这个方法的逻辑,就是前面的 Measure 和 Layout 逻辑处理的在漂亮,也不能呈现。
View #draw(Canvas canvas)
public void draw(Canvas canvas) {
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
*//绘制 View 的内容
*3. Draw view's content
*//绘制子 View,子 View 的绘制也是按照这个流程进行
*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
// Step 1, 绘制背景
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}// 通常情况下,会跳过第 2 步和第 5 步
// 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) {
//绘制 View 的内容,需要子类具体实现,View 的 onDraw 是一个空实现,因为 View 并不是一个具体的 View ,不知道要绘制的内容,所以要绘制的内容留给具体的子类去具体实现
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
//绘制内部包含的子 View,这个方法 View 也没有实现,具体的实现是在 ViewGroup 中,后面会具体分析该方法
// 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;
}
...省略部分代码
View #drawBackground(Canvas) 该方法用于绘制 View 的背景,这个背景是我们在创建 View 时设定的
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
...省略硬件加速相关代码
//判断 View 是否设置了 scrollX 和 mScrollY,并平移滑动,绘制完背景后,在平移到原来的位置
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
View 的 onDraw(Canvas) 方法
protected void onDraw(Canvas canvas) {
}
onDraw 是用来绘制 View 的显示内容的,但是 View 并没有实现 onDraw ,具体的实现逻辑需要派生类去给根据自身情况去绘制具体的内容,可以参看 TextView 的 onDraw 方法
View #dispatchDraw
protected void dispatchDraw(Canvas canvas) {}
dispatchDraw( )方法用于通知子View自己绘制,View未实现该方法,由ViewGroup来实现该方法。我们来看一下吧;
ViewGroup#dispatchDraw(Canvas) 方法
protected void dispatchDraw(Canvas canvas) {。。。(省略)int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
if (clipToPadding) {
// 这里会对画布进行剪切,切掉Padding值
clipSaveCount = canvas.save();
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}。。。(省略)// 遍历子View
for (int i = 0;
i < childrenCount;
i++) {。。。(动画相关操作 省略)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) {
// 绘制子View
drawChild(canvas, child, drawingTime);
}
}。。。(省略)if (clipToPadding) {
canvas.restoreToCount(clipSaveCount);
}
。。。(省略)
}
ViewGroup#drawChild 方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
这里调用的是 View 这个类的重载方法,来看一下
View draw(Canvas canvas,ViewGroup parent,long drawingTime) 方法
// 画布canvas的大小是
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
// 是否开启硬件加速标识
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
。。。省略
int sx = 0;
int sy = 0;
if (!drawingWithRenderNode) {
// 我们在自定义滑动控件时,一般会重写该方法,并设置mScrollX和mScrollY
computeScroll();
sx = mScrollX;
sy = mScrollY;
}final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
final boolean offsetForScroll = cache == null && !drawingWithRenderNode;
int restoreTo = -1;
restoreTo = canvas.save();
// 根据mScrollX和mScrollY移动画布的坐标系
canvas.translate(mLeft - sx, mTop - sy);
。。。(设置画布透明度的操作 省略)if (!drawingWithRenderNode) {
// apply clips directly, since RenderNode won't do it for this draw
if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
}
}if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
// 开启硬件加速时走该分支
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
// 调用View.draw()进行绘制
draw(canvas);
}
}if (restoreTo >= 0) {
canvas.restoreToCount(restoreTo);
}
。。。省略
return more;
}
到这里关于 View 的三个工作流程就介绍完了,后面会详细的介绍一下 View 的绘制技巧 —— Canvas 的使用。
推荐阅读
- android第三方框架(五)ButterKnife
- Android中的AES加密-下
- 带有Hilt的Android上的依赖注入
- android|android studio中ndk的使用
- 唐嫣可真会穿,西装搭牛仔裤都能穿出高级感,一双大长腿太抢镜
- Android事件传递源码分析
- 推荐系统论文进阶|CTR预估 论文精读(十一)--Deep Interest Evolution Network(DIEN)
- RxJava|RxJava 在Android项目中的使用(一)
- Android7.0|Android7.0 第三方应用无法访问私有库
- 深入理解|深入理解 Android 9.0 Crash 机制(二)