android之View工作原理

今天对View的绘制原理进行总结,有助于提高自定义控件的实现能力,对优化布局也有一定帮助。

1、我们知道DecorView是顶层的View,本质是一个FrameLayout,事件也是通过DecorView传递到我们的View。View的工作流程分为measure,layout以及draw三部分。ViewRoot实现类是ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三部分流程也是通过ViewRoot完成的,并且绘制的起点是从ViewRoot的performTraversals方法,此方法之后才会调用DecorView的performMeasure,performLayout,performDraw三个方法最终将View绘制在屏幕上的。首先要明白android中各个View/ViewGroup的结构:

整体流程是这样的:ViewRootImpl会调用performTraversals方法,之后进入DecorView(实际是ViewGroup)调用performMeasure,通过measure测量自身大小,调用onMeasure进入孩子布局的测量,这样就完成了当前布局的测量,进入孩子布局的测量即是重复之前的测量步骤;measure之后,DecorView会调用performLayout来放置View相对父布局的位置,首先会对自身layout,之后会调用onLayout进入孩子布局的layout,如此就确定了当前布局相对父布局的位置,进入孩子布局的layout即是重复之前的步骤;layout之后,进入performDraw方法,调用draw对当前布局绘制,之后调用dispatchDraw来对孩子布局进行绘制;流程图如下所示:

下面对没个流程详细描述:

1、measure

测量过程是三个流程中最复杂的一个,测量之前先要拿到当前View/ViewGroup的MeasureSpec,这个MeasureSpec是测量规则的意思,大致就这样理解,当前View/ViewGroup的MeasureSpec是有当前View的LayoutParams和ParentView的MeasureSpec共同确定的,也就是说我要measure当前View必须要先拿到MeasureSpec值才可以。那么MeasureSpec究竟是啥玩意呢,其实它是有一个32位的int值确定,高2位代表SpecMode,包括EXACTLY(对应match_parent及具体数值),AT_MOST(对应wrap_content),UNSPECTIFIED三种类型;

UNSPECTIFIED:父容器对子容器没有限制,在子容器想多大就多大;

EXACTLY:父容器已经为子容器设置了尺寸,子容器应该服从这个边界,无论子容器想要多大的空间

AT_MOST:子容器可以是声明大小内的任意大小

  • 如果父View的MeasureSpec 是EXACTLY,说明父View的大小是确切的,(确切的意思很好理解,如果一个View的MeasureSpec 是EXACTLY,那么它的size 是多大,最后展示到屏幕就一定是那么大)。

1、如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是确切,子View的大小又MATCH_PARENT(充满整个父View),那么子View的大小肯定是确切的,而且大小值就是父View的size。所以子View的size=父View的size,mode=EXACTLY

2、如果子View 的layout_xxxx是WRAP_CONTENT,也就是子View的大小是根据自己的content 来决定的,但是子View的毕竟是子View,大小不能超过父View的大小,但是子View的是WRAP_CONTENT,我们还不知道具体子View的大小是多少,要等到child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 调用的时候才去真正测量子View 自己content的大小(比如TextView wrap_content 的时候你要测量TextView content 的大小,也就是字符占用的大小,这个测量就是在child.measure(childWidthMeasureSpec, childHeightMeasureSpec)的时候,才能测出字符的大小,MeasureSpec 的意思就是假设你字符100px,但是MeasureSpec 要求最大的只能50px,这时候就要截掉了)。通过上述描述,子View MeasureSpec mode的应该是AT_MOST,而size 暂定父View的 size,表示的意思就是子View的大小没有不确切的值,子View的大小最大为父View的大小,不能超过父View的大小(这就是AT_MOST 的意思),然后这个MeasureSpec 做为子View measure方法 的参数,做为子View的大小的约束或者说是要求,有了这个MeasureSpec子View再实现自己的测量。

3、如果如果子View 的layout_xxxx是确定的值(200dp),那么就更简单了,不管你父View的mode和size是什么,我都写死了就是200dp,那么控件最后展示就是就是200dp,不管我的父View有多大,也不管我自己的content 有多大,反正我就是这么大,所以这种情况MeasureSpec 的mode = EXACTLY 大小size=你在layout_xxxx 填的那个值。

  • 如果父View的MeasureSpec 是AT_MOST,说明父View的大小是不确定,最大的大小是MeasureSpec 的size值,不能超过这个值。

1、如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是不确定(只知道最大只能多大),子View的大小MATCH_PARENT(充满整个父View),那么子View你即使充满父容器,你的大小也是不确定的,父View自己都确定不了自己的大小,你MATCH_PARENT你的大小肯定也不能确定的,所以子View的mode=AT_MOST,size=父View的size,也就是你在布局虽然写的是MATCH_PARENT,但是由于你的父容器自己的大小不确定,导致子View的大小也不确定,只知道最大就是父View的大小。

2、如果子View 的layout_xxxx是WRAP_CONTENT,父View的大小是不确定(只知道最大只能多大),子View又是WRAP_CONTENT,那么在子View的Content没算出大小之前,子View的大小最大就是父View的大小,所以子View MeasureSpec mode的就是AT_MOST,而size 暂定父View的 size。

3、如果如果子View 的layout_xxxx是确定的值(200dp),同上,写多少就是多少,改变不了的。

  • 如果父View的MeasureSpec 是UNSPECIFIED(未指定),表示没有任何束缚和约束,不像AT_MOST表示最大只能多大,不也像EXACTLY表示父View确定的大小,子View可以得到任意想要的大小,不受约束

1、如果子View 的layout_xxxx是MATCH_PARENT,因为父View的MeasureSpec是UNSPECIFIED,父View自己的大小并没有任何约束和要求,
那么对于子View来说无论是WRAP_CONTENT还是MATCH_PARENT,子View也是没有任何束缚的,想多大就多大,没有不能超过多少的要求,一旦没有任何要求和约束,size的值就没有任何意义了,所以一般都直接设置成0

2、同上...

3、如果如果子View 的layout_xxxx是确定的值(200dp),同上,写多少就是多少,改变不了的(记住,只有设置的确切的值,那么无论怎么测量,大小都是不变的,都是你写的那个值)

测量流程图如下:

measure的流程可以分两类,即对View的测量和对ViewGroup的测量;在View的测量过程中,最终调用的测量宽高的方法是setMeasureDimention方法;这里有一个陷阱大家一定要注意,对于继承View的自定义控件,如果宽/高采用wrap_content类型,这种方式是失效的相当于使用了match_parent,这里就需要重写onMeasure方法设置wrap_content时的自身大小。

activity启动时获取View的宽高方法:

1)通过View的onWindowFocusChanged方法

2)view.post(Runnable)

3)ViewTreeObserver

 

2、layout

layout的流程相对于measure较简单,对View的layout最终是通过setFrame方法对四个点的位置确定,对ViewGroup的layout是要看具体的ViewGroup,如果是LinearLayout,则会有垂直或水平两个方向layout,其中对孩子layout方法是setChildFrame;

注意点:getMeasuredWidth与getWidth的区别,前者是在measure过程中得到的测量值并非最后显示的值,一般来说这两个值是相同的,除非在measure与layout这两个过程中对宽高值进行了修改,例如在onLayout方法中,View的位置进行了修改等。

 

3、draw

绘制过程主要考虑onDraw方法,这个方法是对View的绘制,里面不可以做一些耗时操作,否则影响性能。draw绘制流程包括以下几部分:

1)绘制背景

2)绘制自己 onDraw

3)绘制孩子

4)绘制装饰 例如滚动条

注意点:对孩子视图的绘制是通过dispathDraw进行分发处理的

 

4、自定义控件onMeasure中常用的方法

setMeasuredDimension() 设置当前控件长宽,单位pix

child.measure() 给孩子控件设置长宽,参数为长或宽的测量规则

setBounds() 给图片或者背景设置边界

MeasureSpec.getSize() 获取当前控件长宽,参数为长或宽的measuerSpec

MeasureSpec.makeMeasureSpec() 返回长或宽的测量规则,获取之后给child.measure() 使用

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值