自定义View一直是初学者们最头疼的事情,因为他们并没有了解到真正的实现原理就开始试着做自定义View,碰到很多看不懂的代码只能选择回避,做多了会觉得很没自信。其实只要了解了View的工作机制后,会发现是挺简单的,自定义View就是借助View的工作机制开始将View绘制出来的

Android视图工作机制按顺序分为以下三步:

  1. measure:确定View的宽高
  2. layout:确定View的位置
  3. draw:绘制出View的形状

Android视图工作机制其实挺人性化的,当你真正理解之后,就跟我们画画是一个道理的,下面为了更好的理解,我将自定义View的过程拟物化

相关概念:

  • View(照片框):自定义View
  • measure(尺子):测量View大小
  • MeasureSpec(尺子刻度):测量View大小的测量单位
  • layout(照片框的位置):View的具体位置
  • draw(笔):绘制View

画图步骤:

  1. 首先画一个100 x 100的照片框,需要尺子测量出宽高的长度(measure过程)
  2. 然后确定照片框在屏幕中的位置(layout过程)
  3. 最后借助尺子用手画出我们的照片框(draw过程)

自定义View第一步是测量,而测量需要测量规格(或测量标准)才能知道View的宽高,所以在测量之前需要认识MeasureSpec类

MeasureSpec类是决定View的measure过程的测量规格(比喻:尺子),它由以下两部分组成

  • SpecMode:测量模式(比喻:直尺、三角尺等不同类型)
  • SpecSize:测量模式下的规格大小(比喻:尺子的刻度)

MeasureSpec的表示形式是32位的int值

  • 高2位(前面2位):表示测量模式,即SpecMode
  • 低30位(后面30位):表示在测量模式下的测量规格大小,即SpecSize

MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配

我们都知道SpecMode的尺子类型有很多,不同的尺子有不同的功能,而SpecSize刻度是固定的一种,所以SpecMode又分为三种模式

  • UNSPECIFIED:父容器不对View有任何大小的限制,这种情况一般用于系统内部,表示一种测量状态
  • EXACTLY:父容器检测出View所需要的精确大小,这时候View的值就是SpecSize
  • AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值

一、结论:子View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定的

  1. 首先要知道LayoutParams有三种情况:MATCH_PARENT、WARP_CONTENT、100dp(精确大小)
  2. 只要子View的MeasureSpec被确定,那么就可以在measure过程中,测量出子View的宽高

二、通过例子来解释结论

  1. 假如父容器LinearLayout的MeasureSpec:EXACTLY、AT_MOST的任意一种
    子View的LayoutParams:精确大小(100dp)

    也就是说:子View必须是指定大小,不管父容器载不载得下子View

    所以返回子View的MeasureSpec:EXACTLY
    所以返回子View测量出来的大小:子View自身精确大小

  2. 假如父容器LinearLayout的MeasureSpec:EXACTLY、AT_MOST的任意一种

    子View的LayoutParams:MATCH_PARENT

    也就是说:子View必须占满整个父容器,那么父容器多大,子View就多大

    所以返回子View的MeasureSpec:跟父容器一致
    所以返回子View测量出来的大小:父容器可用大小

  3. 假如父容器LinearLayout的MeasureSpec:EXACTLY、AT_MOST的任意一种

    子View的LayoutParams:WARP_CONTENT

    也就是说:子View必须自适应父容器,父容器不管多小,你都不能超过它,只能自适应的缩小

    所以返回子View的MeasureSpec:AT_MOST(不能超过父容器本身)

    所以返回子View测量出来的大小:父容器可用大小

至于第4种情况,父容器是UNSPECIFIED的时候,由于父容器不知道自己多大,而子View又采用MATCH_PARENT、WARP_CONTENT的时候,子View肯定也不知道自己多大,所以只有当子View采用EXACTLY的时候,才知道自己多大

三、通过图片分析结论结果

通过上面的例子总结,我们可以通过父容器的测量规格和子View的布局参数来确定子View的MeasureSpec,这样便确立了子View的宽高,下面是父容器测量规格和子View布局参数确立子ViewMeasureSpec的结果图

measure过程其实和事件分发有点类似,也包括ViewGroup和View,我们通过各自的源码来分析其measure的过程

一、ViewGroup的measure过程

ViewGroup源码中,提供了一个measureChildren的方法来遍历调用子View的measure方法,而各个子View再递归去执行这个过程

从源码中分析,ViewGroup的measure过程特别简单,并且可以看到子View的MeasureSpec确实是通过父容器的MeasureSpec和自身的LayoutParams决定的,这也就印证了结论所说的话,但是measure的过程还是依靠子View自身的MeasureSpec

二、View的measure过程

View的源码中,由于measure方法是个final类型的,所以子类不能重写此方法

可以发现,View的measure方法中,会调用自身的onMeasure方法(平时,自定义View重写这个方法,就是对自定义的View根据自己定的规则来确定测量大小),下面继续追踪

从onMeasure方法中,有getDefaultSize()、getSuggestedMinimumWidth()、getSuggestedMinimumHeight(),它们之间又是什么呢,继续追踪

1、getDefaultSize()

很显然,如果你自定义不重写onMeasure()方法的话,那么系统就会采用默认的测量模式来确定你的测量大小,即getDefaultSize(),它的逻辑很简单,不去看UNSPECIFIED模式,它就是返回specSize,即View测量后的大小

2、getSuggestedMinimumWidth()和getMinimumHeight()

getSuggestedMinimumWidth和getSuggestedMinimumHeight原理是一样的,如果View没有设置背景,那么View的宽度为mMinWidth,而mMinWidth对应的就是android:minWidth这个属性的值,如果这个属性不指定,那么mMinWidth默认为0;如果指定了背景,那么View的宽度就是max(mMinWidth,
mBackground.getMinimumWidth()),而这里的getMinimumWidth()又是什么,继续追踪

getMinimumWidth是在Drawable类中的,它返回的是Drawable的原始宽度,如果没有Drawable,则返回0

到这里measure过程就结束了,如果是自定义View的话,就重写onMeasure方法,将其默认的测量方式改为我们自己规定的测量方式,最后获得我们的宽高

layout过程就比measure过程简单多了,因为它不用什么规格之类的东西,下面是View的layout源码

可以发现,View只需要4个点即可确定一个矩形,就是View的位置,然后调用onLayout()

onLayout()方法其实就是一个空方法,当我们在自定义View时重写onLayout()方法,其实就是让我们重新设置View的位置

draw过程也很简单,就是将View绘制到屏幕上,它有如下几个步骤

  1. 绘制背景:drawBackground(canvas)
  2. 绘制自己:if (!dirtyOpaque) onDraw(canvas)
  3. 绘制children:dispatchDraw(canvas)
  4. 绘制装饰:onDrawForeground(canvas)

我们常常就是重写onDraw()方法来绘制我们的自定义View,否则是没有图像的,这点在源码中也是提供了onDraw()的空实现方法给我们去绘制图像

一、invalidate()和requestLayout()

invalidate()和requestLayout(),常用于View重绘和更新,其主要区别如下

  • invalidate方法只会执行onDraw方法
  • requestLayout方法只会执行onMeasure方法和onLayout方法,并不会执行onDraw方法。

所以当我们进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;若两者均发生改变,口语陪练则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法

二、invalidate()和postInvalidate()

  • invalidate方法用于UI线程中重新绘制视图
  • postInvalidate方法用于非UI线程中重新绘制视图,省去使用handler

Android的自定义其实很简单,对于初学者,可能就是measure过程比较难以理解,不过不要紧,每个人初学都是这样的,建议多多实践,花点时间去研究,你会更加熟能生巧,根本不用死记硬背,只要有思路便可以画出你想要的自定义View,当然,能结合动画那就更完美了,加油

Android进阶——Android视图工作机制之measure、layout、draw的更多相关文章

  1. Android应用层View绘制流程之measure,layout,draw三步曲

    概述 上一篇博文对DecorView和ViewRootImpl的关系进行了剖析,这篇文章主要是来剖析View绘制的三个基本流程:measure,layout,draw.仅仅有把这三个基本流程搞清楚了, ...

  2. Android进阶——Android消息机制之Looper、Handler、MessageQueen

    Android消息机制可以说是我们Android工程师面试题中的必考题,弄懂它的原理是我们避不开的任务,所以长痛不如短痛,花点时间干掉他,废话不多说,开车啦 在安卓开发中,常常会遇到获取数据后更新UI ...

  3. Android 进阶 Android 中的 IOC 框架 【ViewInject】 (下)

    上一篇博客我们已经带大家简单的吹了一下IoC,实现了Activity中View的布局以及控件的注入,如果你不了解,请参考:Android 进阶 教你打造 Android 中的 IOC 框架 [View ...

  4. Android进阶——Android事件分发机制之dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent

    Android事件分发机制可以说是我们Android工程师面试题中的必考题,弄懂它的原理是我们避不开的任务,所以长痛不如短痛,花点时间干掉他,废话不多说,开车啦 Android事件分发机制的发生在Vi ...

  5. Android 进阶Android 中的 IOC 框架 【ViewInject】 (上)

    1.概述 首先我们来吹吹牛,什么叫IoC,控制反转(Inversion of Control,英文缩写为IoC),什么意思呢? 就是你一个类里面需要用到很多个成员变量,传统的写法,你要用这些成员变量, ...

  6. Android全面解析之Window机制

    前言 你好! 我是一只修仙的猿,欢迎阅读我的文章. Window,读者可能更多的认识是windows系统的窗口.在windows系统上,我们可以多个窗口同时运行,每个窗口代表着一个应用程序.但在安卓上 ...

  7. Android 中View的绘制机制源代码分析 一

    尊重原创: http://blog.csdn.net/yuanzeyao/article/details/46765113 差点儿相同半年没有写博客了,一是由于工作比較忙,二是认为没有什么内容值得写, ...

  8. Android开发——Android系统启动以及APK安装、启动过程

    0. 前言   从Android手机打开开关,到我们可以使用其中的app时,这个启动过程到底是怎么样的? 1.  系统上电 当给Android系统上电,在电源接通的瞬间,CPU内的寄存器和各引脚均会被 ...

  9. Android 进阶8:进程通信之 Binder 机制浅析

    读完本文你将了解: IBinder Binder Binder 通信机制 Binder 驱动 Service Manager Binder 机制跨进程通信流程 Binder 机制的优点 总结 Than ...

随机推荐

  1. upload-labs-env文件上传漏洞 11-19关

    Pass-11 源码:加上了本人的注释=.= $is_upload = false; $msg = null; if(isset($_POST['submit'])){ $ext_arr = arra ...

  2. Job 失败了怎么办?【转】

    上一节讨论了 Job 执行成功的情况,如果失败了会怎么样呢? 修改 myjob.yml,故意引入一个错误: 先删除之前的 Job: 如果将 restartPolicy 设置为 OnFailure 会怎 ...

  3. PHP mb_substr mbstring 函数

    定义和用法 mb_substr - 获取部分字符串 版本支持 PHP4 PHP5 PHP7 支持 支持 支持 5.4.8 length 传入 NULL,则从 start 提取到字符串的结尾处. 在之前 ...

  4. python中pandas数据分析基础3(数据索引、数据分组与分组运算、数据离散化、数据合并)

    //2019.07.19/20 python中pandas数据分析基础(数据重塑与轴向转化.数据分组与分组运算.离散化处理.多数据文件合并操作) 3.1 数据重塑与轴向转换1.层次化索引使得一个轴上拥 ...

  5. ubuntu---NVIDIA驱动 + CUDA 安装完可能会遇见的问题

    如果稍不注意:系统内核.GCC.下载的版本不对应.安装过程中选项选择不正确,在NVIDIA驱动 + CUDA 安装完后可能会遇见一些问题. 一.登陆不进桌面 可能的操作: (1)nivida驱动安装完 ...

  6. 吴裕雄--天生自然java开发常用类库学习笔记:线程的生命周期

    class MyThread implements Runnable{ private boolean flag = true ; // 定义标志位 public void run(){ int i ...

  7. MSDN原版系统镜像ISO下载站

    官网 网址1:http://www.imsdn.cn/ Windows 10 最新版本 1909 正式版 ISO 镜像下载 (微软 MSDN / VL 官方原版系统) 网址2:https://www. ...

  8. AS-PATH(路径属性)路由路径欺骗术

    AS-PATH(路径属性)路由路径欺骗术: ①:抓取感兴趣流量——前缀与访问 ②:创建路由地图 ③:路由地图第一法则——permit 10 ④:在第一法则中,匹配(感兴趣流量) ⑤:设置 路径欺骗术— ...

  9. pwntool基础和ida常用操作

    pwntools:http://www.91ri.org/14382.html ida:https://www.jianshu.com/p/d425140c6561

  10. 事件时间(event time)与水印(watermark)

    事件时间和水印诞生的背景 在实际的流式计算中数据到来的顺序对计算结果的正确性有至关重要的影响 比如:某数据源中的某些数据由于某种原因(如:网络原因,外部存储自身原因)会有2秒的延时,也就是在实际时间的 ...