View的绘制流程

入口

View绘制流程的入口是ViewRootImpl的performTraversals;
View绘制不是发生在Activity创建时(OnCreate()时),也不是发送在OnResume时;
发生在OnResume()方法结束之后,在Activity的ActivityThread的handleResumeActivity()方法里面,执行完Activity的OnResume()方法之后,会把DecorView添加到Window里面,并且创建一个ViewRootImpl对象(ViewRootImpl对象通过setView持有DecorView的引用),然后就会调用ViewRootImpl(ViewRootImpl对添所有添加到DecorView里面的view进行事件管理)sheduleTraversals()方法(涉及到屏幕刷新),最后就会调用ViewRootImpl的performTraversals(),通知子元素进行重绘(通过调用持有DecorView对象的measure,layout,draw方法),开启measure(),layout(),draw()三大流程。

View的绘制流程

ViewRootImpl又会调用peformMeasure(),performLayout(),performDraw();
以performMeasure()方法为例来说,它又会调用measure()方法,然后调用OnMeasure()方法,OnMeasure()方法又会遍历所有的子元素让其执行Measure过程,接着子元素重复这个过程,直到整个View树被遍历完。
performLayout()和performDraw()也是类似。
在这里插入图片描述

Measure()过程

MeasureSpec

代表一个32位的int值,高两位代表SpecMode(测量模式),低三十位代表SpecSize,指某种测量模式下的规格大小;
MeasureSpec很大程度上决定了一个view的尺寸(父容器会影响MeasureSpec的创建,而间接影响View的尺寸);

MeasureMode

EXACTLY:精确模式,指定了大小或者match_parent就是这种模式,View的最终大小就是SpecSize;
AT_MOST:最大模式,对应wrap_content,View的最终大小不能大于SpecSize;
UNSPECIFIED:父容器不对子view有任何限制,有多大给多大,像ScrollView这个控件就使用了这种模式,子View要多大给多大。

Measure和LayoutParams的关系

MeasureSpe由LayoutParams和父容器大小共同决定,从而进一步确定View的宽和高;
对于顶层的DecorView和普通的View两者的MeasureSpec有所不同
对于顶层的DecorView:MeasureSpec由窗口尺寸和自身得LayoutParams共同决定;
对于普通的View:MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定。

Measure过程

主要来确定View的测量宽高;
绘制流程都是从DecorView调用下来的,所以无论是View还是ViewGroup,都会先确定它的MeasureSpec;MeasureSpec和LayoutParams的关系如上。
然后调用当前视图的performMeasure(),他又会调用当前视图的Measure()方法;
对于一个View:它会measure方法会对自身进行测量,最后调用onMeasure()方法设置自身测量宽高,这样它的measure方法就完成了测量过程;
对于一个ViewGroup:因为它是一个抽象类,所以它的OnMeasure()方法需要各个子类具体实现,他们的实现逻辑需要去遍历调用所有子view的measure()方法,各个子View再递归去执行这个过程。

注意:直接继承View的自定义控件,需要重写OnMeasure()方法并设置wrap_content时的自身大小,否则他和match_parent的效果一样。

原因:在View默认的OnMeasure()方法的getDeafultSize方法中,使用wrap_content对应的specMode是AT_MOST,无论父容器的SpecMode是EXACTLY还是AT_MOST,它的specSize都是parentSize,也就是父容器当前剩余的空间大小。

在这里插入图片描述
解决办法:直接继承自View的控件,重写OnMeasure()方法,并且在WidthSpecMode和HeightSpecMode为MeasureSpecMode.AT_MOST时通过setMeasuredDimension方法为它指定默认宽高。

如何获取View的宽高

我们经常会需要获取View的宽度或者高度,但是我们会发现不管是在onCreate还是onStart,onResume里面获取到的值都是0.

这是因为View的measure过程和Activity的生命周期方法不是同步进行的,不能保证在Activity的onCreate,onStart,onResume方法执行时View已经测量完毕,所以不能获得正确宽高值。

Activity/View的onWindowFocusChanged方法体内;
View.post(runnable)
View TreeObserver
View.Measure
自定义view时获取宽高
https://blog.csdn.net/u014537423/article/details/51004218

Layout()过程

performMeasure()测量完之后,通过performLayout进行布局,作用时确定子元素在父容器里面放置的位置,进而确定子元素的最终宽高;
View的layout方法会先确定子元素四个顶点的位置,这样View在父容器里面的位置也就确定了,接着会调用OnLayout()方法让父容器确定子元素的位置,它的实现也和具体的布局有关,因此view和ViewGroup都没有实现OnLayout()的方法;在这个方法中ViewGroup应该遍历并调用所有子元素的layout方法。
注意:VIew可能需要多次measure过程才能确定最终宽高,测量宽高和最终宽高并不一定相等。
getMeasuredWidth/getMeasureHeight和getWidth/getHeight的区别
在这里插入图片描述

Draw()过程

作用是将View绘制到屏幕上。
绘制背景(background.draw(canvas))
绘制自己(OnDraw,同样是空实现,需要子View自己重写);
绘制children(dispatchDraw,遍历调用所有子view的draw方法);
绘制装饰(onDrawScrollBars),例如滑动条

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值