Android LayoutInflater.inflate詳解

1. 作用

官方釋義

Inflate a new view hierarchy from the specified xml resource

大概意思就是從給定的xml中加載view樹。

2. 用法

2.1 四種重載

1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root);
2. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot);
3. public View inflate(XmlPullParser parser, @Nullable ViewGroup root)
4. public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot);

對外暴露的有四種重載,但是另外兩種需要自行傳入XmlPullParser,也就是說需要自行解析xml dom,屬於高端操作,一般使用較少。
其中,方法1也是使用了方法2,只不過在root非空的情況下,默認attachToRoot=true,故本文主要對方法2進行分析。

2.2 三個參數一個返回值

2.2.1 resource

resource顧名思義,資源,從給定的註解來看,要求爲layout資源。故參數一般爲R.layout.my_layout。

2.2.2 root

root顧名思義,根佈局,這裏的根佈局可以爲空。
當根部局爲空時,attachToRoot不生效且生成的view樹根節點的LayoutParams爲空,即你在layout中定義的根部局的什麼寬啊高啊內邊距外邊距相對位置等一切以layout_爲前綴的屬性都會失效。爲什麼呢?我們來看看LayoutParam的定義:

LayoutParams are used by views to tell their parents how they want to be laid out

意思是,LayoutParams是被用來告訴父佈局他想要怎麼被擺放的。
如果給定的root爲空,那麼加載出來的view就沒有父佈局,當然也沒必要擁有LayoutParams了!

2.2.3 attachToRoot

attachToRoot,即是否要將創建的view樹附加(連接)到給定的根佈局下。

2.2.4 返回值View

當不附加到根部局下時,返回的爲給定layout的根節點。
當附加到根部局下時,返回的爲根部局。

2.3 使用案例

所謂實踐出真知,下面來看一組栗子。
首先是一個根佈局,我們叫他root_layout

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fl_root"
    android:layout_width="300dp"
    android:layout_height="300dp"
    android:background="@android:color/holo_red_light">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:layout_gravity="center" />

</FrameLayout>

接着是一個我們想要加載的佈局inflate_layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_inflate_root"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:background="@android:color/holo_blue_light">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="im inflated" />

</LinearLayout>

之後我們找個Activity簡單實驗一下,這裏只給出了onCreate方法

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
         // activity content部分的根部局,實際上也是使用了LayoutInflater.inflate
         // 將佈局加載到了contentParent下,感興趣的可以看下PhoneWindow下的實現
        setContentView(R.layout.root_layout);
        FrameLayout rootView = findViewById(R.id.fl_root);  // 首先拿到root_layout的view樹根
        // 之後我們對inflateView進行測試,對應下面三種情況
        View inflateView = LayoutInflater.from(this).inflate(R.layout.inflate_layout, rootView, true);
//      View inflateView = LayoutInflater.from(this).inflate(R.layout.inflate_layout, rootView, false);
//      View inflateView = LayoutInflater.from(this).inflate(R.layout.inflate_layout, rootView);
        ViewGroup.LayoutParams rootLayoutParams = rootView.getLayoutParams();
        ViewGroup.LayoutParams inflateLayoutParams = inflateView.getLayoutParams();
        Log.d(TAG, "rootView=" + rootView + " rootView LayoutParams - " + rootLayoutParams);
        Log.d(TAG, "inflateView=" + inflateView + "inflateView LayoutParams -" + inflateLayoutParams);
    }
2.3.1 根佈局非空
2.3.3.1 attachToRoot=true
D/MainActivity: rootView=android.widget.FrameLayout{6f919a1 V.E...... ......ID 0,0-0,0 #7f07003c app:id/fl_root} rootView LayoutParams - android.widget.FrameLayout$LayoutParams@16527c6
D/MainActivity: inflateView=android.widget.FrameLayout{6f919a1 V.E...... ......ID 0,0-0,0 #7f07003c app:id/fl_root}inflateView LayoutParams -android.widget.FrameLayout$LayoutParams@16527c6

當attachToRoot爲true時,返回的爲root根節點。且將我們的inflate_layout解析出的view樹連接到了給的root下。
attach到view樹

2.3.3.1 attachToRoot=false
D/MainActivity: rootView=android.widget.FrameLayout{6f919a1 V.E...... ......ID 0,0-0,0 #7f07003c app:id/fl_root} rootView LayoutParams - android.widget.FrameLayout$LayoutParams@16527c6
D/MainActivity: inflateView=android.widget.LinearLayout{3547387 V.E...... ......ID 0,0-0,0 #7f07004f app:id/ll_inflate_root}inflateView LayoutParams -android.widget.FrameLayout$LayoutParams@79282b4

當attachToRoot爲false時,返回的爲inflate_layout的根view,且根據給定的rootView生成了自己的LayoutParam。但view並未被插入到給定根節點的view樹中。
未attach到view樹

2.3.1 根佈局爲空
D/MainActivity: rootView=android.widget.FrameLayout{6f919a1 V.E...... ......ID 0,0-0,0 #7f07003c app:id/fl_root} rootView LayoutParams - android.widget.FrameLayout$LayoutParams@16527c6
D/MainActivity: inflateView=android.widget.LinearLayout{3547387 V.E...... ......ID 0,0-0,0 #7f07004f app:id/ll_inflate_root}inflateView LayoutParams -null

根部局爲空時,也未將生成的view插入到view樹中(也不知道要往哪插)。並且與之前不同的是,LayoutParams爲空!
root爲空

3. 源碼解析

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            // .....
                if (TAG_MERGE.equals(name)) {
                	// merge標籤的解析
                	// 必須要attach到根節點
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    // 遞歸的將當前merge標籤下的孩子連接到根節點上
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                	// 其他標籤的解析, temp是當前解析的layout的根節點
                    // 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) {
                        // Create layout params that match root, if supplied
                        // 從解析出來的attr中獲取寬高,創建layoutparam
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            // 如果不attach到root下才進行設置,不然等到addView的時候再進行設置
                            temp.setLayoutParams(params);
                        }
                    }

                    // Inflate all children under temp against its context.
                    // 遞歸的inflate當前解析佈局根view下的孩子們
                    rInflateChildren(parser, temp, attrs, true);

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    // 如果root不爲空且要添加到根佈局下時使用addView
                    if (root != null && attachToRoot) {
                    	// 將temp插入到根節點的最後一個孩子的位置,並設置他的layoutParam爲params
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    // 如果根爲空或者不連接到根節點時返回的是解析的layout.xml的根節點
                    // 否則返回的爲root
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
			// .....
            return result;
        }
    }
    /**
     * Adds a child view with the specified layout parameters.
     *
     * <p><strong>Note:</strong> do not invoke this method from
     * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
     * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
     *
     * @param child the child view to add
     * @param index the position at which to add the child or -1 to add last
     * @param params the layout parameters to set on the child
     */
    public void addView(View child, int index, LayoutParams params) {
        // .....
        addViewInner(child, index, params, false);
    }
    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
        // .....
        // 如果給定的params是空,則爲其創建一個,這個具體的實現在各個父親那,看他們想咋擺孩子
        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }
		
        if (preventRequestLayout) {
        	// 設置佈局時不進行requestLayout,即只設置參數不進行刷新,一般用在正在onLayout時
            child.mLayoutParams = params;
        } else {
            child.setLayoutParams(params);
        }
        // .....
    }

大概就是醬紫,簡單來說就是有無root決定加載出來的孩子是否是健全(知不知道自己多大應該在哪兒)的孩子,是否attachToRoot就是這孩子是要跟着他爹還是要自己單飛。
最後推薦一篇講LayoutParams講的不錯的博客自定義控件知識儲備-LayoutParams的那些事

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