内容简介:根据 measure 测量出的宽高,layout 布局的位置,渲染整个 View 树,将界面呈现出来。以下源码基于版本27在
作用
根据 measure 测量出的宽高,layout 布局的位置,渲染整个 View 树,将界面呈现出来。
具体分析
以下源码基于版本27
DecorView 的draw 流程
在 《View的绘制-measure流程详解》 中说过,View 的绘制流程是从 ViewRootViewImpl 中的 performMeasure()
、 performLayout
、 performDraw
开始的。在执行完 performMeasure()
、 performLayout
后,开始执行 performDraw
方法:(以下源码有所删减)
//ViewRootViewImpl 类 private void performDraw() { .... draw(fullRedrawNeeded); .... } ------------------------------------------------------------------------- //ViewRootViewImpl 类 private void draw(boolean fullRedrawNeeded) { .... mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); .... } ------------------------------------------------------------------------- //ThreadedRenderer 类 void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) { .... updateRootDisplayList(view, callbacks); .... } ------------------------------------------------------------------------- //ThreadedRenderer 类 private void updateRootDisplayList(View view, DrawCallbacks callbacks) { .... updateViewTreeDisplayList(view); .... } ------------------------------------------------------------------------- //ThreadedRenderer 类 private void updateViewTreeDisplayList(View view) { view.mPrivateFlags |= View.PFLAG_DRAWN; view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) == View.PFLAG_INVALIDATED; view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; //这里调用了 View 的 updateDisplayListIfDirty 方法 //这个 View 其实就是 DecorView view.updateDisplayListIfDirty(); view.mRecreateDisplayList = false; } 复制代码
接下来查看 View 的 updateDisplayListIfDirty 方法:
//View 类 public RenderNode updateDisplayListIfDirty() { .... if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); drawAutofilledHighlight(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } if (debugDraw()) { debugDrawFocus(canvas); } } else { /*最终调用了 DecorView 的 draw 方法,为什么没有走上面的 dispatchDraw(canvas) 我也母鸡啊,我是Debug 断点调试晓得走这里的,哈哈*/ draw(canvas); } .... } ------------------------------------------------------------------------- //DecorView 重写了 draw 方法。所以走到了 DecorView 的 draw 方法 @Override public void draw(Canvas canvas) { //调用父类 (View)的 draw 方法 super.draw(canvas); if (mMenuBackground != null) { mMenuBackground.draw(canvas); } } 复制代码
以上流程,推荐两篇文章: ViewRootImpl的performDraw过程 ~~~~~~~~~~~~~~~~~浅谈ondraw的前世今身
View 的 draw 流程
就这样, View 的绘制就开始啦。主要有四个步骤:
drawBackground onDraw dispatchDraw onDrawForeground
//View 类 /** *手动将此视图(及其所有子项)渲染到给定的Canvas。在调用此函数前,视图必须已经完成了完整布局(layout)。 *一般我们在自定义控件继承 View 的时候,不要重写 draw 方法,只需重写 onDraw 方法 */ public void draw(Canvas canvas) { .... // 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 //绘制 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 类 //绘制背景 private void drawBackground(Canvas canvas) { final Drawable background = mBackground; //如果没有设置背景,就不进行绘制 if (background == null) { return; } //如果设置了背景吗,且背景的大小发生了改变, //就用 layout 计算出的四个边界值来确定背景的边界 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) { //调用 Drawable 的 draw 方法来进行背景的绘制 background.draw(canvas); } else { //平移画布 canvas.translate(scrollX, scrollY); //调用 Drawable 的 draw 方法来进行背景的绘制 background.draw(canvas); canvas.translate(-scrollX, -scrollY); } } ------------------------------------------------------------------------- //View 类 //绘制内容 protected void onDraw(Canvas canvas) { /*View 中的 onDraw 是一个空实现。也不难理解,当我们自定义控件继承 View 的时候,需要重写 onDraw 方法,通过 Canvas 和 Paint 来进行内容的绘制*/ } ------------------------------------------------------------------------- //View 类 //绘制 children protected void dispatchDraw(Canvas canvas) { /*View 中的 dispatchDraw 也是一个空实现。因为单独一个 View 本身是没有子元素的,不需要绘制 children */ } ------------------------------------------------------------------------- //View 类 //绘制装饰 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); } //调用 Drawable 的 draw 方法,绘制前景色 foreground.draw(canvas); } } 复制代码
以上就是 View 的绘制流程了。ViewGroup 本身是继承 View 的,它的基本绘制流程也是通过父类 View 进行的,只不过它重写了 dispatchDraw 方法,来进行子元素的绘制。下面我们来进行具体分析:
ViewGroup 的绘制 dispatchDraw 流程
//ViewGroup 类 @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) { 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) { //调用 drawChild 方法,进行绘制子元素 more |= drawChild(canvas, child, drawingTime); } } .... } ------------------------------------------------------------------------- //ViewGroup 类 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { //调用 View 的 draw 方法,这里要注意,调用的是 View 的三个参数的 draw 方法 return child.draw(canvas, this, drawingTime); } 复制代码
在 View 中还有一个 draw(Canvas canvas)
的重载方法,就是 draw(Canvas canvas, ViewGroup parent, long drawingTime)
:
//View 类 /** * ViewGroup.drawChild()调用此方法以使每个子视图自己绘制。 * 这是View专门根据图层类型和硬件加速来渲染行为的地方。 */ boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { .... //是否支持硬件加速 boolean drawingWithRenderNode = mAttachInfo != null && mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas; if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) { if (layerType != LAYER_TYPE_NONE) { //未开启 //调用 View 的 buildDrawingCache 方法 buildDrawingCache(true); } cache = getDrawingCache(true); } //开启了硬件加速 if (drawingWithRenderNode) { //调用 View 的 updateDisplayListIfDirty 方法 renderNode = updateDisplayListIfDirty(); if (!renderNode.isValid()) { // Uncommon, but possible. If a view is removed from the hierarchy during the call // to getDisplayList(), the display list will be marked invalid and we should not // try to use it again. renderNode = null; drawingWithRenderNode = false; } } .... } 复制代码
分别查看 buildDrawingCache
和 updateDisplayListIfDirty
方法:
//View 类 public void buildDrawingCache(boolean autoScale) { .... buildDrawingCacheImpl(autoScale); .... } ------------------------------------------------------------------------- //View 类 private void buildDrawingCacheImpl(boolean autoScale) { .... // 如果不需要进行自身绘制,就直接调用 dispatchDraw 绘制子 Children //否则就直接调用 View 的 draw 方法 if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); drawAutofilledHighlight(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } } else { draw(canvas); } .... } ------------------------------------------------------------------------- //View 类 public RenderNode updateDisplayListIfDirty() { .... // 如果不需要进行自身绘制,就直接调用 dispatchDraw 绘制子 Children //否则就直接调用 View 的 draw 方法 if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); drawAutofilledHighlight(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } if (debugDraw()) { debugDrawFocus(canvas); } } else { draw(canvas); } .... } 复制代码
如此,从顶层 DecorView 的 draw 方法开始,然后调用 dispatchDraw 方法循环遍历绘制子元素,如果子元素是继承了 ViewGroup ,就再次循环调用 dispatchDraw 方法,一层层往下递归调用,直到每一个子元素都被绘制完成,整个 draw 流程也就结束了。
setWillNotDraw 解析
在 View 中有一个方法是 setWillNotDraw:
//View 类 /** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or not this View draw on its own */ public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); } 复制代码
从注释上看,如果此视图本身不执行任何绘制,就设置为 true,系统会进行一些绘制优化。View 本身是默认设置为 false 的,没有启动这个优化标记(这也不难理解,因为一般我们自定义控件继承 View 的时候,是要重写 onDraw 方法进行绘制的)。ViewGroup 默认是开启这个优化标记的。当然如果明确 ViewGroup 是要通过 onDraw 方法进行绘制的时候,要手动关闭这个标记( setWillNotDraw(false)
)。
示例:
我们自定义一个控件,继承 ViewGroup,重写 onDraw 方法。
public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context) { super(context); setWillNotDraw(false); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); //这里如果不调用这句话,我们在使用的时候,onDraw 方法不会被调用 setWillNotDraw(false); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //onLayout 在这里必须重写,因为在 ViewGroup 中 onLayout是一个抽象方法 } //重写 onDraw 方法 @Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.BLACK); } } 复制代码
xml 中使用
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.ownchan.miclock.study.MyViewGroup android:layout_width="match_parent" android:background="@color/black" android:layout_height="match_parent"/> </FrameLayout> 复制代码
当我们的自定义控件在继承 ViewGroup 的时候,如果需要重写 onDraw 方法进行绘制,需要执行 setWillNotDraw(false)
。
推荐一个详解 draw 和 onDraw 调用时机好文: 你真的了解Android ViewGroup的draw和onDraw的调用时机吗
以上所述就是小编给大家介绍的《View的绘制-draw流程详解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- View的绘制-measure流程详解
- View的绘制-layout流程详解
- 一文详解如何用 R 语言绘制热图
- Python绘制六种可视化图表详解(建议收藏)
- ViewGroup 默认顺序绘制子 View,如何修改?什么场景需要修改绘制顺序?
- Shader 绘制基础图形
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Web前端开发最佳实践
党建 / 机械工业出版社 / 2015-1 / 59.00元
本书贴近Web前端标准来介绍前端开发相关最佳实践,目的在于让前端开发工程师提高编写代码的质量,重视代码的可维护性和执行性能,让初级工程师从入门开始就养成一个良好的编码习惯。本书总共分五个部分13章,第一部分包括第1章和第2章,介绍前端开发的基本范畴和现状,并综合介绍前端开发的一些最佳实践;第二部分为第3-5章,讲解HTML相关的最佳实践,并简单介绍HTML5中新标签的使用;第三部分为第6-8章,介......一起来看看 《Web前端开发最佳实践》 这本书的介绍吧!