view是如何被加载到界面上的?
创建一个普通的activity
//1.MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//添加布局入口
setContentView(R.layout.activity_main);
}
}
//2.Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
//getWindow()获取的window对象是mWindow,Window类是个抽象类,拥有唯一的实例android.view.PhoneWindow
/* <p>The only existing implementation of this abstract class is
android.view.PhoneWindow, which you should instantiate when needing a Window.
*/
public abstract class Window {
...
}
//3.PhoneWindow.java
//因此到PhoneWindow找到setContentView(int layoutResID)
@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();
}
}
//4. installDecor();
//5. mLayoutInflater.inflate(layoutResID, mContentParent);
//解析传进来的layoutResID和mContentParent, 实际上就是把传进来的mContentParent加载到mContentParent上,mContentParent是一个frameLayout
//4. installDecor();
//4.1创建DevorView
mDecor = generateDecor();
//4.2创建mContentParent
mContentParent = generateLayout(mDecor);
//4.2创建mContentParent
//解析基础控件layoutResource
View in = mLayoutInflater.inflate(layoutResource, null);
//DecorView添加基础控件in
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//添加ID_ANDROID_CONTENT 并且直接返回contentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// layoutResource是一个基础控件ViewGroup
//xref: /frameworks/base/core/res/res/layout/screen_title.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
23 android:orientation="vertical"
24 android:fitsSystemWindows="true">
25 <!-- Popout bar for action modes -->
26 <ViewStub android:id="@+id/action_mode_bar_stub"
27 android:inflatedId="@+id/action_mode_bar"
28 android:layout="@layout/action_mode_bar"
29 android:layout_width="match_parent"
30 android:layout_height="wrap_content"
31 android:theme="?attr/actionBarTheme" />
32 <FrameLayout
33 android:layout_width="match_parent"
34 android:layout_height="?android:attr/windowTitleSize"
35 style="?android:attr/windowTitleBackgroundStyle">
36 <TextView android:id="@android:id/title"
37 style="?android:attr/windowTitleStyle"
38 android:background="@null"
39 android:fadingEdge="horizontal"
40 android:gravity="center_vertical"
41 android:layout_width="match_parent"
42 android:layout_height="match_parent" />
43 </FrameLayout>
44 <FrameLayout android:id="@android:id/content"
45 android:layout_width="match_parent"
46 android:layout_height="0dip"
47 android:layout_weight="1"
48 android:foregroundGravity="fill_horizontal|top"
49 android:foreground="?android:attr/windowContentOverlay" />
50</LinearLayout>
View就这样被加载到界面上了
总结:
* 创建顶层布局容器DecorView
* 在顶层布局中添加基础布局ViewGroup
* 将contentView添加到基础布局容器的FrameLayout中
view是如何绘制的?
-
- ActivityThread.class
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
...
}
- 2.handleLaunchActivity
- 3.handleResumeActivity
- 4.wm.addView(decor, l);
// 4.wm.addView(decor, l);
ViewManager wm = a.getWindowManager();
-> mWindowManager = mWindow.getWindowManager();
//mWindow唯一的实例是 PhoneWindow,因此
-> mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
//因此wm是WindowManagerImpl的实例,在addView中调用了mGlobal.addView()
-> mGlobal.addView(view, params, mDisplay, mParentWindow);
//ViewRootImpl关联view, params, panelParentView,root是ViewRootImpl实例化对象
-> root.setView(view, wparams, panelParentView);
-> requestLayout()
-> checkThread();
-> scheduleTraversals();
-> TraversalRunnable{}
-> doTraversal();
-> performTraversals();
-> performMeasure(childWidthMeasureSpec,childHeightMeasureSpec); //1890 测量
-> performLayout(lp, desiredWindowWidth, desiredWindowHeight); //1931 布局
-> performDraw(); // 2067 绘制
view的绘制流程就是以上这些步骤,现在具体来看下performMeasure(),performLayout(),performDraw()这三步关键的步骤。
- 测量:performMeasure()
- 先引入一个概念MeasureSpec,view的测量规格,是一个32位的二进制整数类型。view的测量按照一定的规则,以下是MeasureSpec的一些规则:
//MeasureSpec.java
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
UNSPECIFIED: 00 0000 0000 0000 0000 0000 0000 0000 00
-> 父容器不对view做任何限制,系统内部使用
EXACTLY: 01 0000 0000 0000 0000 0000 0000 0000 00
-> 父容器检测view的大小,view的大小就是SpecSize ,对应子控件的 LayoutParams.match_parent / 固定大小
AT_MOST: 10 0000 0000 0000 0000 0000 0000 0000 00
-> 父容器指定一个可用大小,view不能超过这个值, LayoutParams.wrap_content
MODE_MASK = 11 0000 0000 0000 0000 0000 0000 0000 00
~MODE_MASK = 00 1111 1111 1111 1111 1111 1111 1111 11
size & ~MODE_MASK = 00 后三十位
model & MODE_MASK = 前两位 0000 0000 0000 0000 0000 0000 0000 00
(size & ~MODE_MASK) | (mode & MODE_MASK); == model(前两位) size(后三十位)
- 继续回到 ViewRootImpl 的1890行 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
-> performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
-> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
-> onMeasure(widthMeasureSpec, heightMeasureSpec);
-> setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
//保存自身的宽和高
-> setMeasuredDimensionRaw(measuredWidth, measuredHeight);
//此处即测量结束,赋值给宽高,并设置标记flags
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
//以上来看view的测量就结束了。但是view是如何测量的,就需要了解传进来的参数childWidthMeasureSpec,childHeightMeasureSpec是怎么得来的
- int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); //1854 ViewRootImpl.java
//mWidth : 窗口容器的宽度
//lp.width: 顶层容器的宽度
getRootMeasureSpec(mWidth, lp.width)
// 因此 DecorView的MeasureSpec由窗口大小和自身LayoutParams决定,遵守以下规则:
// LayoutParams.MATCH_PARENT: 精确模式,窗口大小
// LayoutParams.WRAP_CONTENT: 最大模式,最大为窗口大小
// 固定大小:精确模式,大小为LayoutParams的大小
//实际上是调用DecorView的measure()方法
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//在view的measure()方法内部还调用了 onMeasure(widthMeasureSpec, heightMeasureSpec);
//DecorView的父类是FrameLayout,在FrameLayout的onMeasure中调用了 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//因为是容器viewGroup,所以测量了自身需要在测量child控件
总结: viewGroup : measure -> onMeasure(子控件的宽高) -> setMeasuredDimension() -> setMeasuredDimensionRaw(保存自己的宽高)
view : measure -> onMeasure() -> setMeasuredDimension() -> setMeasuredDimensionRaw(保存自己的宽高)
自定义view不重写onMeasure方法,使用match_parent 和 wrap_content的效果是一样的
- 布局performLayout() ,回到performLayout(lp, desiredWindowWidth, desiredWindowHeight); //1931 ViewRootImpl.java
-> host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
-> setFrame(l, t, r, b);
//调用view.layout确定自身的位置,即确定mLeft,mTop,mRight,mBottom的值
总结:
viewGroup: layout(确定自己的位置,4个点的位置)->onLayout(进行子view的布局)
view: layout(确定自己的位置,4个点的位置)
- performDraw绘制 //2067 ViewRootImpl.java
-> performDraw()
-> draw()
-> drawSoftware()
-> mView.draw(canvas);
-> /*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
总结:
viewGroup:
- 绘制背景drawBackground()
- 绘制自己onDraw()
- 绘制子view dispatchDraw()
- 绘制前景,滚动条等装饰onDrawForeground()
view:
- 绘制背景drawBackground()
- 绘制自己onDraw()
- 绘制前景,滚动条等装饰onDrawForeground()