View绘制就好比画画,抛开Android概念,如果要画一张图,首先会想到哪几个基本问题:

画多大?

画在哪?

怎么画?

Android绘制系统也是按照这个思路对View进行绘制,上面这些问题的答案分别藏在:

测量(measure)

定位(layout)

绘制(draw)

画什么?

这一篇将从源码的角度分析“绘制(draw)”。View绘制系统中的draw其实是讲的是绘制的顺序,至于具体画什么东西是各个子View自己决定的。

View.draw()

在分析View的测量和定位时,发现它们都是自顶向下进行地,即总是由父控件来触发子控件的测量或定位。不知道“绘制”是不是也是这样?,以View.draw()为切入点,一探究竟:

public void draw(Canvas canvas) {

/*

* 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);

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;

}

}

这个方法实在太长了。。。还好有注释帮我们提炼了一条主线。注释说绘制一共有6个步骤,他们分别是:

绘制控件背景

保存画布层

绘制控件自身内容

绘制子控件

绘制褪色效果并恢复画布层(感觉这一步和第二步是对称的)

绘制装饰物

为啥提炼了主线后还是觉得好复杂。。。还好注释又帮我们省去了一些步骤,注释说“通常情况下第二步和第五步会跳过。”在剩下的步骤中有三个步骤最最重要:

绘制控件背景

绘制控件自身内容

绘制子控件

读到这里可以得出结论:View绘制顺序是先画背景(drawBackground()),再画自己(onDraw()),接着画孩子(dispatchDraw())。晚画的东西会盖在上面。

先看下drawBackground():

/**

* Draws the background onto the specified canvas.

*

* @param canvas Canvas on which to draw the background

*/

private void drawBackground(Canvas canvas) {

//Drawable类型的背景图

final Drawable background = mBackground;

if (background == null) {

return;

}

setBackgroundBounds();

...

//绘制Drawable

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);

}

}

背景是一张Drawable类型的图片,直接调用Drawable.draw()将其绘制在画布上。接着看下onDraw():

public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {

/**

* Implement this to do your drawing.

*

* @param canvas the canvas on which the background will be drawn

*/

protected void onDraw(Canvas canvas) {

}

}

View.onDraw()是一个空实现。想想也对,View是一个基类,它只负责抽象出绘制的顺序,具体绘制什么由子类来决定,看一下ImageView.onDraw():

public class ImageView extends View {

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

...

//绘制drawable

if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {

mDrawable.draw(canvas);

} else {

final int saveCount = canvas.getSaveCount();

canvas.save();

if (mCropToPadding) {

final int scrollX = mScrollX;

final int scrollY = mScrollY;

canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,

scrollX + mRight - mLeft - mPaddingRight,

scrollY + mBottom - mTop - mPaddingBottom);

}

canvas.translate(mPaddingLeft, mPaddingTop);

if (mDrawMatrix != null) {

canvas.concat(mDrawMatrix);

}

mDrawable.draw(canvas);

canvas.restoreToCount(saveCount);

}

}

}

ImageView的绘制方法和View绘制背景一样,都是直接绘制Drawable。

ViewGroup.dispatchDraw()

View.dispatchDraw()也是一个空实现,想想也对,View是叶子结点,它没有孩子:

public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {

/**

* Called by draw to draw the child views. This may be overridden

* by derived classes to gain control just before its children are drawn

* (but after its own view has been drawn).

* @param canvas the canvas on which to draw the view

*/

protected void dispatchDraw(Canvas canvas) {

}

}

所以ViewGroup实现了dispatchDraw():

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

@Override

protected void dispatchDraw(Canvas canvas) {

...

// Only use the preordered list if not HW accelerated, since the HW pipeline will do the

// draw reordering internally

//当没有硬件加速时,使用预定义的绘制列表(根据z-order值升序排列所有子控件)

final ArrayList preorderedList = usingRenderNodeProperties

? null : buildOrderedChildList();

//自定义绘制顺序

final boolean customOrder = preorderedList == null

&& isChildrenDrawingOrderEnabled();

//遍历所有子控件

for (int i = 0; i < childrenCount; i++) {

...

//如果没有自定义绘制顺序和预定义绘制列表,则按照索引i递增顺序遍历子控件

final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);

final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);

if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {

//触发子控件自己绘制自己

more |= drawChild(canvas, child, drawingTime);

}

}

...

}

private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {

final int childIndex;

if (customOrder) {

final int childIndex1 = getChildDrawingOrder(childrenCount, i);

if (childIndex1 >= childrenCount) {

throw new IndexOutOfBoundsException("getChildDrawingOrder() "

+ "returned invalid index " + childIndex1

+ " (child count is " + childrenCount + ")");

}

childIndex = childIndex1;

} else {

//1.如果没有自定义绘制顺序,遍历顺序和i递增顺序一样

childIndex = i;

}

return childIndex;

}

private static View getAndVerifyPreorderedView(ArrayList preorderedList, View[] children,

int childIndex) {

final View child;

if (preorderedList != null) {

child = preorderedList.get(childIndex);

if (child == null) {

throw new RuntimeException("Invalid preorderedList contained null child at index "

+ childIndex);

}

} else {

//2.如果没有预定义绘制列表,则按i递增顺序遍历子控件

child = children[childIndex];

}

return child;

}

}

结合注释相信你一定看懂了:父控件会在dispatchDraw()中遍历所有子控件并触发其绘制自己。 而且还可以通过某种手段来自定义子控件的绘制顺序(对于本篇主题来说,这不重要)。

沿着调用链继续往下:

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

/**

* Draw one child of this View Group. This method is responsible for getting

* the canvas in the right state. This includes clipping, translating so

* that the child’s scrolled origin is at 0, 0, and applying any animation

* transformations.

* 绘制ViewGroup的一个孩子

*

* @param canvas The canvas on which to draw the child

* @param child Who to draw

* @param drawingTime The time at which draw is occurring

* @return True if an invalidate() was issued

*/

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {

return child.draw(canvas, this, drawingTime);

}

}

public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {

/**

* This method is called by ViewGroup.drawChild() to have each child view draw itself.

*

* This is where the View specializes rendering behavior based on layer type,

* and hardware acceleration.

*/

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

...

if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {

mPrivateFlags &= ~PFLAG_DIRTY_MASK;

dispatchDraw(canvas);

} else {

//绘制

draw(canvas);

}

...

}

ViewGroup.drawChild()最终会调用View.draw()。所以,View的绘制是自顶向下递归的过程,“递”表示父控件在ViewGroup.dispatchDraw()中遍历子控件并调用View.draw()触发其绘制自己,“归”表示所有子控件完成绘制后父控件继续后序绘制步骤`

总结

经过三篇文章的分析,对View绘制流程有了一个大概的了解:

View绘制流程就好比画画,它按先后顺序解决了三个问题 :

画多大?(测量measure)

画在哪?(定位layout)

怎么画?(绘制draw)

测量、定位、绘制都是从View树的根结点开始自顶向下进行地,即都是由父控件驱动子控件进行地。父控件的测量在子控件件测量之后,但父控件的定位和绘制都在子控件之前。

父控件测量过程中ViewGroup.onMeasure(),会遍历所有子控件并驱动它们测量自己View.measure()。父控件还会将父控件的布局要求与子控件的布局诉求相结合形成一个MeasureSpec对象传递给子控件以指导其测量自己。View.setMeasuredDimension()是测量过程的终点,它表示View大小有了确定值。

父控件在完成自己定位之后,会调用ViewGroup.onLayout()遍历所有子控件并驱动它们定位自己View.layout()。子控件总是相对于父控件左上角定位。View.setFrame()是定位过程的终点,它表示视图矩形区域以及相对于父控件的位置已经确定。

控件按照绘制背景,绘制自身,绘制孩子的顺序进行。父控件在完成绘制自身之后,会调用ViewGroup.dispatchDraw()遍历所有子控件并驱动他们绘制自己View.draw()

后记

有句话是这么说的:栽一棵树最好的时间是十年前,其次是现在。对于学习编程或者正在工作的朋友,如果你想更好的提升你的编程能力帮助你提升水平!笔者这里或许可以帮到你~

以下分享一下我4年来具体的学习路线及笔记文档,希望能帮助到有心提升技术的朋友!

由于篇幅原因,如有需要Android移动开发架构师学习笔记PDF,可以在我的GitHub无偿分享!!

好了,今天的文章就到这里,感谢阅读,喜欢的话不要忘了三连。大家的支持和认可,是我分享的最大动力。

android画a4矩形,Android自定义View绘制原理:画多大?画在哪?画什么?(三)相关推荐

  1. android覆盖扩散动画,[Android]多层波纹扩散动画——自定义View绘制

    之前整理过一些属性动画的基本操作,这一段时间的动画相关需求都安然度过了.直到这次-- 一.另一种动画需求 多数交互中的动画都是让单个页面元素动起来,这种就很适合用属性动画实现.但是对于 多个元素.非页 ...

  2. android 水波纹扩散动画,[Android]多层波纹扩散动画——自定义View绘制

    之前整理过一些属性动画的基本操作,这一段时间的动画相关需求都安然度过了.直到这次-- 一.另一种动画需求 多数交互中的动画都是让单个页面元素动起来,这种就很适合用属性动画实现.但是对于 多个元素.非页 ...

  3. Android自定义View绘制流程

    Android视图层次结构简介 在介绍View绘制流程之前,咱们先简单介绍一下Android视图层次结构以及DecorView,因为View的绘制流程的入口和DecorView有着密切的联系. 我们平 ...

  4. Android绘图机制(二)——自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解

    Android绘图机制(二)--自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解 我们要想画好一些炫酷的View,首先我们得知道怎么去画一些基础的图案,比如矩形,圆 ...

  5. android自定义弧度按钮,Android 自定义View 绘制六边形设置按钮

    今天逛酷安的时候,发现酷安的设置按钮(截图的右上角),是一个六边形 + 中心圆的图标,所以又是一个自定义View练习对象了.画圆很简单,知道半径即可,而重点就在画出六边形. 酷安截图.png 最终效果 ...

  6. Android自定义View绘制闹钟

    Android自定义View绘制闹钟 本文简单实现了一个闹钟,扩展View,Canvas绘制 效果如下: 代码如下: package com.gaofeng.mobile.clock_demo;imp ...

  7. Android 自定义View绘制电池图标

    /*** @anthor GrainRain* @funcation 自定义View绘制电池* @date 2019/8/27*/ public class DrawBatteryView exten ...

  8. Android软件开发之盘点自定义View界面大合集(二)

    Android软件开发之盘点自定义View界面大合集(二) - 雨松MOMO的程序世界 - 51CTO技术博客 雨松MOMO带大家盘点Android 中的自定义View界面的绘制 今天我用自己写的一个 ...

  9. Android中实现Bitmap在自定义View中的放大与拖动

    一基本实现思路: 基于View类实现自定义View –MyImageView类.在使用View的Activity类中完成OnTouchListener接口,实现对MotionEvent事件的监听与处理 ...

最新文章

  1. iOS 9应用开发教程之显示编辑文本标签文本框
  2. 软硬件融合加速技术系列文章
  3. oracle dp命令的使用说明
  4. linux screen 命令简单使用
  5. NYOJ练习题 Splits the string (简单动态规划)
  6. IOCP配合AcceptEx的例子
  7. 常见八种安卓开发报错的方式
  8. 64位ubuntu 12.04编译linux内核提示mkimage command not found
  9. frontcon函数用不了_C++复制构造函数与析构函数
  10. (1)DBA查询:数据库
  11. linux下mysql数据库目录迁移_mysql实现linux下数据库目录迁移
  12. ROS中测试机器人里程计信息
  13. MATLAB----数据拟合
  14. 【Android驱动】module_init 和 module_exit
  15. ZC公司员工评分系统——后台查询合成DataTable
  16. 常用网络测试的命令的实验报告计算机网络,实验一常用网络命令的使用实验报告-20210409133504.docx-原创力文档...
  17. 微吼直播 html5,微吼直播jssdk接入指引.pdf
  18. html万花筒相册旋转效果,jquery css3 3D万花筒图片相册展示特效
  19. 单片机开发无线控制系列-手机无线超声波测距
  20. 关于安卓开发,在鸿蒙系统应用时,File读取文被拒绝访问的解决方案

热门文章

  1. Git合并分支超详细解释
  2. vue.js表情文本输入框组件
  3. DSPE-PEG-PLA,DSPE-PEG2000-PLA,二硬脂酰基磷脂酰乙醇胺-聚乙二醇-聚乳酸
  4. Google Play需要更新或服务已停止运行解决方法
  5. 结构,设计模式,架构,框架
  6. SQLServer 中的 char varchar、nchar、nvarchar
  7. matting笔记_一周小结
  8. 分布式系统慎用@Transactional注解
  9. OSSEC解决IO占满问题
  10. 策勒县发展问题及对策分析