深入瞭解View(一)——LayoutInflater原理分析

一直想要深入瞭解一下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,哈哈!

好了,今天就聊到這裏,要繼續苦逼做業務需求了!

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