LayoutInflater与attachToRoot杂谈笔记

LayoutInflater笔记杂谈

在初学Android的时候,学到Fragment知识点,一些教程里面都在讲:“Inflate的时候,要把最后一个参数设置成false,暂时不用理解为什么”…

所以在这里挂个参数: attachToRoot,它到底是干啥,它到底有多坑

来自群里讨论的一个问题

为什么我在Fragment创建View的时候 attachToRoot = true,创建的时候会崩,报错The specified child already has a parent. You must call removeView

说到这个错误,还是之前在拆分view的时候遇见,触发的原因是:一个view被重复添加到Container之中(之前认为的是,一个view被addview到ViewGroup1,然后又把它addview到ViewGroup2,就抛出异常)。那么在Fragment创建view的时候爆出这个异常,那想必是触发了这个异常

为什么会触发

我们已知的:

  • 一个view被重复添加到Container的时候触发
  • 我们对view的操作就是Inflate,然后作为重写函数的返回值

探索:attachToRoot做了什么

当然要点进去源码看了(QAQ

我根据经验的猜测是,attachToRoot = true的时候,会自动addviewparent中。

attachToRoot 文档注释-> 
boolean: Whether the inflated hierarchy should be attached to the root parameter? If false, root is only used to create the correct subclass of LayoutParams for the root view in the XML.

看源码:追踪attachToRoot

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

root就是传入的Container

所以在attachToRoot = true的时候,会自动添加到parent里面

为什么会出现重复添加异常

你可以会疑问,attachToRoot也就是自动addview啊,为什么会异常?

怎么办?为什么?

这个异常是由view被两次添加触发的,所以我们的追踪线索应该是 -> 这个view

View哪去了?

当然被oncreateView给return了。所以我们开始追踪onCreateView的返回值

它被performCreateView调用

View performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        if (mChildFragmentManager != null) {
            mChildFragmentManager.noteStateNotSaved();
        }
        mPerformedCreateView = true;
        return onCreateView(inflater, container, savedInstanceState);
    }

然后追踪performCreateView

我们来到了FragmentManager

  f.mContainer = container;
  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) {
          container.addView(f.mView); // 这里!看到了吧!热心的FragmentManager帮你addview了
      }
      if (f.mHidden) {
          f.mView.setVisibility(View.GONE);
      }
      f.onViewCreated(f.mView, f.mSavedFragmentState);
      dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
              false);
      // Only animate the view if it is visible. This is done after
      // dispatchOnFragmentViewCreated in case visibility is changed
      f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
              && f.mContainer != null;
  } else {
      f.mInnerView = null;
  }

结果我们发现热心的FragmentManager帮我们addview了,所以两次addview直接导致异常

我们可以从addview的源码一步步翻腾,直到addViewInner方法,这个方法里面就有了抛出这个异常的逻辑

private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        if (mTransition != null) {
            // Don't prevent other add transitions from completing, but cancel remove
            // transitions to let them complete the process before we add to the container
            mTransition.cancel(LayoutTransition.DISAPPEARING);
        }

        if (child.getParent() != null) {
            throw new IllegalStateException("The specified child already has a parent. " +
                    "You must call removeView() on the child's parent first.");
        }
    .......
}

很明显了,只要child.getParent() != null就直接崩了

事后疑惑

FragmentManger里面f.mContainer = container;的这个Container是谁?是不是我们oncreateView的那个?

    if (f.mContainerId != 0) {
        if (f.mContainerId == View.NO_ID) {
            throwException(new IllegalArgumentException(
                    "Cannot create fragment "
                            + f
                            + " for a container view with no id"));
        }
        container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);

然后我们来看mContainerId是个啥

// When a fragment is being dynamically added to the view hierarchy, this
// is the identifier of the parent container it is being added to.
int mContainerId;

但是我查找了mContainerId的调用却并没有找到什么😓

于是我们来倒着找,从oncreateView的调用里面找(谁用了oncreateview)

f.mView = f.performCreateView(f.performGetLayoutInflater(
                                    f.mSavedFragmentState), container, f.mSavedFragmentState);
View performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        if (mChildFragmentManager != null) {
            mChildFragmentManager.noteStateNotSaved();
        }
        mPerformedCreateView = true;
        return onCreateView(inflater, container, savedInstanceState);
    }
onCreateView(inflater, container, savedInstanceState);

所以就很明显了,调用performCreateView的时候,传入的Container就是那个Container

这个方法怎么这么眼熟呢???

没错,就是它,这句调用的后面,就把view给add到Container里面了

    f.mContainer = container;
    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) {
            container.addView(f.mView); // 这里
        }

小结

问题在:attachToRoot = true的时候,view被两次add到Container里面,触发异常

题外坑

attachToRoot = true只有这一个坑吗?不

attachToRoot = true的时候,Inflate方法返回的view不是你想要的view,而是那个ViewGroup

官方文档悄悄地写在了return里面

The root View of the inflated hierarchy. If root was supplied and attachToRoot is true, this is root; otherwise it is the root of the inflated XML file.

一直没留意...背锅

(我差点就要把源码分析写了… 因为我当时是看源码发现这个“坑”的)

一点小思考

既然我不需要自动addview了,那么parent是用来干嘛的?

ViewGroup: Optional view to be the parent of the generated hierarchy (if attachToRoot is true), or else simply an object that provides a set of LayoutParams values for root of the returned hierarchy (if attachToRoot is false.)
This value may be null.

用来提供LayoutParams来调整生成的view (也可以为空)

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

推荐阅读更多精彩内容