結合Andorid 9.0 的代碼,記錄一下學習過程,大多時候是用的下面這個方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
還有就是重載的另一個方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
最終都會進入下面的方法:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
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;
//通過XmlPullParser解析我們所定義的佈局文件
//這裏會通過其next()方法一直循環,直到遇到START_TAG標誌
//START_TAG代表了我們定義的佈局文件的最外層佈局的起點
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();//name值一般是LinearLayout、FrameLayout等佈局
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
//根據上面返回的最外層佈局的name值來創建View ,會利用反射的方式創建
//該方法一般會返回一個ViewGroup實例作爲根佈局,例如LinearLayout等
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
//下面的情況就是我們經常用的一種方法:inflate(resId,lineaViewGroup,false)
//這種情況下我們在最外層佈局定義的參數會生效
if (root != null) {
......
//根據最外層的佈局參數(就是我們再xml定義的寬、高這些參數)來創建一個LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
//設置上面返回的最外層佈局的佈局參數(我們在xml文件裏自己定義的參數)
temp.setLayoutParams(params);
}
}
......
// Inflate all children under temp against its context.
//將最外層佈局下的所有子佈局(如果有的話)全部遍歷一遍,每遍歷一層
//便會創建一個佈局實例(例如控件TextView、Button,也可能是父佈局)
rInflateChildren(parser, temp, attrs, true);
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
//下面這種情況會爲我們加載的佈局文件指定父佈局(也就是root)
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.
//root爲null時會直接返回我們在xml中定義的佈局
//注意此時並沒有爲temp設置佈局參數
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
......
return result;
}
}
接下來看一下是怎麼遍歷最外層佈局下的所有子佈局的
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
又調用了 rInflate 方法
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
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();
......
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, context, attrs);//再次調用了createViewFromTagz方法創建佈局實例
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);//使用了遞歸來遍歷,每遞歸一次便會創建一個佈局實例,直到所有佈局都遍歷一遍
viewGroup.addView(view, params);
}
}
......
}
接下來會通過一個例子,根據Log日誌看一下XmlPullParser遍歷佈局文件的大致流程
下面是在佈局文件裏定義好的要遍歷的佈局
linear_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="150dp"
android:text="TextView" >
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="50dp"
/>
</TextView>
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
以下是主要代碼:
private void getParserFromResource(int layoutResID){
final Resources res=this.getResources();
final XmlResourceParser parser=res.getLayout(layoutResID);
try{
getInflateInfo(parser);
}catch (Exception e){
}
}
private void getInflateInfo(XmlPullParser parser)throws Exception{
int type;
//type=parser.getEventType();
type=parser.next();//用next方法也可以返回 START_DOCUMENT
//下面的Log會打印不出來,如果加了 parser.nextText() 這個方法便會出現Log打印不出來的情況,這個還是需要注意一下
//Log.d(TAG,"type name:"+typeToString(type)+" parser name:"+parser.getName()+ " textName:"+parser.nextText());
Log.d(TAG,"type name:"+typeToString(type)+" parser name:"+parser.getName()
+" depth:"+parser.getDepth());
while(type != XmlPullParser.END_DOCUMENT)
{
Log.d(TAG,"type name:"+typeToString(type)+" parser name:"+parser.getName()
+" depth:"+parser.getDepth());
switch (type)
{
case XmlPullParser.START_TAG:
//Log.d(TAG,"START_TAG"+" name:"+parser.getName());
break;
case XmlPullParser.END_TAG:
//Log.d(TAG,"END_TAG"+" name:"+parser.getName());
break;
}
//不停的向下解析
type = parser.next();
}
Log.d(TAG,"type name:"+typeToString(type)+" parser name:"+parser.getName());
}
下面是打印出的Log信息:
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:START_DOCUMENT parser name:null depth:0
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:START_DOCUMENT parser name:null depth:0
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:START_TAG parser name:LinearLayout depth:1
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:START_TAG parser name:TextView depth:2
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:START_TAG parser name:Button depth:3
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:END_TAG parser name:Button depth:3
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:END_TAG parser name:TextView depth:2
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:START_TAG parser name:Button depth:2
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:END_TAG parser name:Button depth:2
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:END_TAG parser name:LinearLayout depth:1
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:END_DOCUMENT parser name:null
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: onCreate:
從 Log 可以看到使用XmlPullParser遍歷一個佈局文件時,一開始是從START_DOCUMENT的標記開始的
START_DOCUMENT 代表了一個佈局文件的開始
第一次出現的 START_TAG 標記表示根佈局的開始,中間的STAR_TAG、END_TAG代表了子佈局或者子控件的開始和結束
歡迎批評指正