Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 中LayoutInflater原理分析

Android 中LayoutInflater原理分析

編輯:關於Android編程

概述

在Android開發中LayoutInflater的應用非常普遍,可以將res/layout/下的xml布局文件,實例化為一個View或者ViewGroup的控件。與findViewById的作用類似,但是findViewById在xml布局文件中查找具體的控件,兩者並不完全相同。

應用場景:
1.在一個沒有載入或者想要動態載入的界面中,需要使用layoutInflater.inflate()來載入布局文件;
2.對於一個已經載入的界面,就可以使用findViewById方法來獲得其中的界面元素;

獲得LayoutInflater的對象三種實現方式

1.LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2.LayoutInflater layoutInflater = getLayoutInflater();
3.LayoutInflater layoutInflater = LayoutInflater.from(context);

然而,上面三種LayoutInflater的本質是相同的,最終都是調用第一種的方式生成layoutInflater對象,我們可以查看源碼:

getLayoutInflater()的源碼:
Activity的getLayoutInflater()方法是調用PhoneWindow 的getLayoutInflater()方法,源碼如下:

    public PhoneWindow(Context context) {  
        super(context);  
        mLayoutInflater = LayoutInflater.from(context);  
    }  

LayoutInflater.from(context)的源碼:

    /**
     * Obtains the LayoutInflater from the given context.
     */
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

可以看出其實也都是調用context.getSystemService()。得到了LayoutInflater的對象之後就可以調用它的inflate()方法來加載布局了。

inflate方法
inflate方法根據傳入不同的參數,一共有四種方法,如下:

public View inflate(int resource, ViewGroup root)
public View inflate(int resource, ViewGroup root, boolean attachToRoot)
public View inflate(XmlPullParser parser, ViewGroup root)
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

這裡我們主要使用的是前面兩種方法。
我們來分析第一個方法public View inflate(int resource, ViewGroup root):
第一個參數表示的是要加載的布局id,第二個參數表示的是給加載的布局嵌套的父布局,如果不需要就直接傳null。
第二個方法public View inflate(int resource, ViewGroup root, boolean attachToRoot):
前兩個參數與上面的方法一致,第三個參數attachToRoot表示是否將加載的布局,添加到root中來。

從上面來看 inflate(layoutId, null) 和 inflate(R.layout.button, root,false) 效果似乎是一樣的,實際上兩種方法的效果完全不同。

代碼示例
布局文件button.xml:

Activity中的代碼:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_other);

        ViewGroup group = (ViewGroup) findViewById(android.R.id.content);
        LayoutInflater layoutInflater = getLayoutInflater();
        View view = layoutInflater.inflate(R.layout.button, null);
        group.addView(view);
    }

}

效果圖如下:
這裡寫圖片描述

修改Activity中的代碼:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_other);

        ViewGroup group = (ViewGroup) findViewById(android.R.id.content);
        LayoutInflater layoutInflater = getLayoutInflater();
        View view = layoutInflater.inflate(R.layout.button, group,false);
        group.addView(view);
    }

}

運行後,效果圖:
這裡寫圖片描述

繼續修改Activity中代碼:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_other);

        ViewGroup group = (ViewGroup) findViewById(android.R.id.content);
        LayoutInflater layoutInflater = getLayoutInflater();
        View view = layoutInflater.inflate(R.layout.button, group,true);
        group.addView(view);
    }
}

運行後,發現拋出異常信息:
E/AndroidRuntime(3362): Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child’s parent first.

上面主要是使用了三種不同的inflate方法:
1.inflate(R.layout.button, null);
2.inflate(R.layout.button, group,false);
3.inflate(R.layout.button, group,true);

從上面三個不同的結果中,可以看到使用 inflate(layoutId, null)時,布局中的寬高屬性的設置無效,但是使用inflate(R.layout.button, root,false)時,布局中的寬高屬性又發揮了作用了。使用inflate(layoutId, root, true )時,又拋出異常,無法正常運行。
為什會出現這樣的結果呢?我們可以通過源碼來查看原因。

源碼解析
我們進入inflate方法中查看:

    public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

發現上面三種方式,最終都是調用inflate(parser, root, attachToRoot)的方法,繼續進入:

    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context)mConstructorArgs[0];
            mConstructorArgs[0] = mContext;
            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 (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException(" can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, attrs, false, false);
                } else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, attrs, false);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        // 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);
                        }
                    }
                    // Inflate all children under temp
                    rInflate(parser, temp, attrs, true, true);

                    // 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 (IOException 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;
        }
    }

上面的代碼中,刪去了一個DEBUG的部分,可以一起看關鍵代碼:
1.View result = root,將參數root賦值到result上,並且方法結束時,返回result。
2.下面 final View temp = createViewFromTag(root, name, attrs, false); 創建一個View;
3.接著:

        if (root != null) {

             // 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);
              }
         }

如果root 不為空,並且attachToRoot = false時,給temp設置setLayoutParams;這裡就解釋了上面代碼中使用inflate(R.layout.button, group,false)時,布局中的寬高能夠發生作用。
4.接著看下面:

if (root != null && attachToRoot) {
    root.addView(temp, params);
}

如果root 不為空,並且attachToRoot 為true時,這時params == null。從這裡,我們又可以看到為什上面使用inflate(R.layout.button, group,true)時,會拋出異常信息,原因是在方法內部已經執行了root.addView(temp, params)的操作,外面在執行group.addView(view) 的代碼時,會拋出異常。解決該問題,只需要去掉group.addView(view)這段代碼即可。
5.繼續:

if (root == null || !attachToRoot) {
     result = temp;
}

可以看到,當root的等於null,或者attachToRoot 為false時,直接將temp賦值給result。從這裡我們可以看到,在我們執行代碼inflate(R.layout.button, null)時,其實就是執行的是inflate(R.layout.button, null,false),result並沒有被設置setLayoutParams,所以在布局中設置的寬高屬性無效。

總結
inflate(layoutId, null)時,布局中的寬高屬性的設置無效;
inflate(R.layout.button, root,false)時,布局中的寬高屬性有效;
inflate(layoutId, root, true )時,後面不需要執行addView的操作,否則報異常。

以上針對layoutInflater.inflate的方法的解析便結束了。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved