android View绘制流程解析

来源:互联网 发布:货物进出库软件 编辑:程序博客网 时间:2024/05/16 14:45

今天探讨下view的绘制流程,我们知道系统一个view控件想在屏幕上显示,一般要经过三个过程,测量,计算大小,绘制,也就说想让控件显示在屏幕上首先要做的是测量也就是onMeasure(),现在我们自定义一个View对象,然后跟到源码里去看看,

MyView.java

public class MyView extends View {public MyView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// TODO Auto-generated constructor stub}public MyView(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stub}public MyView(Context context) {super(context);// TODO Auto-generated constructor stub}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int mode = MeasureSpec.getMode(heightMeasureSpec);int size = MeasureSpec.getSize(heightMeasureSpec);Log.e("MyView","mode="+mode+"size="+size);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}}

View源码中有一个measure方法,也就是测量,源码如下:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||                widthMeasureSpec != mOldWidthMeasureSpec ||                heightMeasureSpec != mOldHeightMeasureSpec) {            // first clears the measured dimension flag            mPrivateFlags &= ~MEASURED_DIMENSION_SET;            if (ViewDebug.TRACE_HIERARCHY) {                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);            }            // measure ourselves, this should set the measured dimension flag back            onMeasure(widthMeasureSpec, heightMeasureSpec);            // flag not set, setMeasuredDimension() was not invoked, we raise            // an exception to warn the developer            if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {                throw new IllegalStateException("onMeasure() did not set the"                        + " measured dimension by calling"                        + " setMeasuredDimension()");            }            mPrivateFlags |= LAYOUT_REQUIRED;        }        mOldWidthMeasureSpec = widthMeasureSpec;        mOldHeightMeasureSpec = heightMeasureSpec;    }

不过发现measure这个方法被final修饰了,表示子类不能重写,但是我们仔细看源码发现其实真正测量方法是在onMeasure()方法中,那我们可以重写这个方法来实现我们要实现的效果,这就是为什么在MyView中重写了onMeasure()方法,

  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));    }
上面是onMeasure()方法,最终还是通过setMeasuredDimension()方法实现其作用,但是分析到这里好像和我们想要知道的结果没关系,对吧,别急,现在看我们MyView中重写的onMeasure()方法代码,方法中有2个形参,int widthMeasureSpec, int heightMeasureSpec,这2个参数是父控件传递过来的,现在看一段代码

int mode = MeasureSpec.getMode(heightMeasureSpec);

我们进入源码看下这个方法怎么实现的,

public static int makeMeasureSpec(int size, int mode) {            return size + mode;        }
我们发现其实宽度和高度并不是单一的由size决定,我们知道size就是控件的宽或者高,那么mode是什么呢?我们找找在哪定义了这个变量,发现并没有在源码中找到,因为这时我们传递进来的,那就没办法知道这个mode是什么了么?办法还是有的,可以查看makeMeasureSpec这个方法的解析,把鼠标放在这个方法上,按f2,我截图看看



MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格

我们看到mode有三个值,定义在MeasureSpec类中,现在解释下这三个变量,

 EXACTLY:

表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小

AT_MOST:

表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小

UNSPECIFIED:

表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到

那到源码中找到MeasureSpec类,看看它三个变量,因为一般这样的值都是固定的,

 public static class MeasureSpec {        private static final int MODE_SHIFT = 30;        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;        /**         * 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;        /**         * 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>         *         * @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(int size, int mode) {            return size + mode;        }        /**         * 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}         */        public static int getMode(int measureSpec) {            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);        }        /**         * Returns a String representation of the specified measure         * specification.         *         * @param measureSpec the measure specification to convert to a String         * @return a String with the following format: "MeasureSpec: MODE SIZE"         */        public static String toString(int measureSpec) {            int mode = getMode(measureSpec);            int size = getSize(measureSpec);            StringBuilder sb = new StringBuilder("MeasureSpec: ");            if (mode == UNSPECIFIED)                sb.append("UNSPECIFIED ");            else if (mode == EXACTLY)                sb.append("EXACTLY ");            else if (mode == AT_MOST)                sb.append("AT_MOST ");            else                sb.append(mode).append(" ");            sb.append(size);            return sb.toString();        }    }

这个类是View类中的一个内部类,发现其对应得值为0,1,2

那现在打印出MyView中onMeasure()中的mode是多少,因为源码中值都是向左移动了30,因此在获取mode后要向右移动30,这样的

int mode = MeasureSpec.getMode(heightMeasureSpec)>>>30;

这个mode值才和MeasureSpec类中定义的三个静态变量值有一个是相同的,也就是mode在0,1,2三个中其中一个了,


结果是1,那就明白了

view的测量就分析到此,

0 0
原创粉丝点击