1.View的工作流程入口
1.1DecorView被加载到Window中
看到这里你对Activity
的构成有一定的了解,每个 Activity
都有一个与之关联的 Window
对象,而 DecorView
是这个 Window
的根视图。当DecorView
被创建以及加载资源的时候,此时它的内容还无法进行显示,因为还没有加载到Window
当中。接下来就看看是如何加载的。
DecorView
被加载到Window
当中
当我们调用Activity
的startActivity
方法的时候,最终调用的是ActivityThread
的handleLaunchActivity
方法来创建Activity
public Activity handleLaunchActivity(ActivityClientRecord r,PendingTransactionActions pendingActions, Intent customIntent) {
.......//通过调用 performLaunchActivity(r, customIntent) 方法尝试启动一个 Activity。这个方法会创建 Activity 实例,并调用其 onCreate 方法,完成 DecorView 的创建。这个方法返回一个 Activity 对象,或者在失败时返回 nullfinal 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
,里面会调用Activity
的onCreate
方法,从而完成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
方法最终调用到了Activity
的onCreate
回调方法,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);}}......
}
在上面我们获取到了WindowManager
,WindowManager
是一个接口并继承于ViewManager
,之后调用了WindowManager
的addView
方法,WindowManager
的实现类是WindowManagerImpl
,所以实际调用的是WindowManagerImpl
的addView
方法
public final class WindowManagerImpl implements WindowManager {表示该字段(mGlobal)不应该被应用程序代码直接使用,它是供系统内部使用的@UnsupportedAppUsage//声明了一个WindowManagerGlobal类型的私有成员变量mGlobal,并使用WindowManagerGlobal.getInstance()方法来初始化它。WindowManagerGlobal是一个全局的单例类,提供全局的窗口管理功能private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();......@Overridepublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyTokens(params);mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,mContext.getUserId());}......
}
调用了WindowManagerGlobal
的addView
方法
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
的流程并没有执行完,还需经过measure
、layout
以及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);//1int 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);//2if ((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) {//3mSyncBufferCallback.onBufferReady(null);}}....}
这个方法比较长,我们仍然截取我们需要的部分,可以看到在注释1,2,3处分别调用了performMeasure
,performLayout
,performDraw
三个方法,这三个方法会会调用对应的measure
,layout
和draw
三个方法,而这三个方法又会调用对应的onMeasure
,onLayout
,onDraw
三个方法。
对于
ViewGroup
来说,performTraversals
方法会依次调用performMeasure
,performLayout
和performDraw
三个方法,这三个方法会依次执行对应的工作方法,最后会调用onMeasure
等对应方法,在对应的onMeasure
等方法中又会依次调用子View
的measure
等流程,总之就是会递归执行。
2.理解MeasureSpec
在了解View
的测量过程之前先来看看MeasureSpec
。MeasureSpec
是View
的一个内部类,其封装了一个View
的规格尺寸,包括View
的宽高信息。MeasureSpec
很大程度上决定了View
的规格尺寸,在Measure
流程中,系统会将View
的LayoutParams
根据父容器所施加的规则转换成对应的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)@UnsupportedAppUsagepublic static int makeSafeMeasureSpec(int size, int mode) {if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {return 0;}return makeMeasureSpec(size, mode);}//从给定的MeasureSpec值中提取测量模式@MeasureSpecModepublic 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 ");elsesb.append(mode).append(" ");sb.append(size);return sb.toString();}
}
根据常量可以看出它代表了32位的int值,通过将specMode
与specSize
打包为一个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
共同影响的,作为顶层View
的DecorView
来说,其并没有父容器,它的MeasureSpec
是如何得到的,返回到ViewRootImpl
的performTraversals
方法:
//调用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
的宽和高。他的流程分别为View
的measure
流程和ViewGroup
的measure
流程。
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,表示父视图对子视图的尺寸没有具体要求,因此结果保持为建议的尺寸sizecase MeasureSpec.UNSPECIFIED:result = size;break;//如果模式是AT_MOST或EXACTLY,表示父视图对子视图的尺寸有具体的要求,因此结果设置为specSize,即测量规格中指定的尺寸case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;}
根据上面我们知道不同模式下的返回值,也就是说,对于一个直接继承于View
的自定义View
来说,它的wrap_content
与match_parent
属性的效果是一样的。因此如果要实现自定义View
的wrap_content
,则需要重写onMeasure
方法,并对自定义View
的wrap_content
属性进行处理。我们看到在UNSPECIFIED
模式下返回的是第一个参数size
的值,size
的值由onMeasure
方法看来是通过getSuggestedMinimumWidth
方法或者getSuggestedMinimumHeight
方法获取的,接下来就来看看这个方法做了什么:
protected int getSuggestedMinimumWidth() {return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
如果View没有背景,则取值为mMinWidth
(这是视图的内部最小宽度,可能是在视图构造时设置的或者由视图自身逻辑决定的)。mMinWidth
是可以设置的,它对应的属性Android:minWidth
或者View
的setMinimumWidth
的值,如果不指定的话,默认值为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
,之前提到过子View
的MeasureSpec
的转化会受到父容器的影响就是在这里体现,第二个参数是已经使用的空间,具体来说就是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_PARENT
或WRAP_CONTENT
,则子视图的尺寸可能是0(如果View.sUseZeroUnspecifiedMeasureSpec
为true
)或父视图的可用尺寸,模式是未指定模式。
文章到这里就结束了!