Android LayoutInflater inflate方法学习

结合Andorid 9.0 的代码,记录一下学习过程,大多时候是用的下面这个方法

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

还有就是重载的另一个方法:

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

最终都会进入下面的方法:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                // Look for the root node.
                int type;
                //通过XmlPullParser解析我们所定义的布局文件
                //这里会通过其next()方法一直循环,直到遇到START_TAG标志
                //START_TAG代表了我们定义的布局文件的最外层布局的起点
                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();//name值一般是LinearLayout、FrameLayout等布局
                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, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                   //根据上面返回的最外层布局的name值来创建View ,会利用反射的方式创建
                   //该方法一般会返回一个ViewGroup实例作为根布局,例如LinearLayout等
                   final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                   //下面的情况就是我们经常用的一种方法:inflate(resId,lineaViewGroup,false) 
                   //这种情况下我们在最外层布局定义的参数会生效
                   if (root != null) {
                        ......
                       //根据最外层的布局参数(就是我们再xml定义的宽、高这些参数)来创建一个LayoutParams
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                           //设置上面返回的最外层布局的布局参数(我们在xml文件里自己定义的参数)
                           temp.setLayoutParams(params);
                        }
                    }
                    
                    ...... 
                    
                    // Inflate all children under temp against its context.
                    //将最外层布局下的所有子布局(如果有的话)全部遍历一遍,每遍历一层
                    //便会创建一个布局实例(例如控件TextView、Button,也可能是父布局)
                    rInflateChildren(parser, temp, attrs, true);
                    
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    //下面这种情况会为我们加载的布局文件指定父布局(也就是root)
                    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为null时会直接返回我们在xml中定义的布局
                    //注意此时并没有为temp设置布局参数
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
              ......

            return result;
        }
    }

接下来看一下是怎么遍历最外层布局下的所有子布局的

 

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

又调用了 rInflate 方法

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

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            ......
          
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException(" must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);//再次调用了createViewFromTagz方法创建布局实例
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);//使用了递归来遍历,每递归一次便会创建一个布局实例,直到所有布局都遍历一遍
                viewGroup.addView(view, params);
            }
        }

       ......
    }

 

接下来会通过一个例子,根据Log日志看一下XmlPullParser遍历布局文件的大致流程

下面是在布局文件里定义好的要遍历的布局

linear_layout.xml




    

        

以下是主要代码:

private void getParserFromResource(int layoutResID){
    final Resources res=this.getResources();
    final XmlResourceParser parser=res.getLayout(layoutResID);
    try{
       getInflateInfo(parser);
    }catch (Exception e){

    }
 }

private void getInflateInfo(XmlPullParser parser)throws Exception{
        int type;
        //type=parser.getEventType();
        type=parser.next();//用next方法也可以返回 START_DOCUMENT
        //下面的Log会打印不出来,如果加了 parser.nextText() 这个方法便会出现Log打印不出来的情况,这个还是需要注意一下
        //Log.d(TAG,"type name:"+typeToString(type)+" parser name:"+parser.getName()+ " textName:"+parser.nextText());
        Log.d(TAG,"type name:"+typeToString(type)+" parser name:"+parser.getName()
                +" depth:"+parser.getDepth());
        while(type != XmlPullParser.END_DOCUMENT)
        {
            Log.d(TAG,"type name:"+typeToString(type)+" parser name:"+parser.getName()
                    +" depth:"+parser.getDepth());
            switch (type)
            {
                case XmlPullParser.START_TAG:
                    //Log.d(TAG,"START_TAG"+" name:"+parser.getName());
                    break;
                case XmlPullParser.END_TAG:
                    //Log.d(TAG,"END_TAG"+" name:"+parser.getName());
                    break;
            }
            //不停的向下解析
            type = parser.next();
        }
        Log.d(TAG,"type name:"+typeToString(type)+" parser name:"+parser.getName());
    }

下面是打印出的Log信息:

2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:START_DOCUMENT parser name:null depth:0
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:START_DOCUMENT parser name:null depth:0
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:START_TAG parser name:LinearLayout depth:1
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:START_TAG parser name:TextView depth:2
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:START_TAG parser name:Button depth:3
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:END_TAG parser name:Button depth:3
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:END_TAG parser name:TextView depth:2
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:START_TAG parser name:Button depth:2
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:END_TAG parser name:Button depth:2
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:END_TAG parser name:LinearLayout depth:1
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: type name:END_DOCUMENT parser name:null
2020-01-18 16:54:21.173 31278-31278/com.lollo.view_myxmlparser D/MainActivity: onCreate:

从 Log 可以看到使用XmlPullParser遍历一个布局文件时,一开始是从START_DOCUMENT的标记开始的

START_DOCUMENT 代表了一个布局文件的开始

第一次出现的 START_TAG 标记表示根布局的开始,中间的STAR_TAG、END_TAG代表了子布局或者子控件的开始和结束

 

 欢迎批评指正

 

你可能感兴趣的:(Android开发笔记,Android自定义View)