【Android】View的工作流程——measure

1.View的工作流程入口

1.1DecorView被加载到Window中

看到这里你对Activity的构成有一定的了解,每个 Activity 都有一个与之关联的 Window 对象,而 DecorView 是这个 Window 的根视图。当DecorView被创建以及加载资源的时候,此时它的内容还无法进行显示,因为还没有加载到Window当中。接下来就看看是如何加载的。

  1. DecorView被加载到Window当中

当我们调用ActivitystartActivity方法的时候,最终调用的是ActivityThreadhandleLaunchActivity方法来创建Activity

public Activity handleLaunchActivity(ActivityClientRecord r,
                                     PendingTransactionActions pendingActions, Intent customIntent) {
.......
    //通过调用 performLaunchActivity(r, customIntent) 方法尝试启动一个 Activity。这个方法会创建 Activity 实例,并调用其 onCreate 方法,完成 DecorView 的创建。这个方法返回一个 Activity 对象,或者在失败时返回 null
    final Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        //为 ActivityClientRecord 对象 r 设置一个新的 Configuration 对象,该对象包含当前设备的配置信息
        r.createdConfig = new Configuration(mConfigurationController.getConfiguration());
        //用于报告 Activity 的大小配置,可能用于调试或日志记录
        reportSizeConfigurations(r);
        //如果 r.activity.mFinished 为 false(即 Activity 没有完成)且 pendingActions 不为 null,则设置 pendingActions 的一些状态
        if (!r.activity.mFinished && pendingActions != null) {
            //设置旧的状态;标记需要恢复实例状态;标记需要调用 onPostCreate 方法
            pendingActions.setOldState(r.state);
            pendingActions.setRestoreInstanceState(true);
            pendingActions.setCallOnPostCreate(true);
        }
    } else {
        // 调用 ActivityClient.getInstance().finishActivity 方法,通知活动管理器停止当前的 Activity。r.token用于标识当前的Activity;Activity.RESULT_CANCELED结果码,标识操作被取消或失败
        ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED,
                null /* resultData */, Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
    }
    return a;
}

上面的performLaunchActivity方法是用来创建Activity,里面会调用ActivityonCreate方法,从而完成DecorView的创建,来看看源码:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
 			......
            //将Activity实例设置到ActivityClientRecord中
            r.activity = activity;
    //检查Activity是否是持久化的。持久化的Activity可以在系统配置更改(如屏幕旋转)时保存和恢复状态
            if (r.isPersistable()) {
                //如果Activity是持久化的,调用Activity的onCreate方法,并传递临时状态和持久状态
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                //否则不传入持久状态
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            if (!activity.mCalled) {
                throw new SuperNotCalledException(
                    "Activity " + r.intent.getComponent().toShortString() +
                    " did not call through to super.onCreate()");
            }
            r.mLastReportedWindowingMode = config.windowConfiguration.getWindowingMode();
        }
        r.setState(ON_CREATE);
    } catch (SuperNotCalledException e) {
        throw e;
    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException(
                "Unable to start activity " + component
                + ": " + e.toString(), e);
        }
    }
    return activity;
}

我们看到会调用mInstrumentation.callActivityOnCreate方法,其实无论调用的是哪个方法,里面都是一样的,只是多了一个持久状态参数:

public void callActivityOnCreate(Activity activity, Bundle icicle,
        PersistableBundle persistentState) {
    //在调用Activity的onCreate方法之前执行的准备工作。这包括设置Activity的一些内部状态,或者进行其他必要的初始化操作
    prePerformCreate(activity);
    //调用Activity的performCreate方法,传递临时状态icicle和持久化状态persistentState。这个方法实际上会调用Activity的onCreate方法,并处理状态恢复
    activity.performCreate(icicle, persistentState);
    //在Activity的onCreate方法执行之后执行的后续工作。这包括进一步的初始化操作或者状态设置
    postPerformCreate(activity);
}

performCreate方法最终调用到了ActivityonCreate回调方法,onCreate方法中的setContentView方法就创建得到了DecorView,所以onCreate方法完成了DecorView的创建。

同理,onResume方法也是在ActivityThread中的handleResumeActivity方法中被调用的,我们之前提到的DecorView就是在这个方法里被添加进Window中的,我们接下来看这个handleResumeActivity的源码

@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, boolean shouldSendCompatFakeFocus, String reason) {
    ......
    //检查Activity的窗口是否已经创建,Activity是否未完成,以及Activity是否将可见
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        获取到了DecorView对象
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        //获得了WindowManager对象,是用来将DecorView添加到Window中的
        ViewManager wm = a.getWindowManager();
        //设置窗口布局等等
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        //如果窗口被标记为保留,执行相关操作
        if (r.mPreserveWindow) {
            //标记窗口已被添加
            a.mWindowAdded = true;
            //重置保留标记
            r.mPreserveWindow = false;
            ViewRootImpl impl = decor.getViewRootImpl();
            if (impl != null) {
                //通知视图系统窗口已被重建
                impl.notifyChildRebuilt();
            }
        }
        //如果Activity从客户端可见
        if (a.mVisibleFromClient) {
            //如果窗口尚未添加,将其添加到窗口管理器
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);//1
            } else {
                //如果窗口已添加,通知Activity窗口属性已更改
                a.onWindowAttributesChanged(l);
            }
        }
		......
}

在上面我们获取到了WindowManagerWindowManager是一个接口并继承于ViewManager,之后调用了WindowManageraddView方法,WindowManager的实现类是WindowManagerImpl,所以实际调用的是WindowManagerImpladdView方法

public final class WindowManagerImpl implements WindowManager {
    表示该字段(mGlobal)不应该被应用程序代码直接使用,它是供系统内部使用的
    @UnsupportedAppUsage
    //声明了一个WindowManagerGlobal类型的私有成员变量mGlobal,并使用WindowManagerGlobal.getInstance()方法来初始化它。WindowManagerGlobal是一个全局的单例类,提供全局的窗口管理功能
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ......
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyTokens(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
    ......
}

调用了WindowManagerGlobaladdView方法

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
	......
    //
    ViewRootImpl root;
    View panelParentView = null;
    //确保以下代码块在同一时间只能被一个线程执行,防止多线程并发导致的数据不一致问题
    synchronized (mLock) {
        //说明这不是一个无窗口的会话,创建一个普通的ViewRootImpl实例
        if (windowlessSession == null) {
            root = new ViewRootImpl(view.getContext(), display);
        //如果windowlessSession不为null,说明这是一个无窗口的会话,创建一个ViewRootImpl实例,并传入windowlessSession和WindowlessWindowLayout
        } else {
            root = new ViewRootImpl(view.getContext(), display,
                    windowlessSession, new WindowlessWindowLayout());
        }
		//为传入的view设置布局参数
        view.setLayoutParams(wparams);
		//将view添加到mViews列表中,这个列表管理所有的视图
        mViews.add(view);
        //将root添加到mRoots列表中,这个列表管理所有的ViewRootImpl实例
        mRoots.add(root);
        //将wparams添加到mParams列表中,这个列表管理所有的布局参数
        mParams.add(wparams);
        //调用root的setView方法,将view设置到root中,并传递布局参数、父视图和用户ID。这一步会启动视图的绘制流程
        try {
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {
            final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
            if (viewIndex >= 0) {
                removeViewLocked(viewIndex, true);
            }
            throw e;
        }
    }
}

上面调用了setView方法将DecorView传进去,就这样将DecorView加载到了Window当中。

1.2ViewRootImpl的performTraversals方法

上面我们提到此时将DecorView加载到了Window当中,但此时界面并不会完全显示出来,因为View的流程并没有执行完,还需经过measurelayout以及draw才会将View绘制出来。如何进行绘制的呢?

ViewRootImpl还有一个方法performTraversals,这个方法使ViewTree开启了View的工作流程:

private void performTraversals() {
			....
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,lp.privateFlags);
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//1
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;
                    if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
                    if (measureAgain) {
                        if (DEBUG_LAYOUT) Log.v(mTag,
                                "And hey let's measure once more: width=" + width
                                + " height=" + height);
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }
                    layoutRequested = true;
                }
            }
        } else {
            maybeHandleWindowMove(frame);
        }
        if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
            prepareSurfaces();
        }
        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
            performLayout(lp, mWidth, mHeight);//2
            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
                host.getLocationInWindow(mTmpLocation);
                mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
                        mTmpLocation[0] + host.mRight - host.mLeft,
                        mTmpLocation[1] + host.mBottom - host.mTop);
                host.gatherTransparentRegion(mTransparentRegion);
                if (mTranslator != null) {
                    mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
                }
                if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
                    mPreviousTransparentRegion.set(mTransparentRegion);
                    mFullRedrawNeeded = true;
                    SurfaceControl sc = getSurfaceControl();
                    if (sc.isValid()) {
                        mTransaction.setTransparentRegionHint(sc, mTransparentRegion).apply();
                    }
                }
            }
            if (DBG) {
                System.out.println("======================================");
                System.out.println("performTraversals -- after setFrame");
                host.debug();
            }
        }
       .....
	   .....	
       .....
        if (!isViewVisible) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).endChangingAnimations();
                }
                mPendingTransitions.clear();
            }

            if (mSyncBufferCallback != null) {
                mSyncBufferCallback.onBufferReady(null);
            }
        } else if (cancelAndRedraw) {
            scheduleTraversals();
        } else {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }
            if (!performDraw() && mSyncBufferCallback != null) {//3
                mSyncBufferCallback.onBufferReady(null);
            }
        }
		....
    }

这个方法比较长,我们仍然截取我们需要的部分,可以看到在注释1,2,3处分别调用了performMeasureperformLayoutperformDraw三个方法,这三个方法会会调用对应的measurelayoutdraw三个方法,而这三个方法又会调用对应的onMeasureonLayoutonDraw三个方法。

在这里插入图片描述

对于ViewGroup来说,performTraversals方法会依次调用performMeasureperformLayoutperformDraw三个方法,这三个方法会依次执行对应的工作方法,最后会调用onMeasure等对应方法,在对应的onMeasure等方法中又会依次调用子Viewmeasure等流程,总之就是会递归执行。

2.理解MeasureSpec

在了解View的测量过程之前先来看看MeasureSpecMeasureSpecView的一个内部类,其封装了一个View的规格尺寸,包括View的宽高信息。MeasureSpec很大程度上决定了View的规格尺寸,在Measure流程中,系统会将ViewLayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后在onMeasure方法中根据这个MeasureSpec来确定View的宽和高。这里的宽高是测量的宽高,不一定等于View最终的宽高。

public static class MeasureSpec {
    //是一个位移量,用于确定测量模式在MeasureSpec整数值中的位置,即模式需要左移30位
    private static final int MODE_SHIFT = 30;
    //是一个掩码,用于从MeasureSpec整数值中提取测量模式,0x3二进制为11,左移30为正好占据32整数的高位
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    //这是一个自定义的注解(@IntDef),用于定义测量模式的合法值:UNSPECIFIED、EXACTLY和AT_MOST
    @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
    @Retention(RetentionPolicy.SOURCE)
    public @interface MeasureSpecMode {}
    //三种测量模式
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    public static final int AT_MOST     = 2 << MODE_SHIFT;
    //创建一个MeasureSpec整数值,它结合了给定的尺寸(size)和模式(mode)
    public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << View.MeasureSpec.MODE_SHIFT) - 1) int size,
                                      @MeasureSpecMode int mode) {
        //如果sUseBrokenMakeMeasureSpec为true,则简单地将尺寸和模式相加(这是一种不推荐的做法,因为它可能导致不正确的行为)
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            //使用位运算将尺寸和模式组合成一个整数值
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
    //创建一个安全的MeasureSpec值。如果sUseZeroUnspecifiedMeasureSpec为true且模式为UNSPECIFIED,则返回0;否则,调用makeMeasureSpec(size, mode)
    @UnsupportedAppUsage
    public static int makeSafeMeasureSpec(int size, int mode) {
        if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
            return 0;
        }
        return makeMeasureSpec(size, mode);
    }
    //从给定的MeasureSpec值中提取测量模式
    @MeasureSpecMode
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }
    //从给定的MeasureSpec值中提取尺寸
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
	//调整给定的MeasureSpec值,根据delta值修改尺寸。如果模式是UNSPECIFIED,则不进行调整。如果调整后的尺寸为负数,则记录错误并将尺寸设置为
    static int adjust(int measureSpec, int delta) {
        final int mode = getMode(measureSpec);
        int size = getSize(measureSpec);
        if (mode == UNSPECIFIED) {
            return makeMeasureSpec(size, UNSPECIFIED);
        }
        size += delta;
        if (size < 0) {
            Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                    ") spec: " + toString(measureSpec) + " delta: " + delta);
            size = 0;
        }
        return makeMeasureSpec(size, mode);
    }
    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();
    }
}

根据常量可以看出它代表了32位的int值,通过将specModespecSize打包为一个int值来避免过多的对象内存分配,为了方便操作,提供了打包和解包方法,其中高两位代表specMode(测量模式),低30位则代表specSize(测量大小)。

specMode有三种模式:

  • UNSPECIFIED:未指定模式,View想多大就多大,父容器不做限制,一般用于系统内部的测量,表示一种测量的状态
  • EXACTLY:精准模式,对应于match_parent属性和具体的数值,父容器测量出View所需要的大小,也就是specSize的值
  • AT_MOST:最大模式,对应于LayoutParams中的wrap_content属性,子View的最终大小是父View指定的specSize值,并且子View的大小不能大于这个值

View的测量流程当中,通过makeMeasureSpec来保存宽和高的信息。通过getMode或者getSize得到模式的宽和高。MeasureSpec是受自身的LayoutParams和父容器的MeasureSpec共同影响的,作为顶层ViewDecorView来说,其并没有父容器,它的MeasureSpec是如何得到的,返回到ViewRootImplperformTraversals方法:

//调用getRootMeasureSpec方法来创建子视图的宽度测量规格。这个方法会根据父容器的尺寸、子视图的布局参数和私有标志来确定子视图的宽度测量规格
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
                        lp.privateFlags);
//调用performMeasure方法来执行子视图的测量操作。这个方法会使用前面创建的宽度和高度测量规格来确定子视图的尺寸
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

在注释1处调用了getRootMeasureSpec方法,看看他都做了什么:

//windowSize指的是窗口的尺寸
private static int getRootMeasureSpec(int windowSize, int measurement, int privateFlags) {
    int measureSpec;
    //这行代码检查 privateFlags 是否包含 PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT 标志。如果包含,rootDimension 被设置为 MATCH_PARENT,否则使用 measurement 的值
    final int rootDimension = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
            ? MATCH_PARENT : measurement;
    switch (rootDimension) {
        //如果 rootDimension 是 MATCH_PARENT,表示根视图想要填满父容器的大小。因此,measureSpec 被设置为 windowSize 和 MeasureSpec.EXACTLY 模式,强制根视图的大小为窗口的大小
        case ViewGroup.LayoutParams.MATCH_PARENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        //如果 rootDimension 是 WRAP_CONTENT,表示根视图想要包裹其内容,但窗口可以调整大小。因此,measureSpec 被设置为 windowSize 和 MeasureSpec.AT_MOST 模式,为根视图设置最大尺寸
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        //如果 rootDimension 是具体的尺寸值,这意味着根视图想要一个确切的大小。因此,measureSpec 被设置为 rootDimension 和 MeasureSpec.EXACTLY 模式,强制根视图的大小为 rootDimension 指定的大小
        default:
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
    }
    return measureSpec;
}

windowSize指的是窗口的尺寸,所以对于DecorView来说,它的MeasureSpec由自身的LayoutParams和窗口的尺寸决定,这一点和普通的View不同。

3.View的measure流程

measure用来测量View的宽和高。他的流程分别为Viewmeasure流程和ViewGroupmeasure流程。

3.1View的measure流程

先来看看View的onMeasure方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

setMeasuredDimension方法用于设置 View 的测量尺寸。具体而言,它用于设置 View 在进行测量过程中计算得出的宽度和高度。当一个 View 被测量时,系统会调用其 onMeasure() 方法来计算它的宽度和高度。在 onMeasure() 方法内部,会调用 setMeasureDimension() 方法来设置测量尺寸,将计算得出的宽度和高度保存起来。再来看看getDefaultSize方法:

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        //如果模式是UNSPECIFIED,表示父视图对子视图的尺寸没有具体要求,因此结果保持为建议的尺寸size
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        //如果模式是AT_MOST或EXACTLY,表示父视图对子视图的尺寸有具体的要求,因此结果设置为specSize,即测量规格中指定的尺寸
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

根据上面我们知道不同模式下的返回值,也就是说,对于一个直接继承于View的自定义View来说,它的wrap_contentmatch_parent属性的效果是一样的。因此如果要实现自定义Viewwrap_content,则需要重写onMeasure方法,并对自定义Viewwrap_content属性进行处理。我们看到在UNSPECIFIED模式下返回的是第一个参数size的值,size的值由onMeasure方法看来是通过getSuggestedMinimumWidth方法或者getSuggestedMinimumHeight方法获取的,接下来就来看看这个方法做了什么:

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth,   mBackground.getMinimumWidth());
}

如果View没有背景,则取值为mMinWidth(这是视图的内部最小宽度,可能是在视图构造时设置的或者由视图自身逻辑决定的)。mMinWidth是可以设置的,它对应的属性Android:minWidth或者ViewsetMinimumWidth的值,如果不指定的话,默认值为0。如果View设置了背景,则比较 mMinWidth(视图的内部最小宽度)和 mBackground.getMinimumWidth()(背景图的最小宽度【当背景图设置了大小则为固有的大小,当没有设置则为0】)的大小,并返回两者中的最大值。

3.2ViewGroup的measure流程

前面提到了View的measure流程,接下来看看ViewGroup的measure流程,对于ViewGroup他不止要测量自身,还要遍历的调用子元素的measure方法,ViewGroup中没有定义onMeasure方法,但却定义了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);
            }
        }
    }
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    //获得子元素的LayoutParams属性
        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);
    }

一个一个遍历子元素,具体获得子View的MeasureSpec参数是在measureChild方法中,通过getChildMeasureSpec得到了子View的参数,传入的第一个参数是父容器的MeasureSpec,之前提到过子ViewMeasureSpec的转化会受到父容器的影响就是在这里体现,第二个参数是已经使用的空间,具体来说就是Padding参数(还有个方法是也考虑到Margin参数的),第三个参数是子View的布局参数LayoutParams。,接下来就看看是如何进行转换的:

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) {
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) 
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
  • MeasureSpec.EXACTLY:如果父视图的模式是精确模式,子视图的尺寸由其布局参数决定。如果子视图的布局参数是具体尺寸,则子视图的测量模式也是精确模式;如果子视图的布局参数是MATCH_PARENT,则子视图的尺寸等于父视图的可用尺寸,模式也是精确模式;如果子视图的布局参数是WRAP_CONTENT,则子视图的尺寸等于父视图的可用尺寸,但模式是AT_MOST
  • MeasureSpec.AT_MOST:如果父视图的模式是最大模式,子视图的尺寸和模式与EXACTLY模式类似,但当子视图的布局参数是MATCH_PARENT时,子视图的模式是AT_MOST
  • MeasureSpec.UNSPECIFIED:如果父视图的模式是未指定模式,子视图的尺寸和模式取决于子视图的布局参数。如果子视图的布局参数是具体尺寸,则子视图的测量模式是精确模式;如果子视图的布局参数是MATCH_PARENTWRAP_CONTENT,则子视图的尺寸可能是0(如果View.sUseZeroUnspecifiedMeasureSpectrue)或父视图的可用尺寸,模式是未指定模式。

在这里插入图片描述

文章到这里就结束了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值