目录

作用

根据 measure 测量出的宽高,layout 布局的位置,渲染整个 View 树,将界面呈现出来。

具体分析

以下源码基于版本27

DecorView 的draw 流程

在《View的绘制-measure流程详解》中说过,View 的绘制流程是从 ViewRootViewImpl 中的 performMeasure()performLayoutperformDraw 开始的。在执行完 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 其实就是 DecorViewview.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 绘制 children
  • onDrawForeground 绘制装饰(前景,滚动条)
//View 类
/***手动将此视图(及其所有子项)渲染到给定的Canvas。在调用此函数前,视图必须已经完成了完整布局(layout)。*一般我们在自定义控件继承 View 的时候,不要重写 draw 方法,只需重写 onDraw 方法*/
public void draw(Canvas canvas) {....// Step 1, draw the background, if neededint 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 Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)//绘制装饰 (前景色,滚动条)onDrawForeground(canvas);// Step 7, draw the default focus highlightdrawDefaultFocusHighlight(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;}}....
}
复制代码

分别查看 buildDrawingCacheupdateDisplayListIfDirty 方法:

//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 流程也就结束了。

tips: 在我们使用真机进行源码断点调试的时候,可能会出现源码不能打断点的情况,或者断点没有走在该走的地方。这是因为国内手机厂商基本都是定制系统,可能修改了源码。这个时候可以使用模拟器进行断点调试。注意:模拟器版本号要和项目编译版本号一致!

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);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {//onLayout 在这里必须重写,因为在 ViewGroup 中 onLayout是一个抽象方法}//重写 onDraw 方法@Overrideprotected 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.MyViewGroupandroid:layout_width="match_parent"android:background="@color/black"android:layout_height="match_parent"/>
</FrameLayout>
复制代码

当我们的自定义控件在继承 ViewGroup 的时候,如果需要重写 onDraw 方法进行绘制,需要执行 setWillNotDraw(false)

推荐一个详解 draw 和 onDraw 调用时机好文: 你真的了解Android ViewGroup的draw和onDraw的调用时机吗

总结

参考文档

《Android开发艺术探索》第四章-View的工作原理

自定义View Draw过程- 最易懂的自定义View原理系列(4)

ViewRootImpl的performDraw过程

你真的了解Android ViewGroup的draw和onDraw的调用时机吗

浅谈ondraw的前世今身

转载于:https://juejin.im/post/5cc17280e51d4514df4206b4

View的绘制-draw流程详解相关推荐

  1. View的绘制-layout流程详解

    目录 作用 根据 measure 测量出来的宽高,确定所有 View 的位置. 具体分析 View 本身的位置是通过它的四个点来控制的: 以下涉及到源码的部分都是版本27的,为方便理解观看,代码有所删 ...

  2. View系列 (三) — Measure 流程详解

    Measure 流程详解 一.概述 二.单一 View 的测量流程 1. 流程图 2. 源码分析 三.ViewGroup 的测量流程 1. 流程图 2. 源码分析 一.概述 测量过程分为 View的m ...

  3. Android AR开发实践之七:OpenGLES相机预览背景绘制源码详解

    Android AR开发实践之七:OpenGLES相机预览背景绘制源码详解 目录 Android AR开发实践之七:OpenGLES相机预览背景绘制源码详解 一.OpenGL ES渲染管线 1.基本处 ...

  4. Android事件流程详解

    Android事件流程详解 网络上有不少博客讲述了android的事件分发机制和处理流程机制,但是看过千遍,总还是觉得有些迷迷糊糊,因此特地抽出一天事件来亲测下,向像我一样的广大入门程序员详细讲述an ...

  5. java处理请求的流程_Java Spring mvc请求处理流程详解

    Spring mvc请求处理流程详解 前言 spring mvc框架相信很多人都很熟悉了,关于这方面的资料也是一搜一大把.但是感觉讲的都不是很细致,让很多初学者都云里雾里的.本人也是这样,之前研究过, ...

  6. android自定义view案例,Android自定义View的实现方法实例详解

    一.自绘控件 下面我们准备来自定义一个计数器View,这个View可以响应用户的点击事件,并自动记录一共点击了多少次.新建一个CounterView继承自View,代码如下所示: 可以看到,首先我们在 ...

  7. 无人机航测流程详解:航线规划、像控点布设、CC刺点建模及CASS成图

    无人机航测是传统航空摄影测量手段的有力补充,具有机动灵活.高效快速.精细准确.作业成本低.适用范围广.生产周期短等特点,在小区域和飞行困难地区高分辨率影像快速获取方面具有明显优势,随着无人机与数码相机 ...

  8. iOS APP上架流程详解

    iOS APP上架流程详解 青葱烈马 2016.04.28  前言:作为一名 iOS 开发工程师, APP 的上架是必备技能. iOS 上架的流程主要可以简单总结为: 一个包,两个网址,三个证书, 一 ...

  9. Android App启动流程详解

    前言:在之前的文章中已经写了apk的打包流程.安装流程,今天就是梳理一下apk系列的最后的流程--app启动流程.经过今天的梳理以后咱们就可以对apk包是怎么编译生成的.apk是怎么被安装到安卓手机的 ...

最新文章

  1. 千万用户同时在线,优酷智能档在双11“猫晚”直播如何防卡顿?
  2. java登录注册原理_案例:登录注册实现
  3. 软件工程专业指导1(定义内涵)
  4. Blazor.Server以正确的方式集成Ids4
  5. 【UVA12304】2D Geometry 110 in 1!(外接圆/内切圆/切点等圆相关问题的模版题)
  6. 西门子PCS7常见报警及故障说明
  7. 秃友进销存标准版内存注册机 Cracked.By.HackWm.
  8. word中如何将所有同一级标题统一格式
  9. 作为一名程序员,我都收集了哪些好玩的神器工具?
  10. bootstrap table合并单元格
  11. 华为交换机ARP防网关冲突
  12. 分享美团、字节、腾讯,java从入门到精通第四版光盘下载
  13. Apollo星火计划学习笔记——第一讲 使用Apollo学习自动驾驶
  14. java.sql.SQLException: 要执行的 SQL 语句不得为空白或空值
  15. ag-Grid 超丰富的表格插件(1)——简易使用
  16. React组件间信息传递方式
  17. 人工智能在各个领域里的应用场景
  18. 图表插件 - chart.js (柱状图) 学习总结
  19. 关于C++的数组或者字符串的输入问题
  20. 厉害!Facebook起诉欧盟委员会:称其数据请求超出必要

热门文章

  1. 怎么利用css调整区块大小,使用CSS3 transform:skew方法实现的倾斜区块分割
  2. 台式计算机属于,pc机属于什么型计算机
  3. java与c内存管理_Java基础--Java内存管理与垃圾回收
  4. malloc 初始化_你真的了解 NSObject 对象的初始化吗?
  5. vmware虚拟机迁移到hyperv_ProxmoxVE 之 V2V迁移(vmware-PVE)
  6. python安装环境安装_安装Python运行环境
  7. object detection
  8. 【杂谈】有三AI秋季划增加生成对抗网络小组,你准备好大GAN一场了吗
  9. 【AI初识境】近20年深度学习在图像领域的重要进展节点
  10. 中国水产饲料市场发展深度调研及十四五前景预测报告2022年版