android View绘制流程解析

本文深入探讨了Android中View的测量流程,包括onMeasure方法的作用及其内部实现细节,解析MeasureSpec的含义及不同模式对View尺寸的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天探讨下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
	}
	@Override
	protected 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的测量就分析到此,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值