Android鬼点子-探索View的实例化过程


       本文主要介绍了一个View如何被实例化的。


       在一个Activity中,我们通常使用setContentView(R.layout.XX);或者setContentView(mView);来设置一个布局。

       咱们来先说说第一种,从一个xml布局文件来给Activity设置布局。这个方法是在PhoneWindow类里面实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
}
}

       首先判断父容器是不是空,如果是空的生成Decor;如果不是空的就清空。Decor是页面的顶层容器,它包含一个子容器t。这个子容器里有两个FrameLayout子元素,分别是标题栏和内容栏,这里的内容栏就是setContentView()方法载入的布局界面。

       如果是第二种setContentView(View view)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}

       前面的和第一种的逻辑类似。然后判断是不是有过场动画,如果有生成一个Scene,如果没有直接生成一个View。Scene保存了view层级的状态,并保存了层级中所有view的属性值。然后在transitionTo(newScene);中会对View进行初始化。

1
2
3
4
5
6
7
8
private void transitionTo(Scene scene) {
if (mContentScene == null) {
scene.enter();
} else {
mTransitionManager.transitionTo(scene);
}
mContentScene = scene;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void enter() {
// Apply layout change, if any
if (mLayoutId > 0 || mLayout != null) {
// empty out parent container before adding to it
getSceneRoot().removeAllViews();
if (mLayoutId > 0) {
LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
} else {
mSceneRoot.addView(mLayout);
}
}
// Notify next scene that it is entering. Subclasses may override to configure scene.
if (mEnterAction != null) {
mEnterAction.run();
}
setCurrentScene(mSceneRoot, this);
}

       在scene.enter()判断了是直接传入了一个View还是一个LayoutID,如果是LayoutID,这里最后还是走到了LayoutInflater.inflate()如果没有过场动画,这里还是用LayoutInflater.inflate()实例化一个View,然后把这个View加入父布局中。下面来具体说说LayoutInflater.inflate()。


获取LayoutInflater的方法有如下三种:

LayoutInflater inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

LayoutInflater inflater = LayoutInflater.from(context); (该方法实质就是第一种方法,可参考源代码)

LayoutInflater inflater = getLayoutInflater();(在Activity中可以使用,实际上是View子类下window的一个函数)

我们最常用的便是LayoutInflater的inflate方法,这个方法重载了四种调用方式,分别为:

public View inflate(int resource, ViewGroup root)

public View inflate(int resource, ViewGroup root, boolean attachToRoot)

public View inflate(XmlPullParser parser, ViewGroup root) 
    注:XmlPullParser是final XmlResourceParser parser = res.getLayout(resource);

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

inflate方法有三个参数,分别是

1.resource 布局的资源id

2.root 填充的根视图

3.attachToRoot 是否将载入的视图绑定到根视图中


       那么attachToRoot是什么作用呢? 他会自动把layout加到View hierarchy中, 不需要手动调用root.addView。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}

       其中下面的代码说明会了attachToRoot的作用,分别是是否add进root和返回的是传入的root还是使用LayoutID实例化的tempView。

1
2
3
4
5
6
7
8
9
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}

       注意在Adapter.getView里面不用手动调用root.addView是因为Adapter已经帮我们做了,所以实例化的时候要设置attachToRoot=false。

       LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的。里面调用了createViewFromTag()这个方法,并把节点名和参数传了进去。它是用于根据节点名来创建View对象的。在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。只是创建出了一个根布局的实例而已,接下来会调用rInflate()方法来循环遍历这个根布局下的子元素。

文章目录
|