一直想要深入瞭解一下view的工作原理,現在有時間空出來了,所以就着手準備瞭解一下,首先先看一下LayoutInflater的原理。
相信大家對LayoutInflater一定不陌生,我們在加載佈局的時候通常都會用到這個,一開始對LayoutInflater也不是很瞭解,因爲我們平時用的都是setContentView方法,今天查了一些資料才知道,原來setContentView方法內部也是用LayoutInflater來實現的。
先看看LayoutInflater的使用方法吧,首先需要獲取LayoutInflater的實例:
LayoutInflater layoutInflater = LayoutInflater.from(context);
得到LayoutInflater實例之後,我們就可以使用其中的inflate方法來加載佈局了,如下:
layoutInflater.inflate(resourceId, root);
inflate方法中有兩個參數,第一個就是需要加載佈局的id,第二個就是需要給該佈局外部再嵌套一層父佈局,如果不需要的話,我們就傳一個null就可以了。
用法就介紹到這裏,我們看一下inflate方法到底是如何實現的。
最終跳轉到的inflate方法源碼如下:
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;
}
}
從源碼中我們可以看出,LayoutInflate其實就是利用android pull解析方法來解析佈局文件的。
這個代碼很多,但是我們抓住幾行重要的函數看一下就可以大概瞭解工作機制了,其中調用了createViewFromTag()方法,將節點名和參數傳進去,我們看一下createViewFromTag實現的源碼:
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
這裏面,又調用了createView方法,然後使用反射的方式創建出view示例,然後返回。實際上就是根據節點創建了一個view對象,但是這裏只是創建一個根佈局實例而已,後面又調用rInflate()方法,遍歷這個跟佈局的子元素:
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (finishInflate) {
parent.onFinishInflate();
}
}
這裏其實是一個遞歸方法,調用createViewFromTag創建view實例,然後rInflate方法查找這個view下面的子元素,每次遞歸完成後將這個view添加到父佈局中。這樣將整個你要添加的佈局都解析完成,形成一個完整的DOM結構,然後將最頂層的根佈局返回。
我們再來看看inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)中的參數的含義,可以看一下這個代碼:
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
所以得到的結論如下:
1. 如果root爲null,attachToRoot將失去作用,設置任何值都沒有意義。
2. 如果root不爲null,attachToRoot設爲true,則會給加載的佈局文件的指定一個父佈局,即root。
3. 如果root不爲null,attachToRoot設爲false,則會將佈局文件最外層的所有layout屬性進行設置,當該view被添加到父view當中時,這些layout屬性會自動生效。
介紹到這裏,我們對layoutInflate應該有大致的瞭解了吧!除此之外,通過看一些資料,我發現了一些以前沒有注意過的東西。當我們在使用layoutInflater的時候,如果將一個只有button的佈局加到viewgroup中,無論我們怎麼設置button的layout_width和layout_height,添加到viewgroup中的button的大小都沒改變,這是爲啥咧?
那是因爲button控件不存在任何佈局中,如果你在button哪個xml中外面再包一層relativeLayout,就可以改變button的大小了。
補充:其實平時我們使用的setContentView方法中,我們自定義的佈局爲什麼設置layout_width和layout_height是ok的呢,原來setContentView中在我們佈局外面又嵌套了一層Framelayout,哈哈!
好了,今天就聊到這裏,要繼續苦逼做業務需求了!