码迷,mamicode.com
首页 > 移动开发 > 详细

Android 中LayoutInflater原理分析

时间:2016-07-20 23:08:52      阅读:293      评论:0      收藏:0      [点我收藏+]

标签:

概述

在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:

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:text="button" >

</Button>

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("<merge /> 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的方法的解析便结束了。

Android 中LayoutInflater原理分析

标签:

原文地址:http://blog.csdn.net/yuminfeng728/article/details/51970880

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!