[Android]LayoutInflater的inflate方法半详解

好久没写博客,作为一名刚加入android阵营的android狗,发心得刷人气来了!!!(半详解是因为说详不那么详,说不详也稍微有点详。。)哈哈~~。。咳。。咳。。

一、Activity中的setContentView

对于刚开始学Android的新手来说,在Activity中加载布局文件的方法是在onCreate()回调方法中直接调用setContentView()方法,如:

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

setContentView()的android源码如下:

public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

其中getWindow()返回值类型为PhoneWindow,

首先我们要明白Window与Activity之间的关系。在源码中可以看到Activity的两个成员变量,分别是:

private Window mWindow;
private WindowManager mWindowManager;

而mWindow在Activity的attach()方法中被赋值,代码如下:

mWindow = PolicyManager.makeNewWindow(this);

跟进去可以看到它返回的是一个PhoneWindow对象,它是Window的子类。现在再返回去setContentView()的源码,其中关键的就是getWindow().setContentView(layoutResID);这一句;其中getWindow()方法其实就是返回Activity的成员变量mWindow,因此Activity的setContentView()实质上是调用PhoneWindow的setContentView()方法。接着看源码:

 @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
从上面的代码看到,首先判断mContentParent是否为null,是则调用installDecor(),否则移除其内部所有的子Views,然后通过LayoutInflater.inflate()方法将我们传入的布局资源放入mContentParent中。其中mContentParent是一个ViewGroup作为整个布局文件的容器,而installDecor()方法用于初始化mContentParent

我们看到我们最熟悉的setContentView()方法加载布局时的动作,实质上是由LayoutInflater的inflate()方法完成。等等,这个方法好熟悉!Fragment加载布局就是在其onCreateView()中通过系统传入的LayoutInflater对象调用inflate()方法;在Activity中引用其他布局中的组件也是使用首先LayoutInflater.from(this)实例化一个LayoutInflater对象,再用它inflate()一个布局文件从而引用到该View,再使用该View来findViewById()找到该组件。wow~貌似只要有加载布局的地方就离不开它耶~那么问题来了,这个Inflate()方法究竟是做什么的呢?

二、LayoutInflater.inflate()方法

首先要弄清LayoutInflater是什么鬼!来看API的介绍:

Instantiates a layout XML file into its corresponding View objects. It is never used directly. Instead, use getLayoutInflater() or getSystemService(String) to retrieve a standard LayoutInflater instance that is already hooked up to the current context and correctly configured for the device you are running on. For example:

LayoutInflater inflater = (LayoutInflater)context.getSystemService

    (Context.LAYOUT_INFLATER_SERVICE);

中文(自己翻的):将一个布局的XML文件实例化为一个与其对应的View对象。不能直接使用LayoutInflater。要使用它,必须使用使用getLayoutInflater()或getSystemService(String)方法来获取一个标准的LayoutInflater实例,这个LayoutInflater实例已经绑定到了当前的context并且在当前正在运行的机器上进行过了正确的配置。举例如下:

LayoutInflater inflater = (LayoutInflater)context.getSystemService

    (Context.LAYOUT_INFLATER_SERVICE);

其中在Activity中获取LayoutInflater采用的LayoutInflater.from(this)实质上就是通过上面举得例子的方法获取到的。

接下来看它的inflate()方法,源码中可以看见,inflate()方法有四种重载方式,分别如下:

public View inflate(int resource, ViewGroup root)

public View inflate(XmlPullParser parser, ViewGroup root)

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

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

它们的返回值虽然类型都为View,但在不同的情况下它们的返回值可不见得都是通过传入XML布局资源实例化出来的View,不信?我们慢慢来看。

我们首先来看前三种重载方法的源码。

    public View inflate(int resource, ViewGroup root) {
        return inflate(resource, root, root != null);
    }

    public View inflate(XmlPullParser parser, ViewGroup root) {
        return inflate(parser, root, root != null);
    }

    public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

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

可以看出,这三种方法实际上都是直接或间接调用了第四种重载方法。我们首先来看这个方法的API文档:

Inflate a new view hierarchy from the specified XML node. Throws InflateException if there is an error.

Important   For performance reasons, view inflation relies heavily on pre-processing of XML files that is done at build time. Therefore, it is not currently possible to use LayoutInflater with an XmlPullParser over a plain XML file at runtime.

Parameters

parser

XML dom node containing the description of the view hierarchy.

root

Optional view to be the parent of the generated hierarchy (if attachToRoot is true), or else simply an object that provides a set of LayoutParams values for root of the returned hierarchy (if attachToRoot is false.)

attachToRoot

Whether the inflated hierarchy should be attached to the root parameter? If false, root is only used to create the correct subclass of LayoutParams for the root view in the XML.

Returns

  • The root View of the inflated hierarchy. If root was supplied and attachToRoot is true, this is root; otherwise it is the root of the inflated XML file.

首先来看该方法的作用:从指定的XML节点inflate出一个新的View层次(即把XML文件表述的布局实例化为一个View)。

接着我们主要看它的后两个参数与返回值。

root:如果attachToRoottrue,则它是通过XML文件inflate出的View的父容器;如果attachToRootfalse,则root仅用来帮助通过XML文件来创建包含其根布局参数的View

attachToRoot:是否应该将通过XML文件inflate出的View贴在(个人认为“贴”字比较合适)root参数上?如果为false,那么root只是用来在其内部创造LayoutParams的正确子类以便View能以正确地布局参数贴上去(翻译的好烂。。。不知道对不对)。

返回值:inflated出来的View的父视图。如果root不为null并且attachToRoottrue,则返回root;否则返回通过XML文件inflate出的View。

部分重要源码如下

final AttributeSet attrs = Xml.asAttributeSet(parser);//从Xml中取出传入的布局文件的属性attrs
            		...
          <span style="white-space:pre">	</span>    View result = root;		//默认root为返回值
					...
			    // Temp is the root view that was found in the xml
			    //temp是通过xml生成的view的根视图
		    final View temp = createViewFromTag(root, name, attrs, false);	

                    ViewGroup.LayoutParams params = null;
				...
		    // Inflate all children under temp
		    // 循环inflate temp的子视图(此时的temp为不包含LayoutParams的View)
	            rInflate(parser, temp, attrs, true, true);
				...
                    if (root != null) {	//当root不为null
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);

		   //若attachToRoot为false,则将通过attires生成的bu布局参数params设为temp的内部布局参数变量mLayoutParams
                    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为true,则temp贴到root上(连带temp的布局属性)
			    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为空或者attachToRoot为false,则返回值为temp
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
				...
		    return result;

也就是说temp是利用xml文件解析出的attrs创造出来的一个View,但是temp开始并不包含ViewGroup.LayoutParams类型的布局参数,那么ViewGroup.LayoutParams又是什么呢?通过源码我们可以看到,其实它就是包含了布局View的宽和高的属性。每一个View都包含一个mLayoutParams的成员变量,用于存放该View的根视图的长宽信息。因此,其实上面的代码分为这几种情况:

(1)root == null,则返回temp(未设定其mLayoutParams)。

(2)root != null,且attachToRoot==true,则返回添加了temp(已设定过其mLayoutParams)的root视图。

(3)root != null,且attachToRoot==false,则返回temp(未设定其mLayoutParams)。

设置了mLayoutParams与没有设置的temp的区别在于,若将temp添加到其它布局中,设置过mLayoutParamstemp的根视图的layout_width与layout_height属性会表现出来;而没有设置的temp的根视图的layout_width与layout_height属性则表现不出来。举个栗子:我们写一个xml布局文件如下,其父视图我们设定了长宽及颜色属性。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="60dp"
    android:layout_height="60dp"
    android:background="#777777"
    android:orientation="vertical" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="你好" />

</LinearLayout>

在Activity的布局中加一个LinearLayout,id为mLayout,然后通过三种不同方式将上面这个布局加载到mLayout中,来观察它根视图的长宽属性的变化。下面是mainActivity的部分代码:

        setContentView(R.layout.activity_main);
        mLayout = (LinearLayout) findViewById(R.id.mLayout);
        
        View view = LayoutInflater.from(this).inflate(R.layout.my_text, null, false);
        mLayout.addView(view);
        View view2 = LayoutInflater.from(this).inflate(R.layout.my_text, mLayout, false);
        mLayout.addView(view2);
        LayoutInflater.from(this).inflate(R.layout.my_text, mLayout, true);

页面截图如下:


从我们的截图可以看到,第一个加载的xml的根视图的长宽属性没起作用,而后两种的起到了作用,结果正如我们猜测的那样~

伙计们端午好好耍~


阅读更多
换一批

没有更多推荐了,返回首页