自定义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. BUU pwn cn

    自己不细心,人家别的博客上写的明明没有那个冒号的,把linux命令好好学一学吧! nc后 ls 发现flag文件 cat就得到flag了

  2. Matplotlib 多个图形

    章节 Matplotlib 安装 Matplotlib 入门 Matplotlib 基本概念 Matplotlib 图形绘制 Matplotlib 多个图形 Matplotlib 其他类型图形 Mat ...

  3. CCF 201703-4 地铁修建(最小生成树)

    题意:A市有n个交通枢纽,其中1号和n号非常重要,为了加强运输能力,A市决定在1号到n号枢纽间修建一条地铁.地铁由很多段隧道组成,每段隧道连接两个交通枢纽.经过勘探,有m段隧道作为候选,两个交通枢纽之 ...

  4. vue+element 递归上传图片

    直接上代码. <template>   <div>     <el-upload       action="http://localhost:3000/pic ...

  5. SQL state [72000]; error code [1461]; ORA-01461: 仅能绑定要插入 LONG 列的 LONG 值 ; nested exception is java.sql.BatchUpdateException: ORA-01461: 仅能绑定要插入 LONG 列的 LONG 值

    本文转自  https://www.cnblogs.com/yingsong/p/5685790.html 原 因:某一个字段本为varchar2(1024),但是实际要插入的值超过varchar2允 ...

  6. MySQL之表、列别名及各种JOIN连接详解

    MySQL在SQL中,合理的别名可以让SQL更容易以及可读性更高.别名使用as来表示,可以分为表别名和列别名,别名应该是先定义后使用才对,所以首先要了解sql的执行顺序(1) from(2) on(3 ...

  7. DOM:通过文本框向下拉列表中添加内容

    <!DOCTYPE html><html> <head>        <meta charset="UTF-8">         ...

  8. 嵊州普及Day1T2

    题意:走迷宫.求走到a[n][n]需要多久. 考场上想的dfs,听老师说最多50分.代码懒得码了,知道是走迷宫就好. 正解:bfs,时间复杂度O(n). 见代码: #include<iostre ...

  9. checkbox checked属性值

    记住我1<input type='checkbox' /> 记住我2<input type='checkbox' /> <button onclick='hehe();' ...

  10. UVA - 211 The Domino Effect(多米诺效应)(dfs回溯)

    题意:根据多米诺骨牌的编号的7*8矩阵,每个点可以和相邻的点组成的骨牌对应一个编号,问能形成多少种由编号组成的图. 分析:dfs,组成的图必须有1~28所有编号. #pragma comment(li ...