自定义view的时候我们经常会涉及到如下方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
这里我们就对onMeasure方法参数的由来以及View的绘制过程做一次简单的描述
在这里我们先从Activity.java中的setContentView(int layoutResID)说起
public void setContentView(@LayoutRes int layoutResID) {
//getWindow()返回的是Window抽象类,其具体的实现是PhoneWindow.java
//activity显示的时候会调用attach方法,
//在attach方法中 mWindow = new PhoneWindow(this);
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
在activity的生命周期中,onCreate的方法作为Activity的周期方法之一,但是真正将view绘制上的方法其实是setContentView方法,借由getWindow()我们可以查看到PhoneWindow的setContentView方法
@Override
public void setContentView(int layoutResID) {
//刚开始执行的时候,mContentParent 必然为null,因此必然执行installDecor的方法
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);
}
//通过inflate方法,将layoutResID的layout添加到mContentParent中
else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
当调用setContentView方法的,mContentParent 为null,因此执行installDecor的方法,在installDecor方法中,主要是构建两个view:mContentRoot和mContentParent
接下来,我们分析一下installDecor便可以知道mContentRoot和mContentParent分别代表什么
private void installDecor() {
if (mDecor == null) {
//创建DecorView,DecorView为phonewindow类的内部类,继承自framelayout
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
//通过调用generateLayout方法,来产生activity根viewgroup,并将里面的子view复制给mContentParent
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
}
在installDecor方法中我们重点关注generateLayout方法,该方法主要完成布局文件的产生,即一个activity的根viewgroup的产生
protected ViewGroup generateLayout(DecorView decor) {
//省略部分为根据activity的xml属性设置一些flag,这里不做详细分析
/*layoutResource为关键layout的id,其将根据activity的属性从android系统中选用不同的layout资源作为phonewindow的根layout*/
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
//一般情况下将以此layout创建根viewgroup
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
/*根据activity的属性,我们便可以拿到系统对应的layout资源的id,通过id,我们可以获得 一个view,并在此view添加到decorview中,从而mContentRoot便成为我们的activity的根view,contentParent变成为根view最大的一个子view:即com.android.internal.R.id.content */
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks();
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
mDecor.setWindowBackground(background);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
mDecor.setElevation(mElevation);
mDecor.setClipToOutline(mClipToOutline);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}
通过对上面代码的分析,generateLayout主要根据activity的属性设置,从android系统中选用一个合适的layout作为activity的根布局,这里我们将以screen_title_icons.xml进行分析,然后将此跟布局的layout添加到decorview中,并产生mContentRoot,其代表我们的activity的根view;同时也产生contentParent,其代表view最大的一个子view:即com.android.internal.R.id.content
下面我们来看一下screen_title_icons.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:fitsSystemWindows="true"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Popout bar for action modes -->
<!-- 此处为activity的actionbar -->
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme"/>
<!-- 此处为activity的核心内容区 -->
<RelativeLayout android:id="@android:id/title_container"
style="?android:attr/windowTitleBackgroundStyle"
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize">
<!-- The title background has 9px left padding. -->
<ImageView android:id="@android:id/left_icon"
android:visibility="gone"
android:layout_marginEnd="9dip"
android:layout_width="16dip"
android:layout_height="16dip"
android:scaleType="fitCenter"
android:layout_alignParentStart="true"
android:layout_centerVertical="true" />
<ProgressBar android:id="@+id/progress_circular"
style="?android:attr/progressBarStyleSmallTitle"
android:visibility="gone"
android:max="10000"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_marginStart="6dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- There are 6dip between this and the circular progress on the right, we
also make 6dip (with the -3dip margin_left) to the icon on the left or
the screen left edge if no icon. This also places our left edge 3dip to
the left of the title text left edge. -->
<ProgressBar android:id="@+id/progress_horizontal"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="-3dip"
android:layout_toStartOf="@android:id/progress_circular"
android:layout_toEndOf="@android:id/left_icon"
android:layout_centerVertical="true"
android:visibility="gone"
android:max="10000" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_toStartOf="@id/progress_circular"
android:layout_toEndOf="@android:id/left_icon"
>
<!-- 在app中经常设置的title,就是此处的textview -->
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@null"
android:fadingEdge="horizontal"
android:scrollHorizontally="true"
android:gravity="center_vertical"
android:layout_marginEnd="2dip"
/>
<!-- 2dip between the icon and the title text, if icon is present. -->
<ImageView android:id="@android:id/right_icon"
android:visibility="gone"
android:layout_width="16dip"
android:layout_height="16dip"
android:layout_weight="0"
android:layout_gravity="center_vertical"
android:scaleType="fitCenter"
/>
</LinearLayout>
</RelativeLayout>
<!-- @android:id/content即为我们之前分析的mContentParent,是构建activity的的跟view中最大的一个子view,当app调用setContentView(int layoutid)之后, layoutid的布局文件就会添加到@android:id/content之上-->
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
通过对比系统各种系统xml文件中,每一个通过系统资源获得的layout中,都有@android:id/content这个FrameLayout,因此我们可以一视同仁
接PhoneWindow.java中的setContentView再说,我们刚分析了通过activity的属性去构建一个根View,以及mContentParent和mContentRoot,那么当app通过调用setContentView(int layoutid)之后,是如何将开发者的layout添加到mContentParent中的尼,其实就在PhoneWindow.java中的setContentView的如下方法中
mLayoutInflater.inflate(layoutResID, mContentParent);
先简述下上面的方法,如果调用
inflate(@LayoutRes int resource, @Nullable ViewGroup root)
在我们调用两个参数的inflate的时候,其实最终调用的是
inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
该方法将根据root是否为null,来设置attachToRoot的布尔值,
当root不为null的时候,attachToRoot为rue,即将resource代表的layout添加到root中,添加的过程中resource所指定的的布局的根节点的各个属性都是有效的
当root为null的时候,attachToRoot为false,即表示不将resource表示的资源添加到root中,只是返回了这样的一个view,同时resource表示的资源的宽,高属性将会失效
通过PhoneWindow的setContentView方法,终于将开发者的layout添加到mContentParent中,那这个开发者的layou是如何显示,如何测量,布局?