配合Activity 从启动到布局绘制的简单分析 阅读
View的绘制.png
Android页面来自网络.png
Activity 设置页面布局的过程
setContentView
详细的说明看:Activity 从启动到布局绘制的简单分析
View 的绘制流程可以分成三步:测量、布局、绘制
分别对应了:onMeasure()
onLayout()
onDraw
当然这个过程中也会调用许多其他的方法,都是作为辅助,大的流程就这三步。其中这三步内部的执行都是呈现树状结构,从根 View 开始循环递进,直到所有子 View 全部执行完毕。
onMeasure(int widthMeasureSpec,int heightMeasureSpec)
这个方法对于单控件来说,只是测量他自己,但是对于 ViewGroup 来说还要正确的给它的子控件传入期望的测量数值。然后根据所有子控件的大小和 onMeasure
中的参数来设置自己本身的大小。
关于 MeasureSpec 就不多解释了,这里只说一下内部的三种模式
onMeasure()
的时候,明明父布局给的类型是 AT_MOST 你还要超过,那也没事,只是布局的样子可能就会有问题了)。对应 wrap_content 模式AdapterView
通过 measure 方法传入模式。关于 ViewGroup 的自定义,onMeasure()
方法内部需要实现什么?
首先我们需要给这个控件设置正确的期望大小 setMeasuredDimension(width,height)
要想正确的获取 width 和 height 还需要根据 onMeasure(int widthMeasureSpec,int heightMeasureSpec)
中的参数来确定。如果给的参数类型是 EXACTLY
的话,说明它的父控件给他的大小是确定的,这个时候的大小就填写参数中的数值大小就好(需要 MeasureSpec.getSize(int)
)。如果参数类型是 AT_MOST
的时候,这个表示父布局给了一个值,当前的 View 的大小不能超过这个值。那么我们就需要自己计算出这个 View 想要的大小,然后和父布局给的最大的值做比较,选择一个值给 setMeasuredDimension()
那么如何获取此 ViewGroup 的正确高度呢?做法就是要获取到每个子View 的高度和一些 padding Margin 加起来就是这个 ViewGroup 应该的高度了。
要想获取子 View 的高度就需要调用 child.measure()
然后 child.getMeasureHeight
就获取 Child 的高度了。也就是说需要我们给子 View 测量一下,测量的时候我们需要传入值。当然这个值也不是随便传入的,如果你随便传入的话,那么 child 的大小就乱了,和你在布局文件中设定的大小就不一样了。
那么如果正确的给 child 传入值呢?LinearLayout 是这样做的,当然我们可以根据我们想要的布局来进行自定义。
// 核心代码
// count 是 child 的个数
for(int i=0;i<count;i++){
// 获取 child 的 LayoutParmas 这个对象有我们在 xml 中给 view 设置的大小信息
final LayoutParams lp = (LayoutParams)child.getLayoutParams();
// 然后根据 LayoutParams 中的参数和 ViewGroup本身的 widthMeasureSpec 来进行对比,选择一个合适的数值给
// child LinearLayout 具体的做法是通过
// ViewGroup 中的 getChildMeasureSpec 方法来获取一个合适值
}
// ViewGroup 中的 getChildMeasureSpec(int spec,int padding,int childDimension) 方法的实现代码
// spec 是 onMeasure 中的 spec padding 是子View 的margin + 父控件的 padding childDimension 是子 View 在布局文件中给定的大小
public static int getChildMeasureSpec(int spec,int padding,int childDimension){
int specMode = MeasureSpec.getMode(spec);
int SpecSize = MeasureSpec.getSize(spec);
// 得出 ViewGroup 实际可以使用的大小
int size = Math.max(0,specSize-padding);
int resultSize = 0;
int resultMode = 0;
// 然后就是根据 specMode 和 childDimension 来得出合适的大小。
}
onLayout
对于子控件来说没有什么意义,对于 ViewGroup 来说,onLayout 方法内部要对子控件进行布局,调用子控件的 layout 函数。
onLayout
重写的时候,只需要获取子 View 的实例,然后调用子 View 的 layout 方法来实现布局就可以了,具体 layout 中传入的参数,是重写 onLayout 的重点。需要通过 getMeasureHeight 等获取子 View 的理想高度,然后再根据具体情况传入数值。
onDraw()
函数就是来绘制了,一般 ViewGroup 不会实现内部的方法,子控件才重写 onDraw()
方法。也是内部一层层分发绘制。呈现树状结构
// 最根部调用下面的方法
// public void draw(Canvas canvas);
// 然后此方法内部调用 onDraw()(针对于 子View的)dispatchDraw(Canvas canvas) (主要是针对于 ViewGroup 的)
// 然后 dispatchDraw() 内部会调用 drawChild(Canvas canvas,View child,long drawingTime) 然后此方法内部会执行 draw 方法,就这样一层一层下去了。如果最终到了子View就会终止,因为子View dispatchDraw 方法体是空的。
//
另外可以认为这三个方法都对应着 measure()
、layout()
draw()
方法。可以认为这三个方法内部调用了上面的方法。
上面 onMeaure
onLayout
onDraw()
都介绍完了,那么最根处的 View 是怎么调用的呢?
布局树.png
可以看到上面这张图,追溯到根View DecorView
,其实最开始就是 ViewRootImp 来调用 DecorView 的 measure()
,并且传入了具体的值,这个值一般就是页面的大小。然后在 DecorView 的 measure 方法内部会调用 onMeasure
,onMeasure
的内部又会调用它的子 View 的 measure
然后就这样一层层的下去了,直到所有子 View 执行完毕,DecorView 的 measure
就执行完毕了,到此整个页面的测量工作完成。
onLayout 也是最先 ViewRootImp 来调用 DecorView 的 layout()
开始。onDraw 也是最先 ViewRootImp 来调用 DecorView 的 draw()
开始的。然后 draw()
的内部的执行就和上面介绍 onDraw()
中一样了
到此整个页面的测量、布局、绘制就全部分析完毕了。
可以查看:Activity 从启动到布局绘制的简单分析
本文分享自 Android开发者家园 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!