Android学习之view的绘制

一、view绘制流程

view树 递归

measure -> layout -> draw

ViewRootImpl是windowManager和DecorView的纽带

View的绘制是从ViewRootImpl的performTranslate方法开始的,经过measure、layout、draw三个过程才绘制出来的。

measure用来测量view的宽高,layout是用来确定view在父容器中的位置,draw负责将view画在屏幕上

MeasureSpec :

测量规格 是由父容器的MeasureSpec和view的LayoutParams确定的。
本身是一个32位的int值,包含mode和在mode(模式)下的size,其中高2位代表SpecMode 低30位代表SpecSize

SpecMode有三个值:

MeasureSpec.EXCTLY 精确值 对应match_parent和具体的数值
MeasureSpec.AT_MOST 可用大小 取决于view的具体实现 但最大不能超过父容器的大小 对应wrap_parent
MeasureSpec.UNSPECIFIED 不确定值,一般用于系统内部

二、measure

分为两部分view的测量和viewgroup的测量

1、viewgroup测量:

由于viewgroup是一个抽象类,所以其测量过程的OnMeasure方法需要各个子类去实现,比如LinearLayout、FrameLayout等。这里就以FrameLayout为例来介绍viewgroup绘制的源码分析:

<1> 在FrameLayout的onMeasure方法中会循环所有的子view调用measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0)方法
   
<2> 在measureChildWithMargins方法中,通过调用child.getLayoutParams()获取到子view的layoutParams。
       然后使用传递过来的widthMeasureSpec(即父widthMeasureSpec)以及子view的layoutParams的数值作为参数调用getChildMeasureSpec方法得到子view的MeasureSpec
   
<3> 之后调用child.measure(childWidthMeasureSpec, childHeightMeasureSpec)方法来测量子view的大小

<4> child.measure方法是final类型,所以不能被复写 measure方法会调用view的onMeasure方法

<5> 在使用自定义view的时候就可以复写onMeasure方法,使用传递过来的WidthMeasureSpec来确定view的测量大小,最后调用setMeasureDimension设置view的大小
   
<6> view默认会在onMeasure方法中提供一个view的大小,使用getDefaultSize方法getDefaultSize方法有两个参数,其中size通过getSuggestedMinimumWidth方法获取,指的是通过设置android:minWidth的大小或者大小
	   
	   在getDefaultSize方法的实现中可以看到:
	             view的MeasureMode如果等于AT_MOST或EXACTLY的时候view的大小就是specSize
	             view的MeasureMode如果等于UNSPECIFIED的话view的大小就等于size(即view建议的最小值)
   
<7> 在所有的子view测量完成后FrameLayout经过一些列的计算调用setMeasuredDimension完成自身的测量

   这样整个view树就通过递归的方式测量完成了
2、view的测量:

view的测量就相对简单了,只需要在onMeasure方法来完成view的测量

注意:view的measure过程和activity的生命周期不是同步的,所以无法保证在activity的onCreate、onStart、onResume方法中通过调用view.getMeasureWidth()和view.getMeasureHeight()获取到view的宽高。可以在Activity中onWindowFocusChanged、View.post(runnable)等方法中获取

三、layout

layout是viewgroup来确定子元素的位置的

<1> 在performMeasure测量结束后,ViewRootImpl会调用performLayout方法调用mView.layout方法 这里的mView(mView是个ViewGroup)

<2> 进入ViewGroup的layout方法中会调用super.layout(l, t, r, b)方法(即view的layout方法)

<3> 在view的layout方法中会调用setFrame(l, t, r, b)给mLeft 、mTop、mRight、mBottom赋值,然后基本就能确定View自己在父视图的位置了,这几个值构成的矩形区域就是该View显示的位置,这里的具体位置都是相对与父视图的位置。
    
	然后会调用onLayout方法,对于view来说是一个空方法不需要去实现,对于ViewGroup来说是abstract抽象方法,需要去重写确定子view的位置。
    	
	onLayout的实现与具体的布局有关,所以还是以FrameLayout为例来说
	
<4> 在FrameLayout的onLayout方法中会去遍历所有的子view,然后调用 child.layout(l, t, r, b) ,给子view 通过setFrame(l, t, r, b) 确定位置

getMeasureWidth和getWidth的区别:

getMeasureHeight是获取一个View的测量宽高,是在onMeasure方法中赋值的,代表view原始的大小,也就是这个view在XML文件中配置或者是代码中设置的大小。

getHeight是获取View的真实宽高,是在layout完后在setFrame()中赋值的,view最终显示的大小,这个大小有可能等于原始的大小也有可能不等于原始大小。

一般情况下这两个值时相同的,除非是在重写layout阶段改变left,right的值

四、draw

draw的作用是将view绘制到屏幕上。

performTraversals 方法的在调用完performLayout调用performDraw方法,调用mView.draw(canvas)方法

draw(Canvas canvas)方法中一般包括:

 <1> drawBackground(canvas);绘制背景

 <2> onDraw(canvas) 绘制自己

 <3> dispatchDraw(canvas) 绘制children

 <4> onDrawForeground(canvas);绘制装饰

 <5> drawDefaultFocusHighlight(canvas)

dispatchDraw方法:

view的绘制是通过dispatchDraw来传递给子view的,在该方法中会遍历所有的子view调用draw方法。由于单一View无子View,故View 中默认为空实现。

ViewGroup中系统已经复写好对其子视图进行绘制我们不需要复写

invalidate()和requestLayout()方法的区别:

 invalidate:调用时请求系统重新绘制会触发draw过程,当视图大小没有发生变化时就不会调用layout过程
 
 requestLayout:当布局发生变化时需要去调用该方法,会去触发measure和layout过程,但不会去触发draw过程

进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;

若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;

若两者均发生改变,则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值