Android LayoutInflater inflate方法學習

結合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代表了子佈局或者子控件的開始和結束

 

 歡迎批評指正

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章