【Android】View的工作流程——measure
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
)或父视图的可用尺寸,模式是未指定模式。
文章到这里就结束了!
相关文章:
【Android】View的工作流程——measure
1.View的工作流程入口 1.1DecorView被加载到Window中 看到这里你对Activity的构成有一定的了解,每个 Activity 都有一个与之关联的 Window 对象,而 DecorView 是这个 Window 的根视图。当DecorView被创建以及加载资源的时候,此时它的内容还…...
day35—蓝桥杯2024年第16届校赛模拟第二期-T4(最小花费)
【问题描述】 小蓝有一个整数,初始值为 1 ,他可以花费一些代价对这个整数进行变换。 小蓝可以花费 1 的代价将整数增加 1 。 小蓝可以花费 3 的代价将整数增加一个值,这个值是整数的数位中最大的那个(1 到 9)。 小蓝可…...
Java 中 List 接口的学习笔记
1. 什么是 List? 在 Java 中,List 是一个接口,属于 Java Collections Framework。它表示一个有序的集合,可以包含重复元素。List 接口允许通过索引访问元素,提供了多种实现方式,如 ArrayList 和 LinkedLis…...
朗新科技集团如何用云消息队列 RocketMQ 版“快、准、狠”破解业务难题?
作者:邹星宇、刘尧 朗新科技集团:让数字化的世界更美好 朗新科技集团股份有限公司是领先的能源科技企业,长期深耕电力能源领域,通过新一代数字化、人工智能、物联网、电力电子技术等新质生产力,服务城市、产业、生活中…...
hive hms和hs2的sql执行日志分析
HMS日志: 2024-12-02 15:39:20,811 INFO org.apache.hadoop.hive.metastore.HiveMetaStore: [pool-8-thread-114]: 114: source:10.11.17.999 get_all_databases 2024-12-02 15:39:20,812 INFO org.apache.hadoop.hive.metastore.HiveMetaStore.audit: [pool-8-thre…...
Mybatis-plus 多租户插件
前言 本篇主要分析Mybatis-plus 多租户插件,然后根据多租户插件在延伸到其他场景 案例 Mybatis-plus官网对多租户插件已有详细讲解,这里就不在附上使用案例。 源码分析 MybatisPlus官方是由TenantLineInnerInterceptor这个拦截器进行多租户功能处理…...
浅谈新能源汽车感应钥匙一键启动的步骤和特点
随着汽车智能化技术的发展,无钥匙启动系统还可以与其他智能系统进行集成,如智能车载系统、远程控制系统等。这使得车主可以通过智能手机等智能设备远程控制车辆的启动、解锁、上锁等操作,进一步提升了使用的便捷性和智能化水平。新能源汽车…...
012 路由信息协议RIP
路由信息协议RIP 作为度量(Metric)来衡量到达目的网络的距离 RIP是一种基于距离矢量D-V(Distance-Vector)算法的协议,它使用跳数(Hop Count)作为度量(Metric)来衡量到达目的网络的距离。 默认情况下,路由器到与它直接相连网络的跳数为0,因此…...
008.精读《Apache Paimon Docs - Table w/o PK》
文章目录 1. 引言2. 基本概念2.1 定义2.2 使用场景 3. 流式处理3.1 自动小文件合并3.2 流式查询 4. 数据更新4.1 查询4.2 更新4.3 分桶附加表 5 总结 1. 引言 通过本文,上篇我们了解了Apache Paimon 主键表,本期我们将继续学习附加表(Append…...
在 Windows WSL 上部署 Ollama 和大语言模型:从镜像冗余问题看 Docker 最佳实践20241208
🛠️ 在 Windows WSL 上部署 Ollama 和大语言模型:从镜像冗余问题看 Docker 最佳实践 ⭐ 引言 随着大语言模型(LLM)和人工智能技术的迅猛发展,开发者们越来越多地尝试在本地环境中部署模型进行实验。 但部署过程中常…...
微信小程序uni-app+vue3实现局部上下拉刷新和scroll-view动态高度计算
微信小程序uni-appvue3实现局部上下拉刷新和scroll-view动态高度计算 前言 在uni-appvue3项目开发中,经常需要实现列表的局部上下拉刷新功能。由于网上相关教程较少且比较零散,本文将详细介绍如何使用scroll-view组件实现这一功能,包括动态高度计算、下拉刷新、上拉加载等完整…...
网络原理——HTTPS
一、什么是HTTPS 1.1 HTTPS的概念 HTTPS 也是⼀个应用层协议. 是在 HTTP 协议的基础上引入了一个加密层(即HTTP SSL/TLS,SSL、TLS也是一个应用层协议,专门负责加密) HTTP 协议内容都是按照文本的方式明文传输的. 这就导致在传输…...
数据结构之四:堆和二叉树
堆的实现:SData/Heap/heap.c Hera_Yc/bit_C_学习 - 码云 - 开源中国 树 树的概念 树:是一个非线性数据结构,它是由n(n>0)个有限结点组成一个具有层次关系的集合。 把它叫做树是因为它看起来像一棵倒挂的树,也就…...
语音识别flask接口开发
要开发一个flask语音识别接口,首先要解决语音文件在网络中的传输问题,然后选识别算法进行识别 文章目录 1、以二进制文件流方式上次语音2、网页端长连接流式上传语音文件3、语音识别接口 1、以二进制文件流方式上次语音 python服务端代码,以…...
MISRA C2012学习笔记(10)-Rules 8.15
文章目录 8.15 控制流(Control flow)Rule 15.1 不应使用 goto 语句Rule 15.2 goto 语句仅允许跳到在同一函数中声明的稍后位置的标签Rule 15.3 goto 语句引用的标签必须在 goto 语句所在代码块或包含该代码块的上级代码块中声明Rule 15.4 最多只能有一个用于终止循环语句的 bre…...
《深入浅出HTTPS》读书笔记(16):消息验证码算法分类
MAC算法有两种形式,分别是CBC-MAC算法和HMAC算法。 CBC-MAC算法从块密码算法的CBC分组模式演变而来,简单地说就是最后一个密文分组的值就是MAC值。 HMAC(Hash-based Message Authentication Code)算法使用Hash算法作为加密基元&am…...
DAY168内网对抗-基石框架篇单域架构域内应用控制成员组成用户策略信息收集环境搭建
知识点: 1、基石框架篇-单域架构-权限控制-用户和网络 2、基石框架篇-单域架构-环境搭建-准备和加入 3、基石框架篇-单域架构-信息收集-手工和工具 1、工作组(局域网) 将不同的计算机按照功能分别列入不同的工作组。想要访问某个部门的资源,只要在“…...
如何实现 3D GPR的仿真模拟
通过ai问题生成得到的。 1 模型文件-MATLAB 在gprmax中模拟3D GPR工作,可以通过编写一个MATLAB脚本来创建几何文件,并使用gprmax的输入文件(in文件)来设置模拟参数。以下是一个简单的例子程序,展示了如何创建一个3D …...
k8s 之 Deployment
(1)Deployment 作用是确保 Pod 副本数量,能够保证 Pod 数量与期望值一样,会有自恢复功能。简洁地说:具有 水平扩展 / 收缩 功能。 可能好奇的是在 kubernetes 中是谁在执行这些控制器的,它就是 kube-contr…...
现代C++ 6 声明
文章目录 C 中的冲突声明规则1. **对应声明(Corresponding Declarations)**2. **对应函数重载(Corresponding Function Overloads)**3. **对应函数模板重载(Corresponding Function Template Overloads)**4…...
Spark区分应用程序 Application、作业Job、阶段Stage、任务Task
目录 一、Spark核心概念 1、应用程序Application 2、作业Job 3、阶段Stage 4、任务Task 二、示例 一、Spark核心概念 在Apache Spark中,有几个核心概念用于描述应用程序的执行流程和组件,包括应用程序 Application、作业Job、阶段Stage、任务Task…...
【WebRTC】Android SDK使用教学
文章目录 前言PeerConnectionFactoryPeerConnection 前言 最近在学习WebRTC的时候,发现只有JavaScript的API文档,找了很久没有找到Android相关的API文档,所以通过此片文章记录下在Android应用层如何使用WebRTC 本篇文章结合:【W…...
算法-字符串-8.字符串转换整数
一、题目 二、思路解析 1.思路: 依次遍历,查看当前字符是否在规定范围内 2.常用方法: 1.trim(),去字符串的首尾空字符 ss.trim(); 2.substring(beginIndex),截断字符串,得到新的字符串是[1,s.length()-1] ss.substring(1); 3.st…...
普通算法——一维前缀和
一维前缀和 题目链接:https://www.acwing.com/problem/content/797/ 题目描述: 输入一个长度为 n 的整数序列。接下来再输入 m 个询问,每个询问输入一对 l,r。对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。 **什么是…...
【Elasticsearch】ES+MySQL实现迷糊搜索
1. 技术选型 使用 Elasticsearch (ES) 结合 MySQL 进行数据存储和查询,而不是直接从 MySQL 中进行查询,主要是为了弥补传统关系型数据库(如 MySQL)在处理大规模、高并发和复杂搜索查询时的性能瓶颈。具体来说,ES 与 My…...
MacOS编译webRTC源码小tip
简单记录一下,本人在编译webRTC时,碰到了一下比较烦人的问题,在MacOS终端下,搭建科学上网之后,chromium的depot_tools仓库成功拉下来了,紧接着,使用fetch以及gclient sync始终都返回curl相关的网…...
Android显示系统(05)- OpenGL ES - Shader绘制三角形(使用glsl文件)
Android显示系统(02)- OpenGL ES - 概述 Android显示系统(03)- OpenGL ES - GLSurfaceView的使用 Android显示系统(04)- OpenGL ES - Shader绘制三角形 Android显示系统(05)- OpenGL…...
深度学习小麦头检测-基于Faster-RCNN的小麦头检测——附项目源码
比赛描述 为了获得有关全世界麦田的大量准确数据,植物科学家使用“小麦头”(包含谷物的植物上的穗)的图像检测。这些图像用于估计不同品种的小麦头的密度和大小。但是,在室外野外图像中进行准确的小麦头检测可能在视觉上具有挑战性。密集的小麦植株经常重叠,并且风会使照片…...
成像报告撰写格式
成像报告撰写格式 实验人员: 实验时间: 实验地点: 实验目的: 1实验仪器 1.1相机 包括制造商,型号,面阵还是线阵,彩色还是黑白,图像尺寸,光学接口等。 1.2镜头 包…...
【数学建模】线性规划问题及Matlab求解
问题一 题目: 求解下列线性规划问题 解答: 先将题目中求最大值转化为求最小值,则有 我们就可以得到系数列向量: 我们对问题中所给出的不等式约束进行标准化则得到了 就有不等式约束条件下的变系数矩阵和常系数矩阵分别为: 等式…...
C# 事件(Event)
文章目录 前言1、 声明委托2、 声明事件3、 触发事件4、订阅和取消订阅事件5、示例展示示例一:基础的事件使用流程示例二:简单数值变化触发事件示例三:锅炉系统相关事件应用 前言 在 C# 中,事件(Event)是一…...
企业数字化转型:从爆品起步,迈向生态平台
在当今数字化浪潮席卷全球的时代,企业数字化转型已成为必然趋势。然而,这条转型之路该如何走呢? 企业数字化转型的路径设计,绝不仅仅是技术的升级换代,它需要综合考量多方面因素。一方面,要为实现战略目标做…...
Windows 安装 MySQL
1.下载 MySQL 安装包 访问:MySQL :: Download MySQL Installer选择适合的版本。推荐下载 MySQL Installer for Windows,该安装包包含所有必要的组件选择 Windows (x86, 32-bit), MSI Installer 或 Windows (x86, 64-bit), MSI Installer 2.运行安装程序…...
游戏引擎学习第37天
仓库 : https://gitee.com/mrxiao_com/2d_game 回顾目前的进展 一个简单的调试工具——位图加载器,用于加载存储在硬盘上的位图文件。这个工具将文件加载到内存中,并查看文件头部信息,确保其正确性。接着使用位图头中的偏移量来获取像素数据…...
非常简单实用的前后端分离项目-仓库管理系统(Springboot+Vue)part 4
三十三、出入库管理 Header.vue导一下,RecordController加一个 //将入库数据和原有数据相加吧//新增PostMapping("/save")public Result save(RequestBody Record record) {return recordService.save(record) ? Result.success() : Result.fail();} GoodsManage.v…...
知乎Java后台开发面试题及参考答案
请简述 TCP 的三次握手和四次挥手过程。 TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。 三次握手过程 首先,客户端想要建立连接,会发送一个带有 SYN(同步序列号)标志的 TCP 报文段,这个报文段中还包含一个初始序列号(ISN,Initial Sequenc…...
Java中的String类用法详解
1.字符串拆分 可以把一个完整的字符串按照规定的分隔符拆分为若干个子字符串 String[] split(String regex) 将字符串全部拆分 String[] split(String regex,int limit) 将字符串以指定的格式拆分,拆分成limit组 实例:字符串的拆分处理 public class Main4 {public stat…...
mac电脑安装hadoop、hive等大数据组件
背景:用本地的Hadoop测试Java调用cmd命令 2024-12-08 13:48:19,826 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable ls: .: No such file or directory解决方案:…...
DHCP和DNS
DHCP(动态主机配置协议)和DNS(域名系统)是计算机网络中两个重要的协议,它们在网络的管理和使用中发挥着关键作用。 DHCP(动态主机配置协议) 基本功能 自动分配IP地址:DHCP允许网…...
Postman安装使用教程
Postman(接口测试工具) ①、介绍 Postman是一款支持http协议的接口调试与测试工具,它不仅可以调试简单的css、html、脚本等简单的网页基本信息,还可以发送几乎所有类型的HTTP请求。 ②、安装 Ⅰ、运行安装包/官网直搜 Ⅱ、创建…...
剖析千益畅行,共享旅游-卡,合规运营与技术赋能双驱下的旅游新篇
在数字化浪潮席卷各行各业的当下,旅游产业与共享经济模式深度融合,催生出旅游卡这类新兴产品。然而,市场乱象丛生,诸多打着 “共享” 幌子的旅游卡弊病百出,让从业者与消费者都深陷困扰。今天,咱们聚焦技术…...
信创改造-达梦数据库配置项 dm.ini 优化
设置模式:兼容MySQL,COMPATIBLE_MODE 4 内存占比:90%,MAX_OS_MEMORY 90 目标内存:2G(不影响申请内存超过2G,但这部分内存不会回收),MEMORY_TARGET 2000 参考 https:…...
docker入门 自记录
1.先自己下载离线bao .tar 或者 自己pull docker pull xxx 如果遇到网络问题就换源 2.之后run一个docker 后面是映射本地路径 sudo docker run -it --name ultralytics_241124 --gpus all --shm-size 8G -v /home/oppenheim/detect/train241204/docker:/home/docker ultralyti…...
Axure设计之动态图表——排名图(中继器)
粉丝问我可不可以用中继器做条形图,而且是要做成自动增长的排名图表。所以现在教大家怎么用axure来制作制作排名图。 这个原型制作完成之后,后期有类似的功能,直接拿过去使用也比较简单,基本只需要修改中继器数据就可以了。喜欢、…...
在Java中几种常用数据压缩算法的实现及其优劣势
在Java中几种常用数据压缩算法的实现及其优劣势 背景:项目需要引入Redis作为缓存组件,需要考虑到Redis的内存占用(机器内存越大,成本越高),因此需要引入数据压缩。 1、介绍 数据压缩是计算机领域中一项重要…...
Mac通过Windows App远程访问windows电脑报错0x104的解决办法
1、远程windows电脑,确保打开 远程访问 2、Mac电脑上的配置: 2.1 新版的windows app远程桌面软件相比之前老的Microsoft Remote Desktop,对于mac来说,不会弹出“是否允许该app查找本地网络设备”,需要手动打开 操作步…...
Spring Boot接口返回统一格式
统一的标准数据格式好处 SpringBoot返回统一的标准数据格式主要有以下几点好处: 增强接口的可读性和可维护性,使得前端开发人员能够更加清晰地理解接口返回的数据结构,从而提高开发效率。 降低前后端耦合度,当后端需要修改返回数…...
小程序入门学习(八)之页面事件
一、下拉刷新新事件 1. 什么是下拉刷新 下拉刷新是移动端的专有名词,指的是通过手指在屏幕上的下拉滑动操作,从而重新加载页面数据的行为。 2. 启用下拉刷新 启用下拉刷新有两种方式: 全局开启下拉刷新:在 app.json 的 window…...
Docker基础【windows环境】
课程内容来自尚硅谷3小时速通Docker教程 1. Docker简介 Docker 通过 Docker Hub 实现一行命令安装应用(镜像)【Nginx,Mysql等】,避免繁琐的部署操作。同时通过轻量级(相对于虚拟机)的容器化的思想&#x…...
【docker】docker compose 和 docker swarm
Docker Compose 和 Docker Swarm 都是 Docker 生态中的工具,但它们有不同的用途和目标。 下面是这两者的主要区别,帮助你理解它们在不同场景中的使用。 1. 用途和目标 Docker Compose: 目标:主要用于在单个机器上定义和运行多个容器应用&a…...