Framework-view学习

View绘制的流程

ViewRootImpl是一个Activity UI显示的一个控制类,在View和WindowManager之间起一个中间管理的角色。该类的performTraversals方法里面会相继调用performMeasure、performLayout、performDraw三个方法,继而调用根布局DecorView的mersure、layout、draw方法,DecorView又会调用其孩子的mersure、layout、draw方法,进而完成一个Activity的绘制。

Measure

ViewRootImpl.performTraversals方法

其中,传给performMeasure的两个参数是childWidthMeasureSpec和childHeightMeasureSpec,这是两个int类型的的变量。MeasureSpec里面包含了两个部分modesize,一个int变量是32位的,最高两位表示mode,其余30位用来存放size

GetRootMeasureSpec方法:

其中,rootDimension 是LayoutParams中的宽或者高,如果布局里面我们用的是:android:layout_width="match_parent"或android:layout_width="wrap_content ".

ViewRootImpl.performMeasure方法

由于performMeasure()方法调用了 View中measure()方法俩进行测量,由于DecorView继承自FrameLayout,是PhoneWindow的一个内部类,而FrameLayout没有measure方法,因此调用的是父类View的measure方法,因此从View的成员函数measure开始分析整个测量过程。

跟踪代码,进入View类的 onMeasure方法:

View.onMeasure方法

该方法的作用是根据View布局设置的宽高和父View传递的测量规格重新计算View的测量宽高。由此可知,布局的子View最终的大小是由布局大小和父容器的测量规格共同决定的。如果自定义View没有重写onMeasure使用系统默认方法的话,测量模式MeasureSpec.AT_MOST和MeasureSpec.EXACTLY下的测量大小是一样的。测量模式种类:

MeasureSpec.EXACTLY:确定模式,父容器希望子视图View的大小是固定,也就是specSize大小。

MeasureSpec.AT_MOST:最大模式,父容器希望子视图View的大小不超过父容器希望的大小,也就是不超过specSize大小。

MeasureSpec.UNSPECIFIED: 不确定模式,子视图View请求多大就是多大,父容器不限制其大小范围,也就是size大小。

从上面代码可以看出,当测量模式是MeasureSpec.UNSPECIFIED时,View的测量值为size,当测量模式为MeasureSpec.AT_MOST或者case MeasureSpec.EXACTLY时,View的测量值为specSize。specSize是由父容器决定,那么size是怎么计算出来的呢?getDefaultSize方法的第一个参数是调用getSuggestedMinimumWidth方法获得。

size大小是获取View属性当中的最小值,也就是 android:minWidth和 android:minHeight的值,前提是View没有设置背景属性。否则就在最小值和背景的最小值中间取最大值。sizeSpec大小是由父容器决定的,父容器DecorView的测量模式是MeasureSpec.EXACTLY,测量大小sizeSpec是整个屏幕的大小。

DecorView是继承自FrameLayout的, FrameLayout类中的onMeasure方法的实现:

FrameLayout父类是ViewGroup,measureChildWithMargins方法在ViewGroup下:

该方法中调用getChildMeasureSpec方法来获得ViewGroup下的子视图View的测量规格。然后将测量规格最为参数传递给View的measure方法,最终完成所有子视图View的测量。进入getChildMeasureSpec获得子视图View的测量规格方法:

根布局DecorView的测量规格中的测量模式是MeasureSpec.EXACTLY,测量大小是整个窗口大小。因此上面代码分支走MeasureSpec.EXACTLY。子视图View的测量规格由其宽和高参数决定。

当DecorView根布局的子视图View宽高为一个确定值childDimension时,该View的测量模式为MeasureSpec.EXACTLY,测量大小就是childDimension。

当子视图View宽高为MATCH_PARENT时,该View的测量模式为MeasureSpec.EXACTLY,测量大小是父容器DecorView规定的大小,为整个屏幕大小MATCH_PARENT。

当子视图View宽高为WRAP_CONTENT时,该View的测量模式为MeasureSpec.AT_MOST,测量大小是父容器DecorView规定的大小,为整个屏幕大小MATCH_PARENT。

Layout:

ViewRootImpl.performLayout方法

从host.layout()(host为View类对象)方法可以看出DecorView的四个位置左=0,顶=0,右=屏幕宽,底=屏幕宽,说明DecorView布局的位置是从屏幕最左最顶端开始布局,到屏幕最低最右结束。因此DecorView根布局是充满整个屏幕的。

View.Layout方法

View.SetFrame方法

保存当前View的最新位置,到此当前View的布局基本结束。只有在View 布局完之后调用getWidth才能真正获取到大于0的值。

View.onLayout方法

是空方法,那么该方法的实现应该在子类中。DecorView是继承自FrameLayout的,进入FarmeLayout类中看看 onLayout方法的实现:

FrameLayout.onLayout方法

ViewGroup.onLayout方法

ViewGroup类中的onLayout方法是一个抽象方法,FrameLayout继承自ViewGroup,自然FrameLayout就必须实现ViewGroup中的抽象方法onLayout,而FrameLyayout中的onLayout方法的作用是用来设置它的子视图View的布局位置。

总结:

1.视图View的布局逻辑是由父View,也就是ViewGroup容器布局来实现的。因此,我们如果自定义View一般都无需重写onMeasure方法,但是如果自定义一个ViewGroup容器的话,就必须实现onLayout方法,因为该方法在ViewGroup是抽象的,所有ViewGroup的所有子类必须实现onLayout方法。

2.当视图View在布局中使用 android:visibility=”gone” 属性时,是不占据屏幕空间的,因为在布局时ViewGroup会遍历每个子视图View,判断当前子视图View是否设置了 Visibility==GONE,如果设置了,当前子视图View就会添加到父容器上,因此也就不占据屏幕空间。

3.必须在View布局完之后调用getHeight()和getWidth()方法获取到的View的宽高才大于0。

Draw:

View视图绘制流程中的最后一步绘制draw是由ViewRootImpl中的performDraw成员方法开始的,跟踪代码,最后会在ViewRootImpl类中的drawSoftware方法绘制View:

ViewRootImpl. drawSoftware方法

从mSurface对象中获得canvas画布,然后将变量canvas变量作为参数传递给draw方法。由此可知,视图View最终是绘制到Surface中去的,

跟踪代码,进入View的draw方法分析源码:

View. draw方法

包括:

·绘制当前视图的背景。

·保存当前画布的堆栈状态,并且在在当前画布上创建额外的图层,以便接下来可以用来绘制当前视图在滑动时的边框渐变效果。

·绘制当前视图的内容。

·绘制当前视图的子视图的内容。

·绘制当前视图在滑动时的边框渐变效果。

·绘制当前视图的滚动条。

当根据条件绘制当前视图View的内容,调用了View的成员方法onDraw来绘制视图View的内容,onDraw成员方法的实现:

View. onDraw方法

该方法体里面是一个空实现,也就是视图View将绘制的逻辑留给继承它的子类去实现,这也就是为什么我们在自定义View的时候必须去实现其父类的onDraw方法来完成自己对内容的绘制。

当根据条件绘制当前视图View的子视图时,调用了View的成员方法dispatchDraw来绘制,dispatchDraw成员方法的实现:

View. dispatchDraw方法

ViewGroup. dispatchDraw方法

其中:

·判断当前ViewGroup布局是否设置了布局动画,如果设置了,则条件满足,给每个子视图View设置布局动画,当在布局中使用ViewGroup容器布局,且设置了android:animateLayoutChanges=”true”属性,那么该条件满足,每个子视图View出现的时候都会有一个默认的动画效果。

·对当前视图的画布canvas进行边距裁剪,把不需要绘制内容的边距裁剪掉。

·遍历绘制当前容器布局ViewGroup的子视图,其中调用了成员方法drawChild来完成子视图的绘制。

ViewGroup. drawChild方法

此处调用View类中的draw方法来绘制视图,因此形成了一个嵌套调用,所有的子视图View绘制结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值