自定义View Draw过程(4)
目录
1. 知识基础
具体请看我写的另外一篇文章:自定义View基础 - 最易懂的自定义View原理系列
2. draw过程作用
绘制View视图
3. draw过程详解
同measure、layout过程一样,draw过程根据View的类型分为两种情况:
- 如果View = 单一View,则仅绘制本身View;
- 如果View = VieGroup(包含子View),除了绘制自身View外,还需要绘制子View。
接下来,我将详细分析这两种情况下的draw过程。
3.1 单一View的draw过程
3.1.1 应用场景
在没有现成的View,需要自己实现的时候,就使用自定义View,一般继承自View、SurfaceView等
特点是:不包含子View。
3.1.2 原理(步骤)
- 步骤1:View绘制自身(含背景、内容);
- 步骤2:绘制装饰(滚动指示器、滚动条、和前景)
3.1.3 单一View的的具体draw过程
如下图所示:
下面我将一个个方法进行详细分析。
步骤1: draw()
作用:根据给定的 Canvas 自动渲染 View(包括其所有子 View)。
在调用该方法之前必须要完成 layout 过程
public void draw(Canvas canvas) {
// 特别注意:
// 所有的视图最终都是调用 View 的 draw ()绘制视图( ViewGroup 没有复写此方法)
// 在自定义View时,不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制。
// 如果自定义的视图确实要复写该方法,那么需要先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制。
...
/*
* 绘制过程如下:
* 1. 绘制view背景
* 2. 绘制view内容
* 3. 绘制子View
* 4. 绘制装饰(渐变框,滑动条等等)
*/
int saveCount;
if (!dirtyOpaque) {
// 步骤1: 绘制本身View背景
drawBackground(canvas);
}
// 如果有必要,就保存图层(还有一个复原图层)
// 优化技巧:
// 当不需要绘制 Layer 时,“保存图层“和“复原图层“这两步会跳过
// 因此在绘制的时候,节省 layer 可以提高绘制效率
final int viewFlags = mViewFlags;
if (!verticalEdges && !horizontalEdges) {
if (!dirtyOpaque)
// 步骤2:绘制本身View内容
onDraw(canvas);
// View 中:默认为空实现
// ViewGroup中:自定义View时需要进行复写!!!!
..
// 步骤3:绘制子View
dispatchDraw(canvas);
// 由于单一View没有子View,所以View 中:默认为空实现
...
// 步骤4:绘制滑动条和前景色等等
onDrawScrollBars(canvas);
// we're done...
return;
}
...
}
下面,我们继续分析在draw()中调用的drawBackground()、 onDraw()、dispatchDraw()、onDrawScrollBars(canvas)
步骤2: drawBackground()
- 作用:绘制自身View的背景
- 源码分析如下:
private void drawBackground(Canvas canvas) {
// 获取背景 drawable
final Drawable background = mBackground;
if (background == null) {
return;
}
// 根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界
setBackgroundBounds();
.....
// 获取 mScrollX 和 mScrollY值
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
// 如果 mScrollX 和 mScrollY 有值,则对 canvas 的坐标进行偏移
canvas.translate(scrollX, scrollY);
// 调用 Drawable 的 draw 方法绘制背景
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
步骤3: onDraw()
- 作用:绘制自身View的内容
- 源码分析如下:
// 由于 View 的内容各不相同,所以该方法是一个空实现
// 在自定义绘制过程中,需要由子类去实现复写该方法从而绘制自身的内容。
protected void onDraw(Canvas canvas) {
// 复写从而实现绘制逻辑
}
请记住:自定义View中必须 且 只需要复写onDraw()。
步骤4: dispatchDraw()
- 作用:绘制子View
- 源码分析:
// 单一View 中:默认为空实现
// 因为没有子View,不需要绘制子View,所以为空实现
protected void dispatchDraw(Canvas canvas) {
}
步骤5: onDrawForeground()
- 作用:绘制装饰(滚动指示器、滚动条、和前景)
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);
}
foreground.draw(canvas);
}
}
至此,单一View的draw过程已经分析完毕。
3.1.4 总结
单一View的draw过程解析如下:
只需要绘制自身View
3.2 ViewGroup的draw过程
3.2.1 应用场景
利用现有组件根据特定布局方式来组成新的组件,大多继承自ViewGroup或各种Layout
特点:含有子View
3.2.2 原理(步骤)
步骤1:ViewGroup绘制自身(含背景、内容);
步骤2:ViewGroup遍历子View并绘制包含的所有子View;
类似于单一View的draw过程
步骤3:ViewGroup绘制装饰(滚动指示器、滚动条、和前景)
这样自上而下、一层层地传递下去,直到完成整个View树的draw过程
3.2.3 ViewGroup的具体draw过程
如下图所示:
下面我将对每个步骤和方法进行详细分析。
步骤1:draw()
作用:根据给定的 Canvas 自动渲染 View
在调用该方法之前必须要完成 layout 过程
源码分析:(与单一View draw过程的draw()类似)
public void draw(Canvas canvas) {
// 特别注意:
// 所有的视图最终都是调用 View 的 draw ()绘制视图( ViewGroup 没有复写此方法)
// 在自定义View时,不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制。
// 如果自定义的视图确实要复写该方法,那么需要先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制。
...
/*
* 绘制过程如下:
* 1. 绘制view背景
* 2. 绘制view内容
* 3. 绘制子View
* 4. 绘制装饰(渐变框,滑动条等等)
*/
int saveCount;
if (!dirtyOpaque) {
// 步骤1: 绘制本身View背景
drawBackground(canvas);
}
// 如果有必要,就保存图层(还有一个复原图层)
// 优化技巧:
// 当不需要绘制 Layer 时,“保存图层“和“复原图层“这两步会跳过
// 因此在绘制的时候,节省 layer 可以提高绘制效率
final int viewFlags = mViewFlags;
if (!verticalEdges && !horizontalEdges) {
if (!dirtyOpaque)
// 步骤2:绘制本身View内容
onDraw(canvas);
// View 中:默认为空实现
// ViewGroup中:自定义View时需要进行复写!!!!
..
// 步骤3:绘制子View
dispatchDraw(canvas);
// View 中:默认为空实现
// ViewGroup中:系统已经复写好对其子视图进行绘制我们不需要复写
...
// 步骤4:绘制滑动条和前景色等等
onDrawScrollBars(canvas);
// we're done...
return;
}
...
}
步骤2:drawBackground()、步骤3:onDraw()、步骤5:onDrawForeground()
- 与单一View的draw过程类似,详细请回看上面描述。
- 下面直接进入与单一View draw过程最大不同的
步骤4:dispatchDraw()。
步骤4: dispatchDraw()
- 作用:遍历子View并绘制
- 源码分析:
// 仅贴出重要代码
// 特别注意:
// ViewGroup中:由于 系统 已经为我们实现了该方法,所以我们一般都不需要重写该方法
// View中默认为空实现(因为没有子View可以去绘制)
protected void dispatchDraw(Canvas canvas) {
......
// 遍历子View
final int childrenCount = mChildrenCount;
......
for (int i = 0; i < childrenCount; i++) {
......
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
// 绘制视图
// 继续看下面源码分析
more |= drawChild(canvas, transientChild, drawingTime);
}
......
}
}
// drawChild()源码分析
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// 最终还是调用了子 View 的 draw ()进行子View的绘制
return child.draw(canvas, this, drawingTime);
}
至此,ViewGroup的draw过程已经分析完毕。
3.2.4 总结
对于ViewGroup的draw过程流程如下:
4. 其他细节问题:View.setWillNotDraw()
View 中有一个特殊的方法:setWillNotDraw()
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
- 该方法用于设置 WILL_NOT_DRAW 标记位
- 该标记位的作用是:当一个View不需要绘制内容时,系统进行相应优化
默认情况下:View 不启用该标记位(设置为true);ViewGroup 默认启用(设置为false)
应用场景:
setWillNotDraw参数设置为true:当自定义View继承自 ViewGroup 、且本身并不具备任何绘制时,设置为 true 后,系统会进行相应的优化。
setWillNotDraw参数设置为false:当自定义View继承自 ViewGroup 、且需要绘制内容时,那么设置为 false,来关闭 WILL_NOT_DRAW 这个标记位。
5. 总结
- 对于ViewGroup的draw过程
步骤1:ViewGroup绘制自身(含背景、内容);
步骤2:ViewGroup遍历子View并绘制包含的所有子View;
类似于单一View的draw过程
步骤3:ViewGroup绘制装饰(滚动指示器、滚动条、和前景)
对于View的draw过程
只需要绘制自身(含背景、内容)+装饰即可一个图总结自定义View - draw过程,如下图:
自定义View Draw过程(4)的更多相关文章
- 自定义View Layout过程 (3)
目录 目录 1. 知识基础 具体请看我写的另外一篇文章:(1)自定义View基础 - 最易懂的自定义View原理系列 2. 作用 计算View视图的位置. 即计算View的四个顶点位置:Left.To ...
- 自定义View Measure过程(2)
目录 目录 1. 作用 测量View的宽/高 在某些情况下,需要多次测量(measure)才能确定View最终的宽/高: 在这种情况下measure过程后得到的宽/高可能是不准确的: 建议在layou ...
- 自定义view布局过程详解
布局过程,就是程序在运行时利用布局文件的代码来计算出实际尺寸的过程. 布局分为两个阶段:测量阶段和布局阶段. 测量阶段:从上到下递归地调用每个 View 或者 ViewGroup 的 measure( ...
- 手把手带你做一个超炫酷loading成功动画view Android自定义view
写在前面: 本篇可能是手把手自定义view系列最后一篇了,实际上我也是一周前才开始真正接触自定义view,通过这一周的练习,基本上已经熟练自定义view,能够应对一般的view需要,那么就以本篇来结尾 ...
- 自定义View系列教程06--详解View的Touch事件处理
深入探讨Android异步精髓Handler 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Andr ...
- 自定义View和ViewGroup
为了扫除学习中的盲点,尽可能多的覆盖Android知识的边边角角,决定对自定义View做一个稍微全面一点的使用方法总结,在内容上面并没有什么独特的地方,其他大神们的博客上面基本上都有讲这方面的内容,如 ...
- 自定义view(一)
为什么标题会是自定义view(一)呢?因为自定义view其实内容很多,变化也很多,所以我会慢慢更新博客,争取多写的有关的东西,同时,如果我以后学到了新的有关于自定义view的东西,我也会及时写出来. ...
- Android 自定义View实现QQ运动积分抽奖转盘
因为偶尔关注QQ运动, 看到QQ运动的积分抽奖界面比较有意思,所以就尝试用自定义View实现了下,原本想通过开发者选项查看下界面的一些信息,后来发现积分抽奖界面是在WebView中展示的,应该是在H5 ...
- 手把手带你画一个漂亮蜂窝view Android自定义view
上一篇做了一个水波纹view 不知道大家有没有动手试试呢点击打开链接 这个效果做起来好像没什么意义,如果不加监听回调 图片就能直接替代.写这篇博客的目的是锻炼一下思维能力,以更好的面多各种自定义vi ...
随机推荐
- 001---web应用程序
什么是web应用? 应用程序分两种模式:C/S.B/S 1 .C/S:客户端(Client)与服务端 一般独立运行 2 .B/S:浏览器(Browser)与服务端 这类应用要借助浏览器:谷歌.火狐.I ...
- POJ:3685-Matrix
Matrix Time Limit: 6000MS Memory Limit: 65536K Total Submissions: 7879 Accepted: 2374 Description Gi ...
- python Beautiful Soup库入门
bs4库的HTML内容遍历方法 基于bs4库的HTML格式输出 显示:友好的显示 <tag>.prettify() 编码:bs4库将任何HTML输入都变成utf-8编码(python 3. ...
- ArrayList & Vector的源码实现
#ArrayList & Vector #####前言: 本来按照计划,ArrayList和Vector是分开讲的,但是当我阅读了ArrayList和Vector的源码以后,我就改变了注意,把 ...
- Spring---配置文件概述
概述 Spring 的配置文件是用于指导 Spring 工厂进行Bean的生产.依赖关系注入及 Bean 实例分发的“图纸”,它是一个或多个标准的XML文档,J2EE 程序员必须学会并灵活应用这份“图 ...
- linux下vi的复制,黏贴,删除,撤销,跳转等命令-费元星
前言 在嵌入式linux开发中,进行需要修改一下配置文件之类的,必须使用vi,因此,熟悉 vi 的一些基本操作,有助于提高工作效率. 一,模式vi编辑器有3种模式:命令模式.输入模式.末行模式. ...
- 新工具填补Docker管理空白
[TechTarget中国原创] 从事容器管理领域的IT运维专家这周需要评估一个新的工具. Docker推出了一款新产品,意在让IT运维人员能够跟上开发人员的脚步,这一产品是Docker Datace ...
- b树的实现
花了蛮长时间实现的b树插入操作.有时间再实现其他操作. #include <stdio.h> #include <stdlib.h> #define M 5 enum KeyS ...
- Java基础-6流程控制
一).选择控制: 选择控制分为两种:if...else...和switch 单分支结构:这是最简单的一种选择结构,它只是简单的判断某个条件是否成立,如果成立就执行一段代码,语句形式为: if(条件表达 ...
- iframe 如何让它展现内容自适应高度
引用: <iframe id="ifm1" runat="server" src="/comment/page1?id=@productId&q ...