会讲到自定义View的时候为什么要重写onMeasure()方法。
一 用户的R.layout.activty_xxx.xml加载到window的流程
首先系统会创建一个顶层容器DecorView,是继承framelayout的viewgroup。 DecorView是Phonewindow.java持有的一个实例。
在系统内部帮我们初始化好了DecorView对象, 然后根据activity主题的不同而加载不同的screen_xxx.xml, 比如NoActionBar, DarkActionBar等。
不管是哪个screen_xxx.xml, 都会有一个一样的Framelayout, id是 com.android.internal.R.id.content
在顶层布局中加载基础布局Framelayout,
开发者通过activity的setContentView(), 把activity_xxx_layout.xml添加到ContentView中。再将ContentView添加到基础布局中的Framelayout中。
在这个路径下面:
C:\Users\xxx\AppData\Local\Android\sdk\platforms\android-28\data\res\layout\
有几个screen的xml文件,分别是:
screen.xml
screen_action_bar.xml
screen_custom_title.xml
screen_progress.xml
screen_simple.xml
screen_simple_overlay_action_mode.xml
screen_swipe_dismiss.xml
screen_title.xml
screen_title_icons.xml
screen_toolbar.xml
最终加载哪里一个screen文件,是根据Activty的theme来的。
以screen_simple为例:
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content
protected ViewGroup generateLayout(DecorView decor) {
...
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
}
我们的acitvity_xxx_layout.xml是加载到srceen.xml的FrameLayout里面的。
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
二, 绘制view的流程:
1. 在主线程ActivityThread中, 接到resume acticty的hanlder message, 也就是 RESUME_ACTIVITY。
2. 在ActivityThread.handleResumeActivity()方法中调用,
ViewManager wm = a.getWindowManager();
wm.addView(decor, l);
3. 最终又调用到windowManagerGlobal.addView();
4. 在windowManagerGlobal里面生产ViewRootImpl. 添加顶层view, 也就是decor view。
5. 最后在ViewRootImpl里进行三大步骤。
关于onMeasure(), 必须搞清楚MeasureSpec的含义。
View = 模式 + 尺寸->MeasureSpec 32位int值
00000000000000000000000000000000
MODE_MASK : 11000000000000000000000000000000
~MODE_MASK: 00111111111111111111111111111111
SpecMode(前2位) + SpecSize(后30)
public static final int UNSPECIFIED = 0 << MODE_SHIFT; 00000000000000000000000000000000
父容器不对View做任何限制,系统内部使用
public static final int EXACTLY = 1 << MODE_SHIFT; 01000000000000000000000000000000
父容器检测出View的大小,Vew的大小就是SpecSize LayoutPamras match_parent 固定大小
public static final int AT_MOST = 2 << MODE_SHIFT; 10000000000000000000000000000000
父容器指定一个可用大小,View的大小不能超过这个值,LayoutPamras wrap_content
mode + size --> MeasureSpec
MeasureSpec = mode + size
以FrameLayout的onMeasure为例。如果FrameLayout的layout_width和layout_height都是wrap_content.
那么FrameLayout需要知道子控件的最大宽和最大高, 才能确定FrameLayout自身的宽高。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
//确定子控件的最大宽和最大高
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
//还有一些padding的设置
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
//以及最大最小宽高的设置
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
//最后通过setMeasuredDimension确定自己的真实宽高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
很重要的一点:
ViewGroup measure --> onMeasure(测量子控件的宽高)--> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的宽高)
View measure --> onMeasure --> setMeasuredDimension -->setMeasuredDimensionRaw(保存自己的宽高)
在View的默认OnMeasure()方法中,getDefaultSize()获取到的值就是自身的宽高,match_parent == wrap_content 的情况是一样的,所以自定义View时需要重写onMeasure()。以动态确定你想要的Size.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST://match_parent == wrap_content 的情况是一样的,所以自定义View时需要重写onMeasure()。
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}