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

[复制链接]
发表于 2025-12-11 08:08:31 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
1.View的工作流程入口

1.1DecorView被加载到Window中

看到这里你对Activity的构成有肯定的相识,每个 Activity 都有一个与之关联的 Window 对象,而 DecorView 是这个 Window 的根视图。当DecorView被创建以及加载资源的时间,此时它的内容还无法举行体现,由于还没有加载到Window当中。接下来就看看是怎样加载的。

  • DecorView被加载到Window当中
当我们调用Activity的startActivity方法的时间,终极调用的是ActivityThread的handleLaunchActivity方法来创建Activity
  1. public Activity handleLaunchActivity(ActivityClientRecord r,
  2.                                      PendingTransactionActions pendingActions, Intent customIntent) {
  3. .......
  4.     //通过调用 performLaunchActivity(r, customIntent) 方法尝试启动一个 Activity。这个方法会创建 Activity 实例,并调用其 onCreate 方法,完成 DecorView 的创建。这个方法返回一个 Activity 对象,或者在失败时返回 null
  5.     final Activity a = performLaunchActivity(r, customIntent);
  6.     if (a != null) {
  7.         //为 ActivityClientRecord 对象 r 设置一个新的 Configuration 对象,该对象包含当前设备的配置信息
  8.         r.createdConfig = new Configuration(mConfigurationController.getConfiguration());
  9.         //用于报告 Activity 的大小配置,可能用于调试或日志日志记录
  10.         reportSizeConfigurations(r);
  11.         //如果 r.activity.mFinished 为 false(即 Activity 没有完成)且 pendingActions 不为 null,则设置 pendingActions 的一些状态
  12.         if (!r.activity.mFinished && pendingActions != null) {
  13.             //设置旧的状态;标记需要恢复实例状态;标记需要调用 onPostCreate 方法
  14.             pendingActions.setOldState(r.state);
  15.             pendingActions.setRestoreInstanceState(true);
  16.             pendingActions.setCallOnPostCreate(true);
  17.         }
  18.     } else {
  19.         // 调用 ActivityClient.getInstance().finishActivity 方法,通知活动管理器停止当前的 Activity。r.token用于标识当前的Activity;Activity.RESULT_CANCELED结果码,标识操作被取消或失败
  20.         ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED,
  21.                 null /* resultData */, Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
  22.     }
  23.     return a;
  24. }
复制代码
上面的performLaunchActivity方法是用来创建Activity,内里会调用Activity的onCreate方法,从而完成DecorView的创建,来看看源码:
  1. private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  2.                         ......
  3.             //将Activity实例设置到ActivityClientRecord中
  4.             r.activity = activity;
  5.     //检查Activity是否是持久化的。持久化的Activity可以在系统配置更改(如屏幕旋转)时保存和恢复状态
  6.             if (r.isPersistable()) {
  7.                 //如果Activity是持久化的,调用Activity的onCreate方法,并传递临时状态和持久状态
  8.                 mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
  9.             } else {
  10.                 //否则不传入持久状态
  11.                 mInstrumentation.callActivityOnCreate(activity, r.state);
  12.             }
  13.             if (!activity.mCalled) {
  14.                 throw new SuperNotCalledException(
  15.                     "Activity " + r.intent.getComponent().toShortString() +
  16.                     " did not call through to super.onCreate()");
  17.             }
  18.             r.mLastReportedWindowingMode = config.windowConfiguration.getWindowingMode();
  19.         }
  20.         r.setState(ON_CREATE);
  21.     } catch (SuperNotCalledException e) {
  22.         throw e;
  23.     } catch (Exception e) {
  24.         if (!mInstrumentation.onException(activity, e)) {
  25.             throw new RuntimeException(
  26.                 "Unable to start activity " + component
  27.                 + ": " + e.toString(), e);
  28.         }
  29.     }
  30.     return activity;
  31. }
复制代码
我们看到会调用mInstrumentation.callActivityOnCreate方法,实在无论调用的是哪个方法,内里都是一样的,只是多了一个恒久状态参数:
  1. public void callActivityOnCreate(Activity activity, Bundle icicle,
  2.         PersistableBundle persistentState) {
  3.     //在调用Activity的onCreate方法之前执行的准备工作。这包括设置Activity的一些内部状态,或者进行其他必要的初始化操作
  4.     prePerformCreate(activity);
  5.     //调用Activity的performCreate方法,传递临时状态icicle和持久化状态persistentState。这个方法实际上会调用Activity的onCreate方法,并处理状态恢复
  6.     activity.performCreate(icicle, persistentState);
  7.     //在Activity的onCreate方法执行之后执行的后续工作。这包括进一步的初始化操作或者状态设置
  8.     postPerformCreate(activity);
  9. }
复制代码
performCreate方法终极调用到了Activity的onCreate回调方法,onCreate方法中的setContentView方法就创建得到了DecorView,以是onCreate方法完成了DecorView的创建。
同理,onResume方法也是在ActivityThread中的handleResumeActivity方法中被调用的,我们之条件到的DecorView就是在这个方法里被添加进Window中的,我们接下来看这个handleResumeActivity的源码
  1. @Override
  2. public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
  3.         boolean isForward, boolean shouldSendCompatFakeFocus, String reason) {
  4.     ......
  5.     //检查Activity的窗口是否已经创建,Activity是否未完成,以及Activity是否将可见
  6.     if (r.window == null && !a.mFinished && willBeVisible) {
  7.         r.window = r.activity.getWindow();
  8.         ////获取到了DecorView对象
  9.         View decor = r.window.getDecorView();
  10.         decor.setVisibility(View.INVISIBLE);
  11.         //获得了WindowManager对象,是用来将DecorView添加到Window中的
  12.         ViewManager wm = a.getWindowManager();
  13.         //设置窗口布局等等
  14.         WindowManager.LayoutParams l = r.window.getAttributes();
  15.         a.mDecor = decor;
  16.         l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
  17.         l.softInputMode |= forwardBit;
  18.         //如果窗口被标记为保留,执行相关操作
  19.         if (r.mPreserveWindow) {
  20.             //标记窗口已被添加
  21.             a.mWindowAdded = true;
  22.             //重置保留标记
  23.             r.mPreserveWindow = false;
  24.             ViewRootImpl impl = decor.getViewRootImpl();
  25.             if (impl != null) {
  26.                 //通知视图系统窗口已被重建
  27.                 impl.notifyChildRebuilt();
  28.             }
  29.         }
  30.         //如果Activity从客户端可见
  31.         if (a.mVisibleFromClient) {
  32.             //如果窗口尚未添加,将其添加到窗口管理器
  33.             if (!a.mWindowAdded) {
  34.                 a.mWindowAdded = true;
  35.                 wm.addView(decor, l);//1
  36.             } else {
  37.                 //如果窗口已添加,通知Activity窗口属性已更改
  38.                 a.onWindowAttributesChanged(l);
  39.             }
  40.         }
  41.                 ......
  42. }
复制代码
在上面我们获取到了WindowManager,WindowManager是一个接口并继续于ViewManager,之后调用了WindowManager的addView方法,WindowManager的实现类是WindowManagerImpl,以是实际调用的是WindowManagerImpl的addView方法
  1. public final class WindowManagerImpl implements WindowManager {
  2.     ////表示该字段(mGlobal)不应该被应用程序代码直接使用,它是供系统内部使用的
  3.     @UnsupportedAppUsage
  4.     //声明了一个WindowManagerGlobal类型的私有成员变量mGlobal,并使用WindowManagerGlobal.getInstance()方法来初始化它。WindowManagerGlobal是一个全局的单例类,提供全局的窗口管理功能
  5.     private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
  6.     ......
  7.     @Override
  8.     public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
  9.         applyTokens(params);
  10.         mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
  11.                 mContext.getUserId());
  12.     }
  13.     ......
  14. }
复制代码
调用了WindowManagerGlobal的addView方法
  1. public void addView(View view, ViewGroup.LayoutParams params,
  2.         Display display, Window parentWindow, int userId) {
  3.         ......
  4.     //
  5.     ViewRootImpl root;
  6.     View panelParentView = null;
  7.     //确保以下代码块在同一时间只能被一个线程执行,防止多线程并发导致的数据不一致问题
  8.     synchronized (mLock) {
  9.         //说明这不是一个无窗口的会话,创建一个普通的ViewRootImpl实例
  10.         if (windowlessSession == null) {
  11.             root = new ViewRootImpl(view.getContext(), display);
  12.         //如果windowlessSession不为null,说明这是一个无窗口的会话,创建一个ViewRootImpl实例,并传入windowlessSession和WindowlessWindowLayout
  13.         } else {
  14.             root = new ViewRootImpl(view.getContext(), display,
  15.                     windowlessSession, new WindowlessWindowLayout());
  16.         }
  17.                 //为传入的view设置布局参数
  18.         view.setLayoutParams(wparams);
  19.                 //将view添加到mViews列表中,这个列表管理所有的视图
  20.         mViews.add(view);
  21.         //将root添加到mRoots列表中,这个列表管理所有的ViewRootImpl实例
  22.         mRoots.add(root);
  23.         //将wparams添加到mParams列表中,这个列表管理所有的布局参数
  24.         mParams.add(wparams);
  25.         //调用root的setView方法,将view设置到root中,并传递布局参数、父视图和用户ID。这一步会启动视图的绘制流程
  26.         try {
  27.             root.setView(view, wparams, panelParentView, userId);
  28.         } catch (RuntimeException e) {
  29.             final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
  30.             if (viewIndex >= 0) {
  31.                 removeViewLocked(viewIndex, true);
  32.             }
  33.             throw e;
  34.         }
  35.     }
  36. }
复制代码
上面调用了setView方法将DecorView传进去,就如许将DecorView加载到了Window当中。
1.2ViewRootImpl的performTraversals方法

上面我们提到此时将DecorView加载到了Window当中,但此时界面并不会完全体现出来,由于View的流程并没有实行完,还需颠末measure、layout以及draw才会将View绘制出来。怎样举行绘制的呢?
在ViewRootImpl尚有一个方法performTraversals,这个方法使ViewTree开启了View的工作流程:
  1. private void performTraversals() {
  2.                         ....
  3.                 childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
  4.                 childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,lp.privateFlags);
  5.                     performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//1
  6.                     int width = host.getMeasuredWidth();
  7.                     int height = host.getMeasuredHeight();
  8.                     boolean measureAgain = false;
  9.                     if (lp.horizontalWeight > 0.0f) {
  10.                         width += (int) ((mWidth - width) * lp.horizontalWeight);
  11.                         childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
  12.                                 MeasureSpec.EXACTLY);
  13.                         measureAgain = true;
  14.                     }
  15.                     if (lp.verticalWeight > 0.0f) {
  16.                         height += (int) ((mHeight - height) * lp.verticalWeight);
  17.                         childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
  18.                                 MeasureSpec.EXACTLY);
  19.                         measureAgain = true;
  20.                     }
  21.                     if (measureAgain) {
  22.                         if (DEBUG_LAYOUT) Log.v(mTag,
  23.                                 "And hey let's measure once more: width=" + width
  24.                                 + " height=" + height);
  25.                         performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  26.                     }
  27.                     layoutRequested = true;
  28.                 }
  29.             }
  30.         } else {
  31.             maybeHandleWindowMove(frame);
  32.         }
  33.         if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
  34.             prepareSurfaces();
  35.         }
  36.         final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
  37.         boolean triggerGlobalLayoutListener = didLayout
  38.                 || mAttachInfo.mRecomputeGlobalAttributes;
  39.         if (didLayout) {
  40.             performLayout(lp, mWidth, mHeight);//2
  41.             if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
  42.                 host.getLocationInWindow(mTmpLocation);
  43.                 mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
  44.                         mTmpLocation[0] + host.mRight - host.mLeft,
  45.                         mTmpLocation[1] + host.mBottom - host.mTop);
  46.                 host.gatherTransparentRegion(mTransparentRegion);
  47.                 if (mTranslator != null) {
  48.                     mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
  49.                 }
  50.                 if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
  51.                     mPreviousTransparentRegion.set(mTransparentRegion);
  52.                     mFullRedrawNeeded = true;
  53.                     SurfaceControl sc = getSurfaceControl();
  54.                     if (sc.isValid()) {
  55.                         mTransaction.setTransparentRegionHint(sc, mTransparentRegion).apply();
  56.                     }
  57.                 }
  58.             }
  59.             if (DBG) {
  60.                 System.out.println("======================================");
  61.                 System.out.println("performTraversals -- after setFrame");
  62.                 host.debug();
  63.             }
  64.         }
  65.        .....
  66.            .....       
  67.        .....
  68.         if (!isViewVisible) {
  69.             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
  70.                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
  71.                     mPendingTransitions.get(i).endChangingAnimations();
  72.                 }
  73.                 mPendingTransitions.clear();
  74.             }
  75.             if (mSyncBufferCallback != null) {
  76.                 mSyncBufferCallback.onBufferReady(null);
  77.             }
  78.         } else if (cancelAndRedraw) {
  79.             scheduleTraversals();
  80.         } else {
  81.             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
  82.                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
  83.                     mPendingTransitions.get(i).startChangingAnimations();
  84.                 }
  85.                 mPendingTransitions.clear();
  86.             }
  87.             if (!performDraw() && mSyncBufferCallback != null) {//3
  88.                 mSyncBufferCallback.onBufferReady(null);
  89.             }
  90.         }
  91.                 ....
  92.     }
复制代码
这个方法比力长,我们仍然截取我们必要的部分,可以看到在表明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终极的宽高。
  1. public static class MeasureSpec {
  2.     //是一个位移量,用于确定测量模式在MeasureSpec整数值中的位置,即模式需要左移30位
  3.     private static final int MODE_SHIFT = 30;
  4.     //是一个掩码,用于从MeasureSpec整数值中提取测量模式,0x3二进制为11,左移30为正好占据32整数的高位
  5.     private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
  6.     //这是一个自定义的注解(@IntDef),用于定义测量模式的合法值:UNSPECIFIED、EXACTLY和AT_MOST
  7.     @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
  8.     @Retention(RetentionPolicy.SOURCE)
  9.     public @interface MeasureSpecMode {}
  10.     //三种测量模式
  11.     public static final int UNSPECIFIED = 0 << MODE_SHIFT;
  12.     public static final int EXACTLY     = 1 << MODE_SHIFT;
  13.     public static final int AT_MOST     = 2 << MODE_SHIFT;
  14.     //创建一个MeasureSpec整数值,它结合了给定的尺寸(size)和模式(mode)
  15.     public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << View.MeasureSpec.MODE_SHIFT) - 1) int size,
  16.                                       @MeasureSpecMode int mode) {
  17.         //如果sUseBrokenMakeMeasureSpec为true,则简单地将尺寸和模式相加(这是一种不推荐的做法,因为它可能导致不正确的行为)
  18.         if (sUseBrokenMakeMeasureSpec) {
  19.             return size + mode;
  20.         } else {
  21.             //使用位运算将尺寸和模式组合成一个整数值
  22.             return (size & ~MODE_MASK) | (mode & MODE_MASK);
  23.         }
  24.     }
  25.     //创建一个安全的MeasureSpec值。如果sUseZeroUnspecifiedMeasureSpec为true且模式为UNSPECIFIED,则返回0;否则,调用makeMeasureSpec(size, mode)
  26.     @UnsupportedAppUsage
  27.     public static int makeSafeMeasureSpec(int size, int mode) {
  28.         if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
  29.             return 0;
  30.         }
  31.         return makeMeasureSpec(size, mode);
  32.     }
  33.     //从给定的MeasureSpec值中提取测量模式
  34.     @MeasureSpecMode
  35.     public static int getMode(int measureSpec) {
  36.         return (measureSpec & MODE_MASK);
  37.     }
  38.     //从给定的MeasureSpec值中提取尺寸
  39.     public static int getSize(int measureSpec) {
  40.         return (measureSpec & ~MODE_MASK);
  41.     }
  42.         //调整给定的MeasureSpec值,根据delta值修改尺寸。如果模式是UNSPECIFIED,则不进行调整。如果调整后的尺寸为负数,则记录错误并将尺寸设置为
  43.     static int adjust(int measureSpec, int delta) {
  44.         final int mode = getMode(measureSpec);
  45.         int size = getSize(measureSpec);
  46.         if (mode == UNSPECIFIED) {
  47.             return makeMeasureSpec(size, UNSPECIFIED);
  48.         }
  49.         size += delta;
  50.         if (size < 0) {
  51.             Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
  52.                     ") spec: " + toString(measureSpec) + " delta: " + delta);
  53.             size = 0;
  54.         }
  55.         return makeMeasureSpec(size, mode);
  56.     }
  57.     public static String toString(int measureSpec) {
  58.         int mode = getMode(measureSpec);
  59.         int size = getSize(measureSpec);
  60.         StringBuilder sb = new StringBuilder("MeasureSpec: ");
  61.         if (mode == UNSPECIFIED)
  62.             sb.append("UNSPECIFIED ");
  63.         else if (mode == EXACTLY)
  64.             sb.append("EXACTLY ");
  65.         else if (mode == AT_MOST)
  66.             sb.append("AT_MOST ");
  67.         else
  68.             sb.append(mode).append(" ");
  69.         sb.append(size);
  70.         return sb.toString();
  71.     }
  72. }
复制代码
根据常量可以看出它代表了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方法:
  1. //调用getRootMeasureSpec方法来创建子视图的宽度测量规格。这个方法会根据父容器的尺寸、子视图的布局参数和私有标志来确定子视图的宽度测量规格
  2. childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
  3. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
  4.                         lp.privateFlags);
  5. //调用performMeasure方法来执行子视图的测量操作。这个方法会使用前面创建的宽度和高度测量规格来确定子视图的尺寸
  6. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
复制代码
在表明1处调用了getRootMeasureSpec方法,看看他都做了什么:
  1. //windowSize指的是窗口的尺寸
  2. private static int getRootMeasureSpec(int windowSize, int measurement, int privateFlags) {
  3.     int measureSpec;
  4.     //这行代码检查 privateFlags 是否包含 PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT 标志。如果包含,rootDimension 被设置为 MATCH_PARENT,否则使用 measurement 的值
  5.     final int rootDimension = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
  6.             ? MATCH_PARENT : measurement;
  7.     switch (rootDimension) {
  8.         //如果 rootDimension 是 MATCH_PARENT,表示根视图想要填满父容器的大小。因此,measureSpec 被设置为 windowSize 和 MeasureSpec.EXACTLY 模式,强制根视图的大小为窗口的大小
  9.         case ViewGroup.LayoutParams.MATCH_PARENT:
  10.             measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
  11.             break;
  12.         //如果 rootDimension 是 WRAP_CONTENT,表示根视图想要包裹其内容,但窗口可以调整大小。因此,measureSpec 被设置为 windowSize 和 MeasureSpec.AT_MOST 模式,为根视图设置最大尺寸
  13.         case ViewGroup.LayoutParams.WRAP_CONTENT:
  14.             measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
  15.             break;
  16.         //如果 rootDimension 是具体的尺寸值,这意味着根视图想要一个确切的大小。因此,measureSpec 被设置为 rootDimension 和 MeasureSpec.EXACTLY 模式,强制根视图的大小为 rootDimension 指定的大小
  17.         default:
  18.             measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
  19.             break;
  20.     }
  21.     return measureSpec;
  22. }
复制代码
windowSize指的是窗口的尺寸,以是对于DecorView来说,它的MeasureSpec由自身的LayoutParams和窗口的尺寸决定,这一点和平凡的View差别。
3.View的measure流程

measure用来丈量View的宽和高。他的流程分别为View的measure流程和ViewGroup的measure流程。
3.1View的measure流程

先来看看View的onMeasure方法:
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2.     setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  3.     getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  4. }
复制代码
setMeasuredDimension方法用于设置 View 的丈量尺寸。具体而言,它用于设置 View 在举行丈量过程中盘算得出的宽度和高度。当一个 View 被丈量时,体系会调用其 onMeasure() 方法来盘算它的宽度和高度。在 onMeasure() 方法内部,会调用 setMeasureDimension() 方法来设置丈量尺寸,将盘算得出的宽度和高度生存起来。再来看看getDefaultSize方法:
  1. public static int getDefaultSize(int size, int measureSpec) {
  2.         int result = size;
  3.         int specMode = MeasureSpec.getMode(measureSpec);
  4.         int specSize = MeasureSpec.getSize(measureSpec);
  5.         switch (specMode) {
  6.         //如果模式是UNSPECIFIED,表示父视图对子视图的尺寸没有具体要求,因此结果保持为建议的尺寸size
  7.         case MeasureSpec.UNSPECIFIED:
  8.             result = size;
  9.             break;
  10.         //如果模式是AT_MOST或EXACTLY,表示父视图对子视图的尺寸有具体的要求,因此结果设置为specSize,即测量规格中指定的尺寸
  11.         case MeasureSpec.AT_MOST:
  12.         case MeasureSpec.EXACTLY:
  13.             result = specSize;
  14.             break;
  15.         }
  16.         return result;
  17.     }
复制代码
根据上面我们知道差别模式下的返回值,也就是说,对于一个直接继续于View的自界说View来说,它的wrap_content与match_parent属性的效果是一样的。因此假如要实现自界说View的wrap_content,则必要重写onMeasure方法,并对自界说View的wrap_content属性举行处理处罚。我们看到在UNSPECIFIED模式下返回的是第一个参数size的值,size的值由onMeasure方法看来是通过getSuggestedMinimumWidth方法大概getSuggestedMinimumHeight方法获取的,接下来就来看看这个方法做了什么:
  1. protected int getSuggestedMinimumWidth() {
  2.     return (mBackground == null) ? mMinWidth : max(mMinWidth,   mBackground.getMinimumWidth());
  3. }
复制代码
假如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方法:
  1. protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
  2.         final int size = mChildrenCount;
  3.         final View[] children = mChildren;
  4.         for (int i = 0; i < size; ++i) {
  5.             final View child = children[i];
  6.             if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
  7.                 measureChild(child, widthMeasureSpec, heightMeasureSpec);
  8.             }
  9.         }
  10.     }
复制代码
  1. protected void measureChild(View child, int parentWidthMeasureSpec,
  2.         int parentHeightMeasureSpec) {
  3.     //获得子元素的LayoutParams属性
  4.         final LayoutParams lp = child.getLayoutParams();
  5.         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  6.                 mPaddingLeft + mPaddingRight, lp.width);
  7.         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  8.                 mPaddingTop + mPaddingBottom, lp.height);
  9.         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  10.     }
复制代码
一个一个遍历子元素,具体得到子View的MeasureSpec参数是在measureChild方法中,通过getChildMeasureSpec得到了子View的参数,传入的第一个参数是父容器的MeasureSpec,之条件到过子View的MeasureSpec的转化会受到父容器的影响就是在这里体现,第二个参数是已经利用的空间,具体来说就是Padding参数(尚有个方法是也思量到Margin参数的),第三个参数是子View的布局参数LayoutParams。,接下来就看看是怎样举行转换的:
  1. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  2.     int specMode = MeasureSpec.getMode(spec);
  3.     int specSize = MeasureSpec.getSize(spec);
  4.         //计算父视图的实际可用尺寸,考虑到内边距。
  5.     int size = Math.max(0, specSize - padding);
  6.     int resultSize = 0;
  7.     int resultMode = 0;
  8.     switch (specMode) {
  9.         case MeasureSpec.EXACTLY:
  10.             if (childDimension >= 0) {
  11.                 resultSize = childDimension;
  12.                 resultMode = MeasureSpec.EXACTLY;
  13.             } else if (childDimension == LayoutParams.MATCH_PARENT) {
  14.                 resultSize = size;
  15.                 resultMode = MeasureSpec.EXACTLY;
  16.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  17.                 resultSize = size;
  18.                 resultMode = MeasureSpec.AT_MOST;
  19.             }
  20.             break;
  21.         case MeasureSpec.AT_MOST:
  22.             if (childDimension >= 0) {
  23.                 resultSize = childDimension;
  24.                 resultMode = MeasureSpec.EXACTLY;
  25.             } else if (childDimension == LayoutParams.MATCH_PARENT) {
  26.                 resultSize = size;
  27.                 resultMode = MeasureSpec.AT_MOST;
  28.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  29.                 resultSize = size;
  30.                 resultMode = MeasureSpec.AT_MOST;
  31.             }
  32.             break;
  33.         case MeasureSpec.UNSPECIFIED:
  34.             if (childDimension >= 0) {
  35.                 resultSize = childDimension;
  36.                 resultMode = MeasureSpec.EXACTLY;
  37.             } else if (childDimension == LayoutParams.MATCH_PARENT) {
  38.                 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
  39.                 resultMode = MeasureSpec.UNSPECIFIED;
  40.             } else if (childDimension == LayoutParams.WRAP_CONTENT)
  41.                 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
  42.                 resultMode = MeasureSpec.UNSPECIFIED;
  43.             }
  44.             break;
  45.     }
  46.     return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
  47. }
复制代码

  • 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)或父视图的可用尺寸,模式是未指定模式。

文章到这里就竣事了!
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表