欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android视图的绘制流程(上) View的测量

程序员文章站 2024-02-21 22:55:22
综述   view的绘制流程可以分为三大步,它们分别是measure,layout和draw过程。measure表示view的测量过程,用于测量view的宽度和高度;la...

综述

  view的绘制流程可以分为三大步,它们分别是measure,layout和draw过程。measure表示view的测量过程,用于测量view的宽度和高度;layout用于确定view在父容器的位置;draw则是负责将view绘制到屏幕中。下面主要来看一下view的measure过程。

测量过程

  view的绘制流程是从viewroot的performtraversals方法开始的,viewroot对应viewrootimpl类。viewroot在performtraversals中会调用performmeasure方法来进行对根view的测量过程。而在performmeasure方法中又会调用view的measure方法。对于view的measure方法它是一个final类型,也就是说这个measure方法不能被子类重写。但是在measure方法中调用了onmeasure方法。所以view的子类可以重写onmeasure方法来实现各自的measure过程。在这里也就是主要对onmeasure方法进行分析。

measurespec

  measurespec是view类中的一个静态内部类。一个measurespec封装了父布局传递给子布局的布局要求。每个measurespec都代表着一个高度或宽度的要求。每个mesurespec都是由specsize和specmode组成,它代表着一个32位的int值,其中高2位代表specsize,低30位代表specmode。
  measurespec的测量模式有三种,下面介绍一下这三种测量模式:

unspecified
父容器对子view没有任何的限制,子view可以是任何的大小。
exactly
父容器为子view大小指定一个具体值,view的最终大小就是specsize。对应view属性match_parent和具体值。
at_most
子view的大小最大只能是specsize,也就是所子view的大小不能超过specsize。对应view属性的wrap_content.

  在measurespec中可以通过specsize和specmode并使用makemeasurespec方法来创建一个measurespec,还可以通过getmode和getsize来获取measurespec的specmode和specsize。

view的测量过程

  在上面已经说到,view的measure过程是由measure方法来完成的,而measure方法通过调用onmeasure方法来完成view的measure过程。那么就来看一下onmeasure方法。

protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
  setmeasureddimension(getdefaultsize(getsuggestedminimumwidth(), widthmeasurespec),
      getdefaultsize(getsuggestedminimumheight(), heightmeasurespec));
}

  在view的onmeasure方法中只是调用了setmeasureddimension方法,setmeasureddimension方法的作用就是设置view的高和宽的测量值。对于view测量后宽和高的值是通过getdefaultsize方法来获取的。下面就来一下这个getdefaultsize方法。

public static int getdefaultsize(int size, int measurespec) {
  int result = size;
  int specmode = measurespec.getmode(measurespec);
  int specsize = measurespec.getsize(measurespec);

  switch (specmode) {
  case measurespec.unspecified:
    result = size;
    break;
  case measurespec.at_most:
  case measurespec.exactly:
    result = specsize;
    break;
  }
  return result;
}


  对于measurespec的at_most和exactly模式下,直接返回的就是measurespec的specsize,也就是说这个specsize就是view测量后的大小。而对于在unspecified模式下,view的测量值则为getdefaultsize方法中的第一个参数size。这个size所对应的宽和高是通过getsuggestedminimumwidth和getsuggestedminimumheight两个方法获取的。下面就来看一下这两个方法。

protected int getsuggestedminimumheight() {
  return (mbackground == null) ? mminheight : max(mminheight, mbackground.getminimumheight());

}

protected int getsuggestedminimumwidth() {
  return (mbackground == null) ? mminwidth : max(mminwidth, mbackground.getminimumwidth());
}

  在这里可以看到对于view宽和高的取值是根据view是否存在背景进行设置的。在这里以view的宽度来进行说明。若是view没有背景则是view的宽度mminwidth。对于mminwidth值得设置可以在xml布局文件中设置minwidth属性,它的默认值为0。也可以通过调用view的setminimumwidth()方法其赋值。若是view存在背景的话,则取view本身最小宽度mminwidth和view背景的最小宽度它们中的最大值。

viewgroup的测量过程

  对于viewgroup的measure过程,viewgroup处理measure自己本身的大小,还需要遍历子view,并调用它们的measure方法,然后各个子元素再去递归执行measure过程。在viewgroup中并没有重写onmeasure方法,因为viewgroup它是一个抽象类,对于不同的具体viewgroup它的onmeasure方法中所实现的过程不一样。但是在viewgroup中提供了一个measurechildren方法,对子view进行测量。下面就来看一下这个measurechildren方法。

protected void measurechildren(int widthmeasurespec, int heightmeasurespec) {
  final int size = mchildrencount;
  final view[] children = mchildren;
  for (int i = 0; i < size; ++i) {
    final view child = children[i];
    if ((child.mviewflags & visibility_mask) != gone) {
      measurechild(child, widthmeasurespec, heightmeasurespec);
    }
  }
}

  在这里获取viewgroup中所有的子view。然后遍历viewgroup中子view并调用measurechild方法来完成对子view的测量。下面看一下measurechild方法。

protected void measurechild(view child, int parentwidthmeasurespec,
    int parentheightmeasurespec) {
  final layoutparams lp = child.getlayoutparams();

  final int childwidthmeasurespec = getchildmeasurespec(parentwidthmeasurespec,
      mpaddingleft + mpaddingright, lp.width);
  final int childheightmeasurespec = getchildmeasurespec(parentheightmeasurespec,
      mpaddingtop + mpaddingbottom, lp.height);

  child.measure(childwidthmeasurespec, childheightmeasurespec);
}


  在这段代码中通过getchildmeasurespec方法获取子view宽和高的measurespec。然后调用子view的measure方法开始对view进行测量。下面就来看一下是如何通过getchildmeasurespec方法来获取view的measurespec的。

public static int getchildmeasurespec(int spec, int padding, int childdimension) {
  int specmode = measurespec.getmode(spec);
  int specsize = measurespec.getsize(spec);

  int size = math.max(0, specsize - padding);

  int resultsize = 0;
  int resultmode = 0;

  switch (specmode) {
  // parent has imposed an exact size on us
  case measurespec.exactly:
    if (childdimension >= 0) {
      resultsize = childdimension;
      resultmode = measurespec.exactly;
    } else if (childdimension == layoutparams.match_parent) {
      // child wants to be our size. so be it.
      resultsize = size;
      resultmode = measurespec.exactly;
    } else if (childdimension == layoutparams.wrap_content) {
      // child wants to determine its own size. it can't be
      // bigger than us.
      resultsize = size;
      resultmode = measurespec.at_most;
    }
    break;

  // parent has imposed a maximum size on us
  case measurespec.at_most:
    if (childdimension >= 0) {
      // child wants a specific size... so be it
      resultsize = childdimension;
      resultmode = measurespec.exactly;
    } else if (childdimension == layoutparams.match_parent) {
      // child wants to be our size, but our size is not fixed.
      // constrain child to not be bigger than us.
      resultsize = size;
      resultmode = measurespec.at_most;
    } else if (childdimension == layoutparams.wrap_content) {
      // child wants to determine its own size. it can't be
      // bigger than us.
      resultsize = size;
      resultmode = measurespec.at_most;
    }
    break;

  // parent asked to see how big we want to be
  case measurespec.unspecified:
    if (childdimension >= 0) {
      // child wants a specific size... let him have it
      resultsize = childdimension;
      resultmode = measurespec.exactly;
    } else if (childdimension == layoutparams.match_parent) {
      // child wants to be our size... find out how big it should
      // be
      resultsize = view.susezerounspecifiedmeasurespec ? 0 : size;
      resultmode = measurespec.unspecified;
    } else if (childdimension == layoutparams.wrap_content) {
      // child wants to determine its own size.... find out how
      // big it should be
      resultsize = view.susezerounspecifiedmeasurespec ? 0 : size;
      resultmode = measurespec.unspecified;
    }
    break;
  }
  return measurespec.makemeasurespec(resultsize, resultmode);
}

  在这段代码对于measurespec的获取主要是根据父容器的measurespec和view本身的layoutparams。下面通过一张表格来看一下它们之间的对应关系。

Android视图的绘制流程(上) View的测量  

       到这里通过getchildmeasurespec方法获取到子view的measurespec以后,便调用view的measure方法,开始对view进行测量。
  正如刚才说的那样对于viewgroup它是一个抽象类,并没有重写view的onmeasure方法。但是到具体的viewgroup时,例如framelayout,linearlayout,relativelayout等,它们通过重写onmeasure方法来来完成自身以及子view的measure过程。下面以framelayout为例,看一下的measure过程。在framelayout中,它的measure过程也算是比较简单,下面就来看一下framelayout中的onmeasure方法。

protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
  int count = getchildcount();

  final boolean measurematchparentchildren =
      measurespec.getmode(widthmeasurespec) != measurespec.exactly ||
      measurespec.getmode(heightmeasurespec) != measurespec.exactly;
  mmatchparentchildren.clear();

  int maxheight = 0;
  int maxwidth = 0;
  int childstate = 0;

  for (int i = 0; i < count; i++) {
    final view child = getchildat(i);
    if (mmeasureallchildren || child.getvisibility() != gone) {
      measurechildwithmargins(child, widthmeasurespec, 0, heightmeasurespec, 0);
      final layoutparams lp = (layoutparams) child.getlayoutparams();
      maxwidth = math.max(maxwidth,
          child.getmeasuredwidth() + lp.leftmargin + lp.rightmargin);
      maxheight = math.max(maxheight,
          child.getmeasuredheight() + lp.topmargin + lp.bottommargin);
      childstate = combinemeasuredstates(childstate, child.getmeasuredstate());
      if (measurematchparentchildren) {
        if (lp.width == layoutparams.match_parent ||
            lp.height == layoutparams.match_parent) {
          mmatchparentchildren.add(child);
        }
      }
    }
  }

  // account for padding too
  maxwidth += getpaddingleftwithforeground() + getpaddingrightwithforeground();
  maxheight += getpaddingtopwithforeground() + getpaddingbottomwithforeground();

  // check against our minimum height and width
  maxheight = math.max(maxheight, getsuggestedminimumheight());
  maxwidth = math.max(maxwidth, getsuggestedminimumwidth());

  // check against our foreground's minimum height and width
  final drawable drawable = getforeground();
  if (drawable != null) {
    maxheight = math.max(maxheight, drawable.getminimumheight());
    maxwidth = math.max(maxwidth, drawable.getminimumwidth());
  }

  setmeasureddimension(resolvesizeandstate(maxwidth, widthmeasurespec, childstate),
      resolvesizeandstate(maxheight, heightmeasurespec,
          childstate << measured_height_state_shift));

  count = mmatchparentchildren.size();
  if (count > 1) {
    for (int i = 0; i < count; i++) {
      final view child = mmatchparentchildren.get(i);
      final marginlayoutparams lp = (marginlayoutparams) child.getlayoutparams();

      final int childwidthmeasurespec;
      if (lp.width == layoutparams.match_parent) {
        final int width = math.max(0, getmeasuredwidth()
            - getpaddingleftwithforeground() - getpaddingrightwithforeground()
            - lp.leftmargin - lp.rightmargin);
        childwidthmeasurespec = measurespec.makemeasurespec(
            width, measurespec.exactly);
      } else {
        childwidthmeasurespec = getchildmeasurespec(widthmeasurespec,
            getpaddingleftwithforeground() + getpaddingrightwithforeground() +
            lp.leftmargin + lp.rightmargin,
            lp.width);
      }

      final int childheightmeasurespec;
      if (lp.height == layoutparams.match_parent) {
        final int height = math.max(0, getmeasuredheight()
            - getpaddingtopwithforeground() - getpaddingbottomwithforeground()
            - lp.topmargin - lp.bottommargin);
        childheightmeasurespec = measurespec.makemeasurespec(
            height, measurespec.exactly);
      } else {
        childheightmeasurespec = getchildmeasurespec(heightmeasurespec,
            getpaddingtopwithforeground() + getpaddingbottomwithforeground() +
            lp.topmargin + lp.bottommargin,
            lp.height);
      }

      child.measure(childwidthmeasurespec, childheightmeasurespec);
    }
  }
}


  在这部分代码中逻辑也很简单,主要完成了两件事。首先framelayout完成自身的测量过程,然后在遍历子view,执行view的measure方法,完成view的measure过程。在这里代码比较简单就不在进行详细描述。

总结

  最后对view和viewgroup的measure过程做一下总结。对于view,它的measure很简单,在获取到view的高和宽的测量值之后,便为其设置高和宽。而对于viewgroup来说,除了完成自身的measure过程以外,还需要遍历子view,完成子view的测量过程。