Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Android进阶系列4—从LayoutInflater到setContentView的LayoutInflater

$
0
0

N天前博主面试的时候,面试官问我TextView是如何加载到界面上的。博主说LayoutInflator,interviewer问怎么操作的呢?我说:记不得了。。。翻看郭神的博客,发现早在14年底,我就在他的Android LayoutInflater原理分析,带你一步步深入了解View(一)里面有留言,可能是当时too young,对郭神的讲解不知所云,时隔一年多再看,觉得博主很会讲课,通俗易懂。拍郭神个马屁(他很可能看不到),他是我在CSDN上看到的,最会讲解知识的人之一。
不管是动态加载布局,还是在xml实现布局,控件是被显示的过程都是相似的,离不开LayoutInflater的支持。本文将讲述LayoutInflater如何将布局加载,以及setContentView如何将参数传递到LayoutInflater中。
本文要大力感谢 Android应用setContentView与LayoutInflater加载解析机制源码分析,博主是看了他的文章之后才有些明白的,本文不只总结,会讲细节!!!啊哈哈。

1. LayouInflater

LayoutInflater的使用流程一般是这样的:

//第一步实例化LayoutInflater
LayoutInflater layoutInflater = LayoutInflater.from(context);  
//第二步inflate布局id并返回view
        View view = layoutInflater.inflate(R.layout.***id, null);  

实例化LayoutInflater有两种写法:

//第一种
  LayoutInflater lif = LayoutInflater.from(Context context);
//第二种
   LayoutInflater lif = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

第一种写法其实是对第二种写法的封装

    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);//调用getSystemService获取LayoutInflater实例
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

实例化之后,就是layoutInflater.inflate()方法的调用,我们就从这儿说开去。

1. inflate()

    public View inflate(int resource, ViewGroup root) {
        return inflate(resource, root, root != null);//没有设置attachToRoot的情况下,如果root!=null,默认为true
    }

继续看inflate(int resource, ViewGroup root, boolean attachToRoot)方法:

    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);//Android提供的pull解析方式解析布局文件
        try {
            return inflate(parser, root, attachToRoot);//关键的方法
        } finally {
            parser.close();
        }
    }

实际上,不管使用的哪个inflate()方法的重载,最终都会辗转调用到LayoutInflater的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;//定义返回值,初始化为传入的形参root
        View result = root;
        try {
             // 寻找根节点
             int type;
             while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
              }
              //如果一开始就是END_DOCUMENT,抛出异常
              if (type != XmlPullParser.START_TAG) {
                  throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
               }
                //通过了上面的while和if条件,说明这里type一定是START_TAG,也就是xml文件里的根节点
                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)) {
                //处理merge tag的情况(merge,APP的xml布局优化)
                    //root必须非空且attachToRoot为true,否则抛异常结束(使用merge时要注意的地方:merge的xml并不代表某个具体的view,只是将它包起来的其他xml的内容,加到某个上层ViewGroup中)
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                    //递归rinflate方法调运,循环遍历布局下的子元素
                    rInflate(parser, root, attrs, false, false);
                } else {
                    // xml文件中的root view,根据tag节点创建view对象
                    final View temp = createViewFromTag(root, name, attrs, false);
                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                        if (DEBUG) {
                           System.out.println("Creating params from root: " +root);
                        }
//根据root生成LayoutParams
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
// 设置temp的LayoutParams,如果attachToRoot为true,则在后面会调用root.addView()。
                  temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }
                    //递归调用rInflate绘制剩下的children
                    rInflate(parser, temp, attrs, true, true);
                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }
                    if (root != null && attachToRoot) {
                        //root非空且attachToRoot=true则将xml文件的temp root加到形参提供的root里
                        root.addView(temp, params);
                    }

                    // 如果root为空,或者为绑定到root
                    if (root == null || !attachToRoot) {
                        //返回xml里解析的root view
                        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 {
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
            }

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            //返回参数root或xml文件里的temp root
            return result;
        }
    }

代码的详细说明在注释里面都体现了,inflate()方法的流程概括而言就是:

  1. 找到根节点
  2. 处理merge
  3. 创建根View
  4. 按照root参数,attachToRoot参数确定LayoutParams以及返回
  5. 递归绘制子View。

inflate的返回结果和参数之间的关系总结成三类情况:

  1. inflate(xmlId, null)/inflate(xmlId,null,true/false):返回xml的root view。
  2. inflate(xmlId,parent,false):利用parent的layoutParam绘制xml中的root view,将xml的root view返回。
  3. inflate(xmlId,parent,true)/inflate(xmlId,parent):返回parent,xml中内容以parent.addView()的方式加入到parent中。

注意到inflate()中调用了addView()方法,并提到了LayoutParam,我们看下这两个是干嘛的。

2. addView

//添加一个子View
public void addView(View child) {
        addView(child, -1);
    }
public void addView(View child, int index) {
        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
        //如果没有设置它的布局参数的话,会提供它的父布局的默认参数给子view 
            params = generateDefaultLayoutParams();
            if (params == null) {
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);//按指定的布局添加子View
    }

addView(child, index, params)的具体实现,我们暂且不关心,由上述流程可以看出,addView()的作用即可。

3. LayoutParams

再关心下LayoutParams布局参数有时干啥的?官方文档说:
LayoutParams are used by views to tell their parents how they want to be laid out——用于视图告诉父布局他们该如何摆放。不同的布局一般在ViewGroup的基础上,扩充了LayoutParams,在构建LayoutParams对象时,可调用父布局自身的构造函数创建。比如向LinearLayout添加子视图时,子视图可以

LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);

不同的需求情况,查看谷歌手册就好。

4. rInflate()

在inflate()函数中,只是创建了一个布局文件中的根部局(按照条件决定是否要将其添加到参数root),对于根部局下的子布局会循环调用rInflate()函数遍历解析。看下rInflate()的实现代码,和inflate()有相似之处

    void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
            boolean finishInflate, boolean inheritContext) throws XmlPullParserException,
            IOException {

        final int depth = parser.getDepth();
        int type;
        //XmlPullParser解析器的标准解析模式
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
            //找到START_TAG节点程序才继续执行这个判断语句之后的逻辑
            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            //获取Name标记
            final String name = parser.getName();
            //处理REQUEST_FOCUS的标记
            if (TAG_REQUEST_FOCUS.equals(name)) {
                parseRequestFocus(parser, parent);
            } else if (TAG_TAG.equals(name)) {
                //处理tag标记
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                //处理include标记
                if (parser.getDepth() == 0) {
                    //include节点如果是根节点就抛异常
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, parent, attrs, inheritContext);
            } else if (TAG_MERGE.equals(name)) {
                //merge节点必须是xml文件里的根节点(这里不该再出现merge节点)
                throw new InflateException("<merge /> must be the root element");
            } else {
                //创建View
                final View view = createViewFromTag(parent, name, attrs, inheritContext);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                //继续递归解析子View
                rInflate(parser, view, attrs, true, true);
                viewGroup.addView(view, params);
            }
        }
        //parent的所有子节点都inflate完毕的时候回onFinishInflate方法
        if (finishInflate) parent.onFinishInflate();
    }

rInflate的流程大概包括:

  1. 找到开始节点
  2. 处理requestFoucs,处理tag,处理include,处理merge
  3. 处理其他情况,并递归处理子节点,addView
  4. 全都绘制完后返回onFinish方法

可以看到不管在rInflate还是在inflate中,都是利用createViewFromTag创建View的。createViewFromTag方法内部又会去调用createView()方法,然后使用反射的方式创建出View的实例,createView方法不做深入分析。

总结

好啦,LayoutInflater就差不多说完了。从创建对象->inflate()->rInflate()完成从布局文件到View的建立,再到LayoutParams构建参数->addView,添加到父布局中。下篇再说setContentView到LayoutInflater中间又是如何衔接的。
很惭愧,做了一点微小的贡献!

作者:u011026779 发表于2016/8/21 15:18:03 原文链接
阅读:43 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>