美文网首页
View的LayoutInflater源码分析

View的LayoutInflater源码分析

作者: 楚灵彦 | 来源:发表于2017-08-11 23:55 被阅读0次

    1.layout布局转换view方法

    2个重载方法,最终都是到最后一个方法

    LayoutInflater.from(this).inflate();
    
    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) {
            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();
            }
    }
    

    2.真正的解析方法

    Android里面的layout都是xml形式,因此出现XmlResourceParser解析器,用于解析xml布局各个节点(tag)view. 继续看方法inflate(parser, root, attachToRoot),很明显解析器作为参数传入方法进行解析。

     public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
            synchronized (mConstructorArgs) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
                .......
                View result = root;
                try {
                    // Look for the root node. 此处检查根布局tag
                    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)) {  //merge标签
                        if (root == null || !attachToRoot) {
                            throw new InflateException("<merge /> 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
                        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                        ViewGroup.LayoutParams params = null;
                        if (root != null) {
                            if (DEBUG) {
                                System.out.println("Creating params from root: " +
                                        root);
                            }
                            // 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 against its context. 递归调用解析view树
                        rInflateChildren(parser, temp, attrs, 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 (Exception e) {
                   .......
                } finally {
                   .......
                }
                return result;
            }
        }
    

    以下是几个关键点:

    • root作为该布局view的父布局
    View result = root;
    
    • 生成临时的view,布局根view
     final View temp = createViewFromTag(root, name, inflaterContext, attrs);
      ViewGroup.LayoutParams params = null; //初始布局参数
    
    • 根据root测量布局根view的布局参数
    final AttributeSet attrs = Xml.asAttributeSet(parser);
    
     final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
     ViewGroup.LayoutParams params = null;
    
     params = root.generateLayoutParams(attrs);  //获取attrs布局参数
    
    
    • 递归调用解析view树
      rInflateChildren(parser, temp, attrs, true);
      
      
    
    • root是否为null,attachToRoot是否为true三种组合
    //此处root!=null,就获取布局里面的布局参数
    if (root != null) {
    // Create layout params that match root, if supplied params =  
    params = root.generateLayoutParams(attrs);  //获取attrs布局参数
    if (!attachToRoot) {
        // Set the layout params for temp if we are not
        // attaching. (If we are, we use addView, below)
        temp.setLayoutParams(params);
    }
    }
    
    // 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);
    }
    
    if (root == null || !attachToRoot) {
        result = temp;
    }
    

    2.分析结论

    上面的tempView代表整个layout布局转换出来的view,而最外层的根view,大小是由其自己和父view共同测量决定,也就是在父view里面的布局参数。

    1.1 inflate(int resource, ViewGroup root)==inflate(resource, root, root != null)

    1.2 inflate(int resource, ViewGroup root, boolean attachToRoot)

    • 1.1等价1.2 root != null, attachToRoot=true

    1.3 inflate(parser, root, attachToRoot)

    • root=null 则 ViewGroup.LayoutParams params = null , temp和根布局无关; //返回temp,没有任何父布局,不被加入任何root,而且没有设置布局参数,如果加入容器中,布局属性失效。

    • root!=null 且attachToRoot=true则 root.addView(temp, params);,返回root,temp布局属性生效,并且被加入root。

    • root!=null 且attachToRoot=fasle则 temp.setLayoutParams(params); 返回temp,不被加入root,设置了布局参数,如果加入view容器中,temp布局属性生效。

    以上结论,大家可以自己验证,可以借助Layout Inpector查看布局的层级结构。

    3. Android 源码出现的例子(基于7.0)

    • listview的item布局填充
     public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
        holder=new ViewHolder();  
        convertView = mInflater.inflate(R.layout.item, null);
        }else {holder = (ViewHolder)convertView.getTag(); }
          
    //只能填充以下的例子
    1.convertView = mInflater.inflate(R.layout.item, parent,false);
    
    2.convertView = mInflater.inflate(R.layout.item, null);
    
    2.convertView = mInflater.inflate(R.layout.item, null,false);
    
    
    

    这样添加会异常

    convertView = mInflater.inflate(R.layout.item, parent);
    
    @Override
    public void addView(View child, LayoutParams params) {
            throw new UnsupportedOperationException("addView(View, LayoutParams) "
                    + "is not supported in AdapterView");
    }
    
    • Activity 的布局填充
    public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
    }
    
    //PhoneWindow实现
    @Override
    public void setContentView(int layoutResID) {
            
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent); //将布局view放入到DecorVew
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
    }
    

    将布局view放入到DecorVew

    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    
     mLayoutInflater.inflate(layoutResID, mContentParent);
    
    • Dialog

    Dialog布局

     final Window w = new PhoneWindow(mContext);
     
     public void setContentView(@LayoutRes int layoutResID) {
            mWindow.setContentView(layoutResID);
     }
     
    

    DecorView布局参数

     mDecor = mWindow.getDecorView();
    if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
        final ApplicationInfo info = mContext.getApplicationInfo();
        mWindow.setDefaultIcon(info.icon);
        mWindow.setDefaultLogo(info.logo);
        mActionBar = new WindowDecorActionBar(this);
    }
    
    WindowManager.LayoutParams l = mWindow.getAttributes();
    if ((l.softInputMode
            & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
        WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
        nl.copyFrom(l);
        nl.softInputMode |=
                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
        l = nl;
    }
    
    mWindowManager.addView(mDecor, l);
    
    

    相关文章

      网友评论

          本文标题: View的LayoutInflater源码分析

          本文链接:https://www.haomeiwen.com/subject/hiqdrxtx.html