View的draw过程相比之于measrue过程,也是比较简单的。并且在我们自定义View时,也经常需要重写onDraw方法,来绘制出我们要实现的效果。

如之前的文章所说,绘制的流程也是起始于ViewRootImpl#perfomTraversalsViewRootImpl#performDraw() 方法调用了ViewRootImpl#draw(boolean fullRedrawNeeded) ,其中这个boolean类型的形参,作用是判断是否需要重新绘制全部视图。最后调用的DecorView.draw(canvas)方法,自此开始了正式的绘制流程。

ViewGroup当中并没有实现draw(Canvas canvas)onDraw(Canvas canvas)方法,所以所有View都是调用View#draw方法, 其源码如下:

    /**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags; //@author www.yaoxiaowen.com
// dirtyOpaque 标志位,判断该view是否透明,如果透明,它就可以省略一些步骤
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); // 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;
} //...
}

源码还是比较复杂的,不过注释倒是很清晰的。所以结合注释看源码,倒也算是一种正确的姿势。

注释当中清晰的说明了绘制流程的6个步骤。

  1. 绘制View背景。drawBackground
  2. 保存当前图层信息。(可跳过)
  3. 绘制View内容。onDraw(canvas)
  4. 绘制子View。(如果有的话)。dispatchDraw(canvas)
  5. 绘制View的褪色边缘,类似于阴影效果。(可跳过)
  6. 绘制View的装饰。(比如滚动条)。onDrawForeground(canvas)

Skip 1 : 绘制背景

这里调用的是 View#drawBackground方法,源码如下:

private void drawBackground(Canvas canvas) {

    //mBackground是该View的背景参数,比如背景颜色
final Drawable background = mBackground;
if (background == null) {
return;
} //根据View四个布局参数来确定背景的边界
setBackgroundBounds(); ... //获取当前View的mScrollX和mScrollY值
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
//如果scrollX和scrollY有值,则对canvas的坐标进行偏移,再绘制背景
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}

Skip 3 : 绘制内容

View#onDraw(Canvas)方法是空实现,这是因为不同View有不同的内容,所以具体各个子View都有不同的绘制内容。而我们在自定义View的时候,也一定要重写该方法。

Skip 4 : 绘制子View

如果当前View是ViewGroup容器类,那么它就要去循环遍历绘制它的子View了。而View#dispatchDraw是空实现,只有ViewGroup#dispatchDraw才有具体实现。

ViewGroup#dispatchDraw方法的主要内容是 遍历所有子View,对每个子View调用ViewGroup#drawChild方法,该方法源码如下:

//ViewGroup#drawChild
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}

这里调用的是 View#draw(Canvas canvas, ViewGroup parent, long drawingTime)方法。这其实是View#draw(Canvas canvas)的重载方法.

而三个参数的draw方法,则会先判断是否有缓存,如果有缓存,则利用缓存显示。如果没有,则 调用draw(canvas)方法,开始正常绘制流程(就是那6个步骤)。

Skip 6 : 绘制装饰

所谓绘制装饰,就是指View除了背景,内容,子View的其余部分。(比如滚动条)。

调用的是 View#onDrawForeground方法,该过程和一般的绘制流程比较相似,都是先设定绘制区域,然后再利用 canvas进行绘制。

自此,view的draw流程就已经分析完了。

其实在这篇博客中,自己思考分析原创部分比较少,而参考别人的内容比较多。

而其实 对于我们自定义控件而言,如果是自定义ViewGroup,则不用重写onDraw方法,它默认就会遍历所有子View进行绘制。

而如果自定义View的话,那么需要重写onDraw方法,然后结合

Paint来使用canvas.drawRect, canvas#drawText等方法来绘制出我们所需要的内容,所以这里要对相关的API比较熟悉。

我觉的这就像之前写的那篇 注解(二)- 自定义注解处理器 一样,虽然知道相关流程,但是对相关API不熟悉,其实也无力去实现很多功能。

因此未来如果有时间的话,希望能结合具体的自定义控件,和相关的常用API来就相关过程,进行更详细的讨论吧。


参考内容:


作者:
www.yaoxiaowen.com

github:
https://github.com/yaowen369

欢迎对于本人的博客内容批评指点,如果问题,可评论或邮件(yaowen369@gmail.com)联系

<p >
欢迎转载,转载请注明出处.谢谢
</p> <script type="text/javascript">
function Curgo()
{
window.open(window.location.href);
}
</script>

View学习(四)-View的绘制(draw)过程的更多相关文章

  1. View学习(二)-View的测量(measure)过程

    在上一篇文章中,我们介绍了DecorView与MeasureSpec, 下面的文章就开始讨论View的三大流程. View的三大流程都是通过ViewRoot来完成的.ViewRoot对应于ViewRo ...

  2. dubbo源码学习(四):暴露服务的过程

    dubbo采用的nio异步的通信,通信协议默认为 netty,当然也可以选择 mina,grizzy.在服务端(provider)在启动时主要是开启netty监听,在zookeeper上注册服务节点, ...

  3. Android学习笔记View的工作原理

    自定义View,也可以称为自定义控件,通过自定义View可以使得控件实现各种定制的效果. 实现自定义View,需要掌握View的底层工作原理,比如View的测量过程.布局流程以及绘制流程,除此之外,还 ...

  4. Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)

    View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇  ...

  5. [Android学习笔记]View的draw过程学习

    View从创建到显示到屏幕需要经历几个过程: measure -> layout -> draw measure过程:计算view所占屏幕大小layout过程:设置view在屏幕的位置dr ...

  6. 【Android - 自定义View】之View的draw过程解析

    draw(绘制)过程的作用是将View绘制到屏幕上面.View中有 draw() 方法和 onDraw() 方法,但onDraw()方法是空方法:ViewGroup中没有draw()方法,也没有onD ...

  7. Android UI 绘制过程浅析(四)draw过程

    前言 draw是绘制View三个步骤中的最后一步.同measure.layout一样,通常不对draw本身进行重写,draw内部会调用onDraw方法,子类View需要重写onDraw(Canvas) ...

  8. [Android学习笔记]view的layout过程学习

    View从创建到显示到屏幕需要经历几个过程: measure -> layout -> draw measure过程:计算view所占屏幕大小layout过程:设置view在屏幕的位置dr ...

  9. [Android学习笔记]View的measure过程学习

    View从创建到显示到屏幕需要经历几个过程: measure -> layout -> draw measure过程:计算view所占屏幕大小layout过程:设置view在屏幕的位置dr ...

随机推荐

  1. 数据库索引------B-Tree 索引和 Hash 索引的对比

    对于 B-tree 和 hash 数据结构的理解能够有助于预测不同存储引擎下使用不同索引的查询性能的差异,尤其是那些允许你选择 B-tree 或者 hash 索引的内存存储引擎. B-Tree 索引的 ...

  2. .NET Core快速入门教程 1、开篇:说说.NET Core的那些事儿

    一..NET Core的诞生 聊 .NET Core,就不得不说他的爸爸 .NET.当年Java刚刚兴起,如火如荼,微软也非常推崇Java,当时Windows平台的Java虚拟机就是微软按照JVM标准 ...

  3. 利用模板template动态渲染jsp页面

    一.场景 在js中写html简直是噩梦,刚进新公司,在codereview的时候得知可以通过将html模板写在jsp页面,然后由js调取模板,利用replace()方法替换传值的方式避免在js中拼接h ...

  4. nuget服务器搭建,以及如何发布一个Nuget包

    本文章主要介绍如何将本地dll打包成为一个Nuget包,并如何发布到自己的nuget服务器.章节如下 1. 本地dll如何打包,以及版本的更新 2. 在linux上搭建nuget.server 3. ...

  5. Linux多进程编程实例

    前言:编写多进程程序时,我们应该了解一下,创建一个子进程时,操作系统内核是怎样做的.当通过fork函数创建新的子进程时,内核将父进程的用户地址空间的内容复制给子进程,这样父子进程拥有各自独立的用户空间 ...

  6. PHP运算符优先级 运算符分类

    运算符 运算符是可以通过给出的一或多个值(用编程行话来说,表达式)来产生另一个值(因而整个结构成为一个表达式)的东西. 运算符可按照其能接受几个值来分组.一元运算符只能接受一个值,例如 !(逻辑取反运 ...

  7. ActiveMQ基本详解与总结

    MQ简介: MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过写和检索出入列队的针对应用程序的数据(消息)来通信,而无需专用连接来链接它们.消息传 ...

  8. 向MapReduce转换:生成用户向量

    分两部分: <span style="font-size:18px;">/*** * @author YangXin * @date 2016/2/21 * @ inf ...

  9. POJ3621 Sightseeing Cows(最优比率环)

    题目链接:id=3621">http://poj.org/problem?id=3621 在一个有向图中选一个环,使得环上的点权和除以边权和最大.求这个比值. 经典的分数规划问题,我认 ...

  10. 用IFeatureWorkspaceAnno.CreateAnnotationClass 创建注记图层时报“The application is not licensed to modify or create schema”的错误的解决方案。

    用IFeatureWorkspaceAnno.CreateAnnotationClass 的方法创建注记图层的时候报"The application is not licensed to m ...