View的LayoutInflater源码分析

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);

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容