自定义view之view显示流程
一个view要显示出来,需要经过测量、布局和绘制这三个过程,本章就这三个流程详细探讨一下。View的三大流程具体分析起来比较复杂,本文不会从根源详细地分析,但是可以保证能达到实用的地步。
1. measure过程 1.1 理解MeasureSpecView的测量方法为public final void measure(int widthMeasureSpec, int heightMeasureSpec)和protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec),它们的参数都是两个MeasuerSpec,因此在搞懂测量过程之前,要先搞明白MeasureSpec。
MeasureSpec可以理解为测量规范,它是一个32位的int值,高2位代表SpecMode,低30位代表SpecSize,SpecSize就代表测量值。查看MeasureSpec的部分源码,不难发现MeasureSpec的工作方式。
MeaureSpec部分源码: private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** * Creates a measure specification based on the supplied size and mode. * * The mode must always be one of the following: * <ul> * <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li> * <li>{@link android.view.View.MeasureSpec#EXACTLY}</li> * <li>{@link android.view.View.MeasureSpec#AT_MOST}</li> * </ul> * * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's * implementation was such that the order of arguments did not matter * and overflow in either value could impact the resulting MeasureSpec. * {@link android.widget.RelativeLayout} was affected by this bug. * Apps targeting API levels greater than 17 will get the fixed, more strict * behavior.</p> * * @param size the size of the measure specification * @param mode the mode of the measure specification * @return the measure specification based on size and mode */ public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } /** * Extracts the mode from the supplied measure specification. * * @param measureSpec the measure specification to extract the mode from * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, * {@link android.view.View.MeasureSpec#AT_MOST} or * {@link android.view.View.MeasureSpec#EXACTLY} */ @MeasureSpecMode public static int getMode(int measureSpec) { //noinspection ResourceType return (measureSpec & MODE_MASK); } /** * Extracts the size from the supplied measure specification. * * @param measureSpec the measure specification to extract the size from * @return the size in pixels defined in the supplied measure specification */ public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }不难看出MeasureSpec的结构是怎样的。并且我们可以从一个MeasureSpec中分解出SpecMode和SpecSize,也可以用SpecMode和SpecSize来组装一个MeasureSpec。在上面的代码中看到了有SpecMode,我们先看下SpecMode的源码和注释。
/** * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how big it wants to be. */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * Measure specification mode: The child can be as large as it wants up * to the specified size. */ public static final int AT_MOST = 2 << MODE_SHIFT;SpecMode有三种:
1. EXACTLY:确定大小,如果SpecMode是Exactly,那么SpecSize是多少,测量结果就是多少。比如子View在layout中设置的是指定大小,那么在测量时从父布局中传到measure方法的SpecMode就是Exactly。或者说子布局设置的是match_parent,而父布局此时已经能够确定自己的大小,那么模式也是Exactly。
2. AT_MOST:子布局可以自行确定自己的大小,但是不能超过SpecSize的大小。典型的就是父布局已经确定了自己的大小,而子布局设置的参数是wrap_content,
3. UNSPECIFIED:父布局对子view不做限制,要多大给多大。用于特殊的测量场合。
在弄明白MeasureSpec之后,就可以看看measure方法了,其实measure方法是View中的一个final方法,我们是无法重写的,measure方法做了一些基本工作,但是在measure方法的注释里说道真正的measure工作应该放在onMeasure方法里,所以基本都是重写onMeasure方法。接下来看一下onMeasure的源码和注释。大家在看源码的时候千万不要漏掉注释只看源码,注释是非常重要的,往往一些流程的说明就在注释里,看关键注释能比看十个方法的源码有用。
/** * <p> * Measure the view and its content to determine the measured width and the * measured height. This method is invoked by {@link #measure(int, int)} and * should be overridden by subclasses to provide accurate and efficient * measurement of their contents. * </p> * * <p> * <strong>CONTRACT:</strong> When overriding this method, you * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the * measured width and height of this view. Failure to do so will trigger an * <code>IllegalStateException</code>, thrown by * {@link #measure(int, int)}. Calling the superclass' * {@link #onMeasure(int, int)} is a valid use. * </p> * * <p> * The base class implementation of measure defaults to the background size, * unless a larger size is allowed by the MeasureSpec. Subclasses should * override {@link #onMeasure(int, int)} to provide better measurements of * their content. * </p> * * <p> * If this method is overridden, it is the subclass's responsibility to make * sure the measured height and width are at least the view's minimum height * and width ({@link #getSuggestedMinimumHeight()} and * {@link #getSuggestedMinimumWidth()}). * </p> * * @param widthMeasureSpec horizontal space requirements as imposed by the parent. * The requirements are encoded with * {@link android.view.View.MeasureSpec}. * @param heightMeasureSpec vertical space requirements as imposed by the parent. * The requirements are encoded with * {@link android.view.View.MeasureSpec}. * * @see #getMeasuredWidth() * @see #getMeasuredHeight() * @see #setMeasuredDimension(int, int) * @see #getSuggestedMinimumHeight() * @see #getSuggestedMinimumWidth() * @see android.view.View.MeasureSpec#getMode(int) * @see android.view.View.MeasureSpec#getSize(int) */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }