现在的位置: 首页 > 综合 > 正文

Android自定义视图系列———– 计算view大小(measure)

2018年02月16日 ⁄ 综合 ⁄ 共 6125字 ⁄ 字号 评论关闭

原帖地址:http://www.eoeandroid.com/thread-200062-1-1.html点击打开链接

1.android绘制view的过程简单描述

           简单描述可以解释为:计算大小(measure),布局坐标计算(layout),绘制到屏幕(draw);
           
           第一步:当activity启动的时候,触发初始化view过程的是由Window对象的DecorView调用View(具体怎样从xml中读取是用LayoutInflater.from(context).inflate)对象的 public final void measure(int widthMeasureSpec, int heightMeasureSpec)方法开始的,这个方法是final类型的,也就是所有的子类都不能继承该方法,保证android初始化view的原理不变。具体参数类值,后面会介绍。

           第二步:View的measure方法 onMeasure(widthMeasureSpec, heightMeasureSpec),该方法进行实质性的view大小计算。注意:view的大小是有父view和自己的大小决定的,而不是单一决定的。这也就是为什么ViewGroup的子类会重写该方法,比如LinearLayout等。因为他们要计算自己和子view的大小。View基类有自己的实现,只是设置大小。其实根据源码来看,measure的过程本质上就是把match_parent和wrap_content转换为实际大小

            第三步:当measure结束时,回到DecorView,计算大小计算好了,那么就开始布局了,开始调用view的 public final void layout(int l, int t, int r, int b),该还是也是final类型的,目的和measure方法一样。layout方法内部会调用onlayout(int l, int t, int
r, int b )方法,ViewGroup将此方法abstract的了,所以我们继承ViewGroup的时候,需要重新该方法。该方法的本质是通过measure计算好的大小,计算出view在屏幕上的坐标点


           第四步:measure过了,layout过了,那么就要开始绘制到屏幕上了,所以开始调用view的  public void draw(Canvas canvas)方法,此时方法不是final了,原因是程序员可以自己画,内部会调用ondraw,我们经常需要重写的方法。

         以上就是view的大概工作过程,当然了,只是概述,细节多成马了!!!!!


      measure的过程分析

     1. public final void measure(int widthMeasureSpec, int heightMeasureSpec)的参数来源及代表的意思

          这个两个参数都是有父view传递过来的,也就是代表了父view的大小。其实说大小不太对,应该说是建议“规格”。他有两部分组成,第一部分:高16位表示MODE,定义在MeasureSpec类中,有三种类型,MeasureSpec.EXACTLY:表示确定大小, MeasureSpec.AT_MOST:表示最大大小, MeasureSpec.UNSPECIFIED:不确定。第二部分:低16位表示size,既父view的大小,这就是为什么,我们在重写onmeasure方法是需要:int
specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec);这样调用,因为MeasureSpec知道怎么读取。对于根view(并不是我们在xml中声明的第一个元素),而是系统的Window对象的decorVIew对象。Mode一般都为MeasureSpec.EXACTLY ,而size分别对应屏幕宽,高。也就是Window第一次掉用的view(这个view才是Xml中声明的第一个元素),一般都是这个值,而对于子view来说,这个值就是你在xml定义的属性  android:layout_width和android:layout_height这个,当然了上面说过view的大小是有父view和子view共同决定的,所以这样有点不对,但是来源于这两个值。意思明白了,我们看看measure里边做什么了


     2.measure方法内部操作过程

  • 调用 onMeasure(widthMeasureSpec, heightMeasureSpec),将父view的建议大小和规格传入,view类自己的onmeasure方法,只是根据xml中配置的大小初始化大小  ,这个就不分析了,重要的我们看看viewGroup中的onMeasure方法,ViewGroup类中没有处理该方法,一般在他的子类中处理,比如LinearLayout中,我们以Linearlayout作为分析类。
  • Linearlayout类的onMeasure方法分两种情况处理,1:重置排序,2:水平排序,这个大家都知道,我们分析重置排序,
  • 获取所有的子view数量,对每个子view开始处理,如果子view是GONE的,则直接跳过
  • 在LinearLaout.measureVertical方法中,首先:获取该子view的LayoutParams,在xml中定义的参数,将通过layout_weight定义的值累加到变量totalWeight中,所有的权重,然后判断如果view的height设置为零,但weight设置的大于0,则将height的值设置为LayoutParams.WRAP_CONTENT这个值,其他的不处理
  • 然后调用measureChildWithMargins方法,这个做的处理包括:计算子view的measureSpec,即MODE和SIZE,调用方法为:getChildMeasureSpec,调用两次,分别 计算宽和高,getChildMeasureSpec内部根据父view的Measure和子view的layout_width和layout_height属性计算子view的measure,查看图片:getChildMeasureSpec计算子view的measure,总结如下:1.如果在xml中指定了子view的具体大小,那么计算结果不管父的measure是什么,结果都是EXACITY+child_size,2.如果子view的height指定的值为FILL_PARENT,则返回的结果为:EXACITY+size,原因很简单:因为FILL_PARENT的意思是充满这个父view,所以返回的子view的measure就是view的大小3.如果子vide的大小为wrap_content,那么返回的结果都为At_most+size,原因是:最大不能超过父view的大小。
  • 子view的measure确定好以后,然后调用子view的measure方法,如果子view是View对象,则该view的大小测量结束,开始下一个子view的循环,如果子view是ViewGroup那么,又开始一个新的递归,处理逻辑和上面一样,值得所有的view对象测量结束。
  • LinearLayout会在每个他的直接子view测量结束后,将该子view的高度累加到变量mTotalLength,其其实应该叫mTotalHeight更合适,但是为了和wight同一,所以命名为这个(这个过程不处理:父view的大小指定为具体值和fill_parent,且子view的高度指定为0和子viewweight值大于的)。
  • 所有的子view测量结束后,才开始对layout_weight计算,这样我们可能想到,如果父view已经被占满了,那么有可能layout_weight大于0的view对象是不会显示的,而计算layout_weight的方法也很简单,就是用总高度减去上面分析完mTotalLength的值,就是剩下,然后去平分给view对象,注意计算权重时优先去android:android:weightSum(LinearLayout的xml属性)的值,如果不设置该值会计算和,所以该值既然设置了,就一定要子view的weight的总和相等,否则平分可能不能得到预期效果,分析一个例子吧:

     <LinearLayout android:layout_width="fill_parent"
       android:layout_height="200dp"
       >
       <TextView android:layout_width="fill_parent"
           android:layout_height="100dp"
           android:layout_weight="1"
           />
       <TextView android:layout_width="fill_parent"
           android:layout_height="0dp"
           android:layout_weight="1"
           />
   </LinearLayout> 
上面这个例子再计算第一个TextView的时候,根据android:layout_height="100dp" 值,确定高度为100dp,计算第二个TextView的时候,由于android:layout_height="0"为0,所以不计算其高度,等到计算weight的时候才计算,当计算weight的时候,千万别认为第一个TextView已经计算过了,就不计算了,还是计算的,计算过程如下:第一个分配了100dp,还剩100dp,所以两个textview各分50dp,所以第一个TextVIew的
150dp,第二个就为50dp


至此,view的measure就结束了,所有的view值都结束了,大家可能发现,这个过程只用了几个属性: android:layout_width,android:layout_height,android:layout_weight还有margen和padding,其他的多数属性都在ondraw时候使用,



二、对View的onMeasure方法理解(原地址:点击打开链接http://blog.csdn.net/czh0766/article/details/5846460)

  我们知道View在屏幕上显示出来要先经过measure和layout. 在调用onMeasure(int widthSpec, int heightSpec)方法时,要涉及到MeasureSpec的使用,MeasureSpec有3种模式分别是UNSPECIFIED, EXACTLY和AT_MOST, 那么这些模式和我们平时设置的layout参数fill_parent,
wrap_content有什么关系呢。经过代码测试就知道,当我们设置width或height为fill_parent时,容器在布局时调用子view的measure方法传入的模式是EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。而当设置为wrap_content时,容器传进去的是AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。当子view的大小设置为精确值时,容器传入的是EXACTLY, 而MeasureSpec的UNSPECIFIED模式目前还没有发现在什么情况下使用。

   View的onMeasure方法默认行为是当模式为UNSPECIFIED时,设置尺寸为mMinWidth(通常为0)或者背景drawable的最小尺寸,当模式为EXACTLY或者AT_MOST时,尺寸设置为传入的MeasureSpec的大小。

   有个观念需要纠正的是,fill_parent应该是子view会占据剩下容器的空间,而不会覆盖前面已布局好的其他view空间,当然后面布局子view就没有空间给分配了,所以fill_parent属性对布局顺序很重要。以前所想的是把所有容器的空间都占满了,难怪google在2.2版本里把fill_parent的名字改为match_parent.

三、对window和view显示的理解(原地址:点击打开链接

很多时候都搞不清window和view之间的关系,今天重新看了一下api和源代码,终于又有多点认识了。其实window没有继承其他类,它是object的直接子类,这和iPhone中window和view的关系不同。window是抽象类,由系统内部实现,我们通常实例化不了。window其实是对view的包装,提供更多的特性,比如标题、进度条等,可通过requestFeature(int
id)方法进行设置。

    每个activity都包含一个window, 它的内部实现是PhoneWindow, 要添加新的window在activity中,则通过WindowManager来实现,调用addView(View view, LayoutParams lp)方法,
lp是WindowManager.LayoutParams, lp有许多参数可以进行设置,而且都是public来的,主要有x, y, width, height, flags等,详细说明可以参看api文档和源代码。

    总的来说,我们看到的ui界面还是通过view来实现的,window只是对其进行了包装作了其他额外的处理,或者说,它是把view和WindowManager.LayoutParams绑定了一起,看看Dialog的实现就清楚了。window是不能实例化的,不过可以通过WindowManager的addView,
removeView方法来增加或删除。真正的window貌似在android里有2个,一个是activity的PhoneWindow, 另一个是dialog的MidWindow, 在activity里增加其他window就直接是view了。PhoneWindow在activity里显示的是它的DecorView, 它是一个FramwLayout, 调用activity的setContentView方法设置的view其实是DecorView下的一个子view, activity里view的结构层次可通过hierarchyviewer工具查看。估计这个DecorView最终也是通过WindowManager的addView方法才显示在屏幕上的,WindowManager的实现是WindowManagerImpl,
它里面每个view对应一个ViewRoot, ViewRoot通过setView方法才把把view显示出来。所以,ViewRoot才是最终实现view显示的类。


抱歉!评论已关闭.