View.inflate方法和LayoutInflater.from(context).inflate方法详解

  • 从xml中加载一个View,一般通过以下两个方法:View#inflate(Context context, @LayoutRes int resource, ViewGroup root)方法和LayoutInflater#inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)方法

  • View#inflate方法源码可以看到,它最终也是调用LayoutInflater#inflate方法

<!----------View----------->

  public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
    }
  • 跟着进入LayoutInflater#inflate方法源码
<!----------LayoutInflater------------>

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);   
    }
  • LayoutInflater两个参数的inflate方法最终又调用了三个参数的inflate方法
   public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
  
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
  • 三个参数的方法最终又调用了它的重载方法,真正的返回了View
 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

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

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // 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) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(parser.getPositionDescription()
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }
  • 为了方便分析取出关键代码如下
 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
            View result = root;
            
            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, inflaterContext, attrs, false);
            } else {
                // 获取xml中的根view
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                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)
                        //如果ViewGroup root不为null并且attachToRoot == false,为xml获取的根view temp设置 ViewGroup.LayoutParams
                        temp.setLayoutParams(params);
                    }
                }


                // 将temp下所有的子view添加到temp中
                rInflateChildren(parser, temp, attrs, true);  

                //如果ViewGroup root不为null并且attachToRoot == true,将temp添加到root中,并设置 ViewGroup.LayoutParams
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }

                // 如果传进来的ViewGroup root为null 或者attachToRoot == false,返回xml中的根view temp,其它情况返回传进来的ViewGroup root
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

            return result;  
    }
  • 从最后的方法中可以看到,关键的点在于传入的ViewGroup root的值和boolean attachToRoot的值,这两个值得取值不同,最终影响返回的是传入的ViewGroup root还是获取xml中的根view temp,并且影响是否为temp设置了ViewGroup.LayoutParams
  1. ViewGroup root == null && attachToRoot == false ====> 返回获取xml中的根view temp,同时temp并没有设置了ViewGroup.LayoutParams(此时如果获取LayoutParams可能为空)
  2. ViewGroup root == null && attachToRoot == true ====> 返回获取xml中的根view temp,同时temp并没有设置了ViewGroup.LayoutParams(此时如果获取LayoutParams可能为空)
  3. ViewGroup root != null && attachToRoot == false====>返回获取xml中的根view temp,同时设置了ViewGroup.LayoutParams
  4. ViewGroup root != null && attachToRoot == true====> 返回ViewGroup root,同时为获取到xml中的根view temp设置了ViewGroup.LayoutParams,并添加到ViewGroup root
  • 现在再来看View#inflate(Context context, @LayoutRes int resource, ViewGroup root)方法,它实际上调用了inflate(resource, root, root != null) ,也就是说有两种情况:
  1. 一种是 ViewGroup root == null && attachToRoot == false,此时需要注意的是返回的是xml布局的根View,并且并未为该根View设置ViewGroup.LayoutParams,在这种情况需要获取view的LayoutParams进行操作的需要特别注意
  2. 另外一种是ViewGroup root != null && attachToRoot == true,此时返回的是传入的ViewGroup root,同时为获取到xml中的根view temp设置了ViewGroup.LayoutParams,并添加到ViewGroup root,这种情况需要注意的是xml中的布局已经被添加到ViewGroup root中,如果需要添加到另外的地方,这种方法是不可行的
  • 也就是说对于View#inflate方法,想要满足ViewGroup root != null && attachToRoot == false是无法满足的。而如果需要使用xml中的根view的ViewGroup.LayoutParams,首先需要满足ViewGroup root不为空(当然在onLayout之后使用是可以的,此时已经有parent了),另外对于ReclclerView/ListView来说,它会自己在合适的时机将child添加进来,所以attachToRoot必须需要false,否则在渲染View的时候就会首先添加到ViewGroup root中,导致重复添加到parent中报错,因此View#inflate方法是无法满足的,需要使用LayoutInflater#inflate(resource,root,false)方法
  • 对于LayoutInflater#inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)方法,同样的也会根据传入的值得不同得到不同的结果,在选择使用哪种方法获取View的时候就需要考虑对于接下来要对View进行的操作是否有影响

  • 扩展
    在学习Fragment的时候,下面的写法应该是熟悉到不能再熟悉了

 @Nullable
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
            View view = inflater.inflate(layoutRes, container, false);
        return view ;
    }
  • View view = inflater.inflate(layoutRes, container, false);一直是我们的固定写法,我想一定有同学会跟我一样觉得为什么一定要false,改成true可不可以
public class TestViewInflaterFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.activity_main, container, true);
    }
}
  • 当把它添加到activity中的时候
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        supportFragmentManager.beginTransaction().add(R.id.fl_container, TestViewInflaterFragment()).commit()
    }
}
  • 下面的错误一定会教你老老实实做人,大家都是这样说用false是有道理的,但是我们除了记得需要用false,还是需要知道为什么一定要用false,而用true就不行
 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.test.demo/com.test.demo.MainActivity}: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
                                                                                  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2817)
                                                                                  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
                                                                                  at android.app.ActivityThread.-wrap11(Unknown Source:0)
                                                                                  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
                                                                                  at android.os.Handler.dispatchMessage(Handler.java:105)
                                                                                  at android.os.Looper.loop(Looper.java:164)
                                                                                  at android.app.ActivityThread.main(ActivityThread.java:6541)
                                                                                  at java.lang.reflect.Method.invoke(Native Method)
                                                                                  at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
                                                                                  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
                                                                               Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
                                                                                  at android.view.ViewGroup.addViewInner(ViewGroup.java:4915)
                                                                                  at android.view.ViewGroup.addView(ViewGroup.java:4746)
                                                                                  at android.view.ViewGroup.addView(ViewGroup.java:4686)
                                                                                  at android.view.ViewGroup.addView(ViewGroup.java:4659)
                                                                                  at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1425)
                                                                                  at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740)
                                                                                  at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1809)
                                                                                  at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:799)
                                                                                  at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2580)
                                                                                  at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2367)
                                                                                  at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2322)
                                                                                  at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2229)
                                                                                  at android.support.v4.app.FragmentManagerImpl.dispatchStateChange(FragmentManager.java:3221)
                                                                                  at android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:3171)
                                                                                  at android.support.v4.app.FragmentController.dispatchActivityCreated(FragmentController.java:192)
                                                                                  at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:560)
                                                                                  at android.support.v7.app.AppCompatActivity.onStart(AppCompatActivity.java:177)
                                                                                  at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1333)
                                                                                  at android.app.Activity.performStart(Activity.java:6992)
                                                                                  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2780)
                                                                                  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892) 
                                                                                  at android.app.ActivityThread.-wrap11(Unknown Source:0) 
                                                                                  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593) 
                                                                                  at android.os.Handler.dispatchMessage(Handler.java:105) 
                                                                                  at android.os.Looper.loop(Looper.java:164) 
                                                                                  at android.app.ActivityThread.main(ActivityThread.java:6541) 
                                                                                  at java.lang.reflect.Method.invoke(Native Method) 
                                                                                  at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) 
                                                                                  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767) 
  • 仔细看一看错误日志The specified child already has a parent. You must call removeView() on the child's parent first.,看这句结合前面说过的内容,我想大家肯定已经知道为什么了,当我们写成true的时候,会将xml创建的root view添加到container,然后推测Fragment被加载的时候在某个地方又将xml创建的root view再添加到一个ViewGroup中,所以会导致这个错误,继续看一看推测是否正确

  • 先看Fragment#onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)在哪里被调用

  View performCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (mChildFragmentManager != null) {
            mChildFragmentManager.noteStateNotSaved();
        }
        mPerformedCreateView = true;
        return onCreateView(inflater, container, savedInstanceState);
    }
  • 继续看Fragment#performCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)的调用
//-----------FramgnetManager---------------
 void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {   
                //省略部分代码
                  ``` 
            switch (f.mState) {
                  //省略部分代码
                  ``` 
                case Fragment.CREATED:
                    // This is outside the if statement below on purpose; we want this to run
                    // even if we do a moveToState from CREATED => *, CREATED => CREATED, and
                    // * => CREATED as part of the case fallthrough above.
                    ensureInflatedFragmentView(f);

                    if (newState > Fragment.CREATED) {
                        if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
                        if (!f.mFromLayout) {
                            ViewGroup container = null;
                            if (f.mContainerId != 0) {
                                if (f.mContainerId == View.NO_ID) {
                                    throwException(new IllegalArgumentException(
                                            "Cannot create fragment "
                                                    + f
                                                    + " for a container view with no id"));
                                }
                           //首先根据我们传入的mContainerId(对于本案例来说就是R.id.fl_container)找到container 
                                container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
                                if (container == null && !f.mRestored) {
                                    String resName;
                                    try {
                                        resName = f.getResources().getResourceName(f.mContainerId);
                                    } catch (NotFoundException e) {
                                        resName = "unknown";
                                    }
                                    throwException(new IllegalArgumentException(
                                            "No view found for id 0x"
                                            + Integer.toHexString(f.mContainerId) + " ("
                                            + resName
                                            + ") for fragment " + f));
                                }
                            }
                            f.mContainer = container;
                          //这个f.mView就是我们自己在onCreateView方法中创建返回的View
                            f.mView = f.performCreateView(f.performGetLayoutInflater(
                                    f.mSavedFragmentState), container, f.mSavedFragmentState);
                            if (f.mView != null) {
                                f.mInnerView = f.mView;
                                f.mView.setSaveFromParentEnabled(false);
                                if (container != null) {
                            //关键地方,在这里会将xml创建的view添加到container
                                    container.addView(f.mView);
                                }
                          //省略部分代码
                            ``` 
                    }

                   //省略部分代码
                  ``` 
            }
    }
  • 方法太长,为了便于查看省略了部分代码,看注释已经说明了前面的推测是正确的,当改为true的时候,在创建xml view的时候会将view add到传入的parent(container)中,然后将Fragment加载进来的时候,FragmentManager会再一次将创建的xml view添加到container(也就是我们指定的container id指向的ViewGroup)中,所以导致了重复添加一个view到ViewGroup中的错误。

  • 另外再说一个查看源码的小技巧,首先源码太多太复杂,一头扎进去可能就出不来了。首先需要抱着一个目的去查看源码,一般查看源码是为了验证一个东西或者学习源码是怎么样实现某种效果或者某个功能的,这就是这次查看源码的目的,主线。像前面就是为了验证是不是Fragment加载的时候会将创建的View添加到某个ViewGroup中,然后根据方法的调用去找寻可能是我们目的的代码,有时候有些方法的调用是很复杂的,有些方法也特别长,稍微不注意可能就错过了想要的内容。比如之前的Fragment#performCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)还有另外一个地方呗调用,可能我们会进来了再一直深入,然后很容易就被绕晕了

 void ensureInflatedFragmentView(Fragment f) {
        if (f.mFromLayout && !f.mPerformedCreateView) {
            f.mView = f.performCreateView(f.performGetLayoutInflater(
                    f.mSavedFragmentState), null, f.mSavedFragmentState);
            if (f.mView != null) {
                f.mInnerView = f.mView;
                f.mView.setSaveFromParentEnabled(false);
                if (f.mHidden) f.mView.setVisibility(View.GONE);
                f.onViewCreated(f.mView, f.mSavedFragmentState);
                dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState, false);
            } else {
                f.mInnerView = null;
            }
        }
    }
  • 另外FragmentManager#moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive)也非常长,怎么样才能提高找到线索的可能性呢? 还记的我们的目的吗?验证是不是Fragment加载的时候会将创建的View添加到某个ViewGroup,添加到ViewGroup,第一时间应该想到ViewGroup#addView方法,然后在对应的方法里面搜索一下addView,如果找到了有,再前后代码查看一下是不是我们的目的,一步一步排查下去最终解决我们的问题。
  • 这只是一个查看源码的小技巧,可能并不能每次都能解决所有的问题。但是可以帮我们少走一点弯路。毕竟一入源码深似海。而且最好不要抱着把源码里面的所有东西都看懂,每一个变量代表什么都去纠结(当然如果有这个能力的话也是可以的)。应该抱着学习和解决问题的目的,有针对性的去看源码,沿着一条主线去搞懂并解决我们遇到的问题。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容