自定义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是如何显示,如何测量,布局?
本文深入剖析了Android中视图的绘制流程,从Activity的setContentView方法开始,逐步介绍了视图的创建、布局和绘制过程。重点分析了PhoneWindow中的setContentView方法,解释了如何构建根视图和如何将开发者定义的布局添加到视图层次结构中。
414

被折叠的 条评论
为什么被折叠?



