View的工作原理

目录

  • 初识ViewRoot和DecorView
  • 理解MeasureSpec
  • View的工作流程
  • 自定义View

初识ViewRoot和DecorView

View的绘制流程从ViewRoot的perfromTraversals方法开始,他经过measure,layout和draw三个过程才能最终将一个View绘制出来,其中measure用来测量View的宽和高,layout用来确定view在父容器中的放置位置,draw负责将View绘制在屏幕上,针对perfromTraversals的大致流程,可以看图:

perfromTraversals的工作流程图

图中的perfromTraversals会依次调用perfromMeasure,perfromLayout,perfromDraw,他们分别完成顶级View的measure,layout和draw这三大流程,其中在perfromMeasure中会调用measure方法,在measure方法中又调用onMeasure,这个时候measure流程就从父容器传递到子元素了,这样就完成了一次measure过程,接着子元素会重复父容器的measure过程,如此反复的完成了整个View树的遍历,同理,其他两个也是如此,唯一有点区别的是perfromDraw的传递过程是在draw反复中通过dispatchDraw来实现的,不过这并没有什么本质的区别。

measure过程决定了View的宽高,Measure完成之后可以通过getMeasureWidth和getMeasureHeight来获取View测量后的高宽,在所有的情况下塔几乎都是等于最终的宽高,但是特殊情况除外。

layout过程决定了view的四个顶点的坐标和实际View的宽高,完成之后,通过getTop,getLeft,getRight,getBottom来拿到View的四个顶点的位置,并可以通过getWight和getHeight方法来拿到View的最终宽高。

Draw决定了View的显示,只有draw方法完成了之后,view才会显示在屏幕上。

理解MeasureSpec

MeasureSpec代表一个32位int值,高两位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某个测量模式下的规格大小。

SpecMode有三类,每一类都有特殊的含义

  • UNSPECIFIED

父容器不对View有任何的限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。

  • EXACTLY

父容器已经检测出View所需要的精度大小,这个时候View的最终大小就是SpecSize所指定的值,它对应于LayoutParams中的match_parent,和具体的数值这两种模式。

  • AT_MOST

父容器指定了一个可用大小,即SpecSize,view的大小不能大于这个值,具体是什么值要看不同view的具体实现,它对应于LayoutParams中wrap_content。

MeasureSpec 和 LayoutParams 的对应关系

MeasureSpec不是唯一由layoutparams决定的,layoutparams需要和父容器一起决定view的MeasureSpec从而进一步决定view的宽高,对于顶级view(DecorView)和普通的view来说,MeasureSpec的转换过程有些不同,对于decorview,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams来共同决定,MeasureSpec一旦确定后,onMeasure就可以确定View的测量宽高。

  • LayouParams.MATCH_PARENT:精确模式,大小就是窗口的大小
  • LayouParams.WRAP_CONTENT:最大模式,大小不定,但是不能超出屏幕的大小
  • 固定大小(比如100dp):精确模式,大小为LayoutParams中指定的大小

对于普通View,其MeasureSpec 由父容器的MeasureSpec和自身的LayoutParams来共同决定,那么针对不同的父容器和Viev本身不同的LayoutParams,View就可以有多种MeasureSpec。

当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpee都是精确模式,那么View也是精准模式并且其大小是父容器的剩余空间;

如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。

当View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化,并且大小不能超过父容器的剩余空间。

View的工作流程

measure过程

View的measure过程

  • measure
/** 开始测量一个View有多大,parent会在参数中提供约束信息,实际的测量工作是在onMeasure()中进行的,该方法会调用onMeasure()方法,所以只有onMeasure能被也必须要被override */
public final void measure(int widthMeasureSpec, int heightMeasureSpec);

父布局会在自己的onMeasure方法中,调用child.measure ,这就把measure过程转移到了子View中。

  • onMeasure
/** 具体测量过程,测量view和它的内容,来决定测量的宽高(mMeasuredWidth  mMeasuredHeight )。该方法中必须要调用setMeasuredDimension(int, int)来保存该view测量的宽高。 */
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);

子View会在该方法中,根据父布局给出的限制信息,和自己的content大小,来合理的测量自己的尺寸。

  • setMeasuredDimension
/** 保存测量结果 */
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight);

当View测量结束后,把测量结果保存起来,具体保存在mMeasuredWidth和mMeasuredHeight中。

ViewGroup的measure过程

  • measureChildren
/** 让所有子view测量自己的尺寸,需要考虑当前ViewGroup的MeasureSpec和Padding。跳过状态为gone的子view */
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec);-->getChildMeasureSpec()-->child.measure();

测量所有的子View尺寸,把measure过程交到子View内部。

  • measureChild
/** 测量单个View,需要考虑当前ViewGroup的MeasureSpec和Padding。 */
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec);-->getChildMeasureSpec()-->child.measure();

对每一个具体的子View进行测量。

  • measureChildWithMargins
/** 测量单个View,需要考虑当前ViewGroup的MeasureSpec和Padding、margins。 */
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed);-->getChildMeasureSpec()-->child.measure();

对每一个具体的子View进行测量。但是需要考虑到margin等信息。

  • getChildMeasureSpec
/** measureChildren过程中最困难的一部分,为child计算MeasureSpec。该方法为每个child的每个维度(宽、高)计算正确的MeasureSpec。目标就是把当前viewgroup的MeasureSpec和child的LayoutParams结合起来,生成最合理的结果。
比如,当前ViewGroup知道自己的准确大小,因为MeasureSpec的mode为EXACTLY,而child希望能够match_parent,这时就会为child生成一个mode为EXACTLY,大小为ViewGroup大小的MeasureSpec。
 */
public static int getChildMeasureSpec(int spec, int padding, int childDimension);

根据当前自身的状况,以及特定子View的尺寸参数,为特定子View计算一个合理的限制信息。

layout过程

Layout的作用是ViewGroup用来确定子元素的作用的,当ViewGroup的位置被确认之后,他的layout就会去遍历所有子元素并且调用onLayout方法,在layout方法中onLayou又被调用。

layout方法确定了View本身的位置,而onLayout方法则会确定所有子元素的位置。

layout的方法的大致流程如下,首先会通过一个setFrame方法来设定View的四个顶点的位置,即初始化mLeft,mTop,mRight,mBottom这四个值,View的四个顶点一旦确定,那么View在父容器的位置也就确定了。接下来会调用onLayout方法,这个方法的用途是调用父容器确定子元素的位置。

draw过程

draw过程的作用是将View绘制到屏幕上面,View的绘制过程:

  1. 绘制背景background.draw(canvas)。
  2. 绘制自己(onDraw)。
  3. 绘制children(dispatchDraw)。
  4. 绘制装饰(onDrawScrollBars)。

View绘制过程的传递是通过dispatchDraw来实现的,dispatchDraw会遍历调用所有子元素的draw方法,如此draw事件就一层层地传递了下去。

自定义View

自定义View的分类

  1. 继承View重写onDraw方法
  2. 继承ViewGroup派生出来的Layout
  3. 继承特定的View
  4. 继承特定的ViewGroup

自定义View的须知

  1. 让View支持warp_content
  2. 如果有有必要,让你的View支持padding
  3. 尽量不要在View中使用Handler
  4. View中如果有线程或者动画,需要及时停止,参考View#onDetachedFromWindow
  5. View带有滑动嵌套时,需要处理好滑动冲突

参考资料:

《Android开发艺术探索》

Android开发艺术探索笔记

Android View框架的measure机制

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,884评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,212评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,351评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,412评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,438评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,127评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,714评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,636评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,173评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,264评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,402评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,073评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,763评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,253评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,382评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,749评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,403评论 2 358