android6.0 Activity(五) Activity的测量(Measure)、布局(Layout)和绘制(Draw)



原文:http://blog.youkuaiyun.com/luoshengyang/article/details/8372924  http://blog.youkuaiyun.com/feiduclear_up/article/details/46772477。在这两者基础上改动了一下。

Android应用程序窗口的绘图表面在创建完成之后,我们就可以从上到下地绘制它里面的各个视图了,即各个UI元素了。不过在绘制这些UI元素之前,我们还需要从上到下地测量它们实际所需要的大小,以及对它们的位置进行合适的安排,即对它们进行合适的布局。在本文中,我们就将详细地分析Android应用程序窗口的测量、布局以及绘制过程。

一、简介

Android应用程序窗口请求SurfaceFlinger服务创建了一个绘图表面之后,就可以接着请求为该绘图表面创建图形缓冲区,而当Android应用程序窗口往这些图形缓冲区填充好UI数据之后,就可以请求SurfaceFlinger服务将它们渲染到硬件帧缓冲区中去,这样我们就可以看到应用程序窗口的UI了。

Android应用程序窗口一般不会直接去操作分配给它的图形缓冲区,而是通过一些图形库API来操作。例如在开机画面使用C++来开发的开机动画应用程序bootanimation,它是通过OpenGL提供的API来绘制UI的。对于使用Java来开发的Android应用程序来说,它们一般是使用Skia图形库提供的API来绘制UI的。在Skia图库中,所有的UI都是绘制在画布(Canvas)上的,因此,Android应用程序窗口需要将它的图形缓冲区封装在一块画布里面,然后才可以使用Skia库提供的API来绘制UI。

 我们知道,一个Android应用程序窗口里面包含了很多UI元素,这些UI元素是以树形结构来组织的,即它们存在着父子关系,其中,子UI元素位于父UI元素里面,因此,在绘制一个Android应用程序窗口的UI之前,我们首先要确定它里面的各个子UI元素在父UI元素里面的大小以及位置。确定各个子UI元素在父UI元素里面的大小以及位置的过程又称为测量过程和布局过程。因此,Android应用程序窗口的UI渲染过程可以分为测量、布局和绘制三个阶段

Android应用程序窗口的顶层视图是一个类型为DecorView的UI元素,这个顶层视图最终是由ViewRootImpl类的成员函数performTraversals来启动测量、布局和绘制操作的。


二、performTraversals函数

之前的博客中,我们分析过如果从ActivityThread的handleResumeActivity函数,最后到ViewRootImpl的performTraversals函数进行绘制。

private void performTraversals() {
        // cache mView since it is used so much below...
        //mView就是DecorView根布局
        final View host = mView;
        if (host == null || !mAdded)
            return;
        //是否正在遍历
        mIsInTraversal = true;
        //是否马上绘制View
        mWillDrawSoon = true;

        .............
        //顶层视图DecorView所需要窗口的宽度和高度
        int desiredWindowWidth;
        int desiredWindowHeight;

        .....................
        //在构造方法中mFirst已经设置为true,表示是否是第一次绘制DecorView
        if (mFirst) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            //如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要窗口的宽度和高度就是除了状态栏
            if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
                    || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
                // NOTE -- system code, won't try to do compat mode.
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {//否则顶层视图DecorView所需要窗口的宽度和高度就是整个屏幕的宽高
                DisplayMetrics packageMetrics =
                    mView.getContext().getResources().getDisplayMetrics();
                desiredWindowWidth = packageMetrics.widthPixels;
                desiredWindowHeight = packageMetrics.heightPixels;
            }
    }
............
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高
 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

  // Ask host how big it wants to be
  //执行测量操作
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

........................
//执行布局操作
 performLayout(lp, desiredWindowWidth, desiredWindowHeight);

.......................
//执行绘制操作
performDraw();

}

该方法主要流程就体现了View绘制渲染的三个主要步骤,分别是测量,布局,绘制三个阶段。

这里先给出Android系统View的绘制流程:依次执行View类里面的如下三个方法:
1.measure(int ,int) :测量View的大小
2.layout(int ,int ,int ,int) :设置子View的位置
3.draw(Canvas) :绘制View内容到Canvas画布上


三、测量

我们先看下在performTraversals函数中调用performMeasure之前会先调用getRootMeasureSpec函数,通过getRootMeasureSpec方法获得顶层视图DecorView的测量规格

                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed!  mWidth="
                            + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                            + " mHeight=" + mHeight
                            + " measuredHeight=" + host.getMeasuredHeight()
                            + " coveredInsetsChanged=" + contentInsetsChanged);

                     // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

我们来看下getRootMeasureSpec函数

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
        //匹配父容器时,测量模式为MeasureSpec.EXACTLY,测量大小直接为屏幕的大小,也就是充满真个屏幕
        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        //包裹内容时,测量模式为MeasureSpec.AT_MOST,测量大小直接为屏幕大小,也就是充满真个屏幕
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        //其他情况时,测量模式为MeasureSpec.EXACTLY,测量大小为DecorView顶层视图布局设置的大小。
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

分析:该方法主要作用是在整个窗口的基础上计算出root view(顶层视图DecorView)的测量规格,该方法的两个参数分别表示:
1.windowSize:当前手机窗口的有效宽和高,一般都是除了通知栏的屏幕宽和高
2.rootDimension 根布局DecorView请求的宽和高,由前面的博客我们知道是MATCH_PARENT

我们的DecorView根布局宽和高都是MATCH_PARENT,因此DecorView根布局的测量模式就是MeasureSpec.EXACTLY,测量大小一般都是整个屏幕大小,所以一般我们的Activity窗口都是全屏的。因此上面代码走第一个分支,通过调用MeasureSpec.makeMeasureSpec方法将DecorView的测量模式和测量大小封装成DecorView的测量规格。

        public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
makeMeasureSpec只是刚mode封装在高位中。

我们再来看下performMeasure函数,就是调用了DecorView的measure函数,而DecorView中没有measure函数,一直到View基类才有measure函数

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

我们先来看下View的measure函数,注意这个方法是final的,子类不能继承。

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||//和上次测量不一样
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                    mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);//调用子类的onMeasure函数
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;//保存上一次测量值
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

 参数widthMeasureSpec和heightMeasureSpec用来描述当前正在处理的视图可以获得的最大宽度和高度。对于应用程序窗口的顶层视图来说,我们也可以认为这两个参数是用来描述应用程序窗口的宽度和高度。

        View类的成员变量mPrivateFlags的类型为int,如果它的某一个位的值不等于0,那么就隐含着当前视图有一个相应的操作在等待执行中。View类的另外两个成员变量mOldWidthMeasureSpec和mOldHeightMeasureSpec用来保存当前视图上一次可以获得的最大宽度和高度。

        当ViewRoot类的成员变量mPrivateFlags的FORCE_LAYOUT位不等于0时,就表示当前视图正在请求执行一次布局操作,这时候函数就需要重新测量当前视图的宽度和高度。此外,当参数widthMeasureSpec和heightMeasureSpec的值不等于ViewRoot类的成员变量mldWidthMeasureSpec和mOldHeightMeasureSpec的值时,就表示当前视图上一次可以获得的最大宽度和高度已经失效了,这时候函数也需要重新测量当前视图的宽度和高度。

        当View类的成员函数measure决定要重新测量当前视图的宽度和高度之后,它就会首先将成员变量mPrivateFlags的MEASURED_DIMENSION_SET位设置为0,接着再调用另外一个成员函数onMeasure来真正执行测量宽度和高度的操作。View类的成员函数onMeasure执行完成之后,需要再调用另外一个成员函数setMeasuredDimension来将测量好的宽度和高度设置到View类的成员变量mMeasuredWidth和mMeasuredHeight中,并且将成员变量mPrivateFlags的EASURED_DIMENSION_SET位设置为1。这个操作是强制的,因为当前视图最终就是通过View类的成员变量mMeasuredWidth和mMeasuredHeight来获得它的宽度和高度的。为了保证这个操作是强制的,View类的成员函数measure再接下来就会检查成员变量mPrivateFlags的EASURED_DIMENSION_SET位是否被设置为1了。如果不是的话,那么就会抛出一个类型为IllegalStateException的异常来。

        View类的成员函数measure最后就会把参数widthMeasureSpec和heightMeasureSpec的值保存在成员变量mldWidthMeasureSpec和mOldHeightMeasureSpec中,以便可以记录当前视图上一次可以获得的最大宽度和高度。

        View类的成员函数onMeasure一般是由其子类来重写的。例如,对于用来应用程序窗口的顶层视图的DecorView类来说,它是通过父类FrameLayout来重写祖父类View的成员函数onMeasure的。因此,接下来我们就分析FrameLayout类的成员函数onMeasure的实现。

onMeasure我们先从子类DecorView的onMeasure入手,这个函数主要是调整了两个入参高度和宽度,然后调用其父类的onMeasure函数。

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
            final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;

            final int widthMode = getMode(widthMeasureSpec);
            final int heightMode = getMode(heightMeasureSpec);

            boolean fixedWidth = false;
            if (widthMode == AT
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值