【问题标题】:What does the LayoutInflater attachToRoot parameter mean?LayoutInflater attachToRoot 参数是什么意思?
【发布时间】:2012-09-16 01:30:56
【问题描述】:

LayoutInflater.inflate 文档对我来说并不清楚attachToRoot 参数的用途。

attachToRoot:膨胀的层次结构是否应该附加到根参数?如果为 false,则 root 仅用于创建正确的 XML 中根视图的 LayoutParams 的子类。

谁能更详细地解释一下,特别是根视图是什么,并可能显示truefalse 值之间的行为变化示例?

【问题讨论】:

标签: android android-layout android-view layout-inflater


【解决方案1】:

现在还是现在

“第三个”参数attachToRoot 是真还是假的主要区别是这个。

当你把 attachToRoot

true : 将子视图添加到父视图 现在
false: 将子视图添加到父视图 现在不
稍后添加。 `

稍后是什么时候?

稍后是当您使用例如parent.addView(childView)

一个常见的误解是,如果 attachToRoot 参数为 false,那么子视图将不会添加到父视图。 错误
在这两种情况下,子视图都会被添加到父视图中。这只是时间的问题。

inflater.inflate(child,parent,false);
parent.addView(child);   

等价于

inflater.inflate(child,parent,true);

大忌
当您不负责将子视图添加到父视图时,切勿将 attachToRoot 传递为 true。
例如添加片段时

public View onCreateView(LayoutInflater inflater,ViewGroup parent,Bundle bundle)
  {
        super.onCreateView(inflater,parent,bundle);
        View view = inflater.inflate(R.layout.image_fragment,parent,false);
        .....
        return view;
  }

如果你将第三个参数作为 true 传递,你会因为这个人而得到 IllegalStateException。

getSupportFragmentManager()
      .beginTransaction()
      .add(parent, childFragment)
      .commit();

由于您已经错误地在 onCreateView() 中添加了子片段。调用 add 将告诉您子视图已添加到父视图因此 IllegalStateException
这里你不负责添加childView,FragmentManager负责。所以在这种情况下总是传递 false 。

注意:我还了解到,如果 attachToRoot 为 false,则 parentView 将不会获得 childView touchEvents。不过我还没有测试过。

【讨论】:

  • 非常有帮助,尤其是关于FragmentManager的部分,谢谢!
  • 解释得很好。这应该是 Android 文档的一部分。当我阅读 android 文档时,我并不清楚
【解决方案2】:

如果设置为 true,那么当您的布局膨胀时,它将自动添加到第二个参数中指定的 ViewGroup 的视图层次结构中作为子级。例如,如果根参数是 LinearLayout,那么您的膨胀视图将自动添加为该视图的子视图。

如果设置为 false,那么您的布局将被放大,但不会附加到任何其他布局(因此不会被绘制、接收触摸事件等)。

【讨论】:

  • 我很困惑。在我阅读 this answer 之前,我收到“指定的孩子已经有父错误”,这指示我在 Fragment 的 onCreateView 期间将 false 用于 attachToRoot。这解决了问题,但片段的布局是可见的和活跃,尽管你的回答。这是怎么回事?
  • 因为一个 Fragment 会自动附加从 onCreateView 返回的布局。因此,如果您在 onCreateView 中手动附加它,那么您的视图将附加到 2 个父级(这会产生您提到的错误)。
  • 我在这里有点困惑,@JosephEarl 你说如果设置为true,则视图附加到第二个参数container,但是你说片段是自动附加的onCreateView(),所以据我了解,第三个参数没用,应该一直设置false
  • 您在 oncreateview 中返回视图,然后自动附加。如果将附加设置为 true,则会引发错误。但是,当您在独立情况下为视图膨胀时,您可以通过设置为 true 来选择自动将视图附加到其容器。我几乎没有设置为 true,因为我总是自己添加视图。
  • @unmultimedio 仅对onCreateView 返回的根视图无用。如果您将进一步的布局膨胀到该根视图中,或者您在不同的上下文中(例如在 Activity 中)膨胀,那么它很有用。
【解决方案3】:

回复中似乎有很多文字但没有代码,这就是为什么我决定用一个代码示例来重提这个老问题,在人们提到的几个回复中:

如果设置为 true,那么当您的布局膨胀时,它将自动添加到第二个参数中指定的 ViewGroup 的视图层次结构中。

这在代码中的实际含义(大多数程序员都理解)是:

public class MyCustomLayout extends LinearLayout {
    public MyCustomLayout(Context context) {
        super(context);
        // Inflate the view from the layout resource and pass it as child of mine (Notice I'm a LinearLayout class).

        LayoutInflater.from(context).inflate(R.layout.child_view, this, true);
    }
}

请注意,前面的代码将布局 R.layout.child_view 添加为 MyCustomLayout 的子级,因为 attachToRoot 参数是 true 并以与使用 @ 相同的方式分配父级的布局参数987654326@ 以编程方式,或者好像我在 xml 中执行此操作:

<LinearLayout>
   <View.../>
   ...
</LinearLayout>

以下代码解释了将attachRoot 传递为false 时的场景:

LinearLayout linearLayout = new LinearLayout(context);
linearLayout.setLayoutParams(new LayoutParams(
    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
linearLayout.setOrientation(LinearLayout.VERTICAL);
    // Create a stand-alone view
View myView = LayoutInflater.from(context)
    .inflate(R.layout.ownRootView, null, false);
linearLayout.addView(myView);

在前面的代码中,您指定您希望 myView 成为它自己的根对象并且不将其附加到任何父对象,后来我们将其添加为 LinearLayout 的一部分,但有一段时间它是一个立场- 单独(无父)视图。

片段也会发生同样的事情,您可以将它们添加到已经存在的组中并成为其中的一部分,或者只是传递参数:

inflater.inflate(R.layout.fragment, null, false);

指定它将是它自己的根。

【讨论】:

  • 总之,这是最有帮助的。
【解决方案4】:

文档和前两个答案应该足够了,只是我的一些想法。

inflate 方法用于扩充布局文件。对于那些膨胀的布局,您必须能够将它们直接附加到父 ViewGroup 或只是从该布局文件膨胀视图层次结构并在正常视图层次结构之外使用它。

在第一种情况下,attachToRoot 参数必须设置为true(或者更简单的使用inflate 方法,该方法采用布局文件和父根ViewGroup(非null)) .在这种情况下,返回的View 只是在方法中传递的ViewGroup,即添加膨胀视图层次结构的ViewGroup

对于第二个选项,返回的View 是布局文件中的根ViewGroup。如果你还记得我们上次在include-merge pair question 中的讨论,这是merge 限制的原因之一(当以merge 为根的布局文件被膨胀时,您必须提供父级并且attachedToRoot 必须是设置为true)。如果您的布局文件的根为 merge 标记,并且 attachedToRoot 设置为 false,那么 inflate 方法将没有任何返回值,因为 merge 没有等效项。 此外,正如文档所述,将attachToRoot 设置为falseinflate 版本很重要,因为您可以从父级创建具有正确LayoutParams 的视图层次结构。这在某些情况下很重要,最值得注意的是 AdapterView 的子类,ViewGroup 的子类,不支持 addView() 方法集。我相信你记得在 getView() 方法中使用过这一行:

convertView = inflater.inflate(R.layout.row_layout, parent, false);

此行确保膨胀的R.layout.row_layout 文件在其根ViewGroup 上具有来自AdapterView 子类集的正确LayoutParams。如果您不这样做,如果根是RelativeLayout,布局文件可能会出现一些问题。 TableLayout/TableRow 也有一些特殊而重要的LayoutParams,您应该确保其中的视图具有正确的LayoutParams

【讨论】:

    【解决方案5】:

    我自己也对attachToRootinflate 方法中的真正用途感到困惑。经过一番UI研究,我终于得到了答案:

    父母:

    在这种情况下,是围绕您要使用 findViewById() 膨胀的视图对象的小部件/布局。

    attachToRoot:

    将视图附加到它们的父视图(包括它们在父层次结构中),因此视图接收到的任何触摸事件也将被转移到父视图。现在由父级决定是接受这些事件还是忽略它们。如果设置为 false,它们不会被添加为父级的直接子级,并且父级不会从视图中接收任何触摸事件。

    希望这能消除混乱

    【讨论】:

    【解决方案6】:

    我写了这个答案,因为即使在浏览了几个 StackOverflow 页面后,我也无法清楚地理解 attachToRoot 的含义。下面是 LayoutInflater 类中的 inflate() 方法。

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

    看看我创建的 activity_main.xml 文件、button.xml 布局和 MainActivity.java 文件。

    activity_main.xml

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
    </LinearLayout>
    

    button.xml

    <Button xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    

    MainActivity.java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        LayoutInflater inflater = getLayoutInflater();
        LinearLayout root = (LinearLayout) findViewById(R.id.root);
        View view = inflater.inflate(R.layout.button, root, false);
    }
    

    当我们运行代码时,我们不会在布局中看到按钮。这是因为我们的按钮布局没有添加到主活动布局中,因为 attachToRoot 设置为 false。

    LinearLayout 有一个 addView(View view) 方法,可用于将 View 添加到 LinearLayout。这会将按钮布局添加到主活动布局中,并在您运行代码时使按钮可见。

    root.addView(view);
    

    让我们删除上一行,看看当我们将 attachToRoot 设置为 true 时会发生什么。

    View view = inflater.inflate(R.layout.button, root, true);
    

    我们再次看到按钮布局是可见的。这是因为 attachToRoot 直接将膨胀的布局附加到指定的父级。在这种情况下是根 LinearLayout。在这里,我们不必像之前使用 addView(View view) 方法那样手动添加视图。

    为什么人们在将片段的 attachToRoot 设置为 true 时会收到 IllegalStateException。

    这是因为对于片段,您已经指定了在活动文件中放置片段布局的位置。

    FragmentManager fragmentManager = getSupportFragmentManager();
    fragmentManager.beginTransaction()
        .add(R.id.root, fragment)
        .commit();
    

    add(int parent, Fragment fragment) 将具有其布局的片段添加到父布局。如果我们将 attachToRoot 设置为 true,您将得到 IllegalStateException: The specified child has a parent。由于片段布局已经在 add() 方法中添加到父布局中。

    在对 Fragment 进行膨胀时,您应该始终为 attachToRoot 传递 false。添加、删除和替换 Fragment 是 FragmentManager 的工作。

    回到我的例子。如果我们两者都做呢。

    View view = inflater.inflate(R.layout.button, root, true);
    root.addView(view);
    

    在第一行中,LayoutInflater 将按钮布局附加到根布局并返回一个包含相同按钮布局的 View 对象。在第二行中,我们将相同的 View 对象添加到父根布局。这会导致我们在 Fragments 中看到的相同的 IllegalStateException(指定的子级已经有父级)。

    请记住,还有另一个重载的 inflate() 方法,它默认将 attachToRoot 设置为 true。

    View inflate (int resource, ViewGroup root)
    

    【讨论】:

    • 简单明了的解释,正是我想要的!
    • 这是这里唯一的答案,它解释了attachToRoot 在省略时默认设置为true(这是反直觉的)
    • 解释清楚!!消除了这方面的困惑
    【解决方案7】:

    由于 inflate() 方法的文档,在这个主题上有很多混淆。

    一般来说,如果attachToRoot设置为true,那么第一个参数中指定的布局文件会在那个时刻被膨胀并附加到第二个参数中指定的ViewGroup。当 attachToRoot 为 false 时,第一个参数中的布局文件会膨胀并作为视图返回,并且任何视图附件都会在其他时间发生。

    除非您看到很多示例,否则这可能意义不大。在 Fragment 的 onCreateView 方法中调用 LayoutInflater.inflate() 时,您需要为 attachToRoot 传入 false,因为与该 Fragment 关联的 Activity 实际上负责添加该 Fragment 的视图。如果您在稍后的某个时间点手动膨胀并添加一个视图到另一个视图,例如使用 addView() 方法,您将需要为 attachToRoot 传递 false,因为附件会在稍后的时间点出现。

    您可以在我写的关于这个主题的博客文章中阅读有关对话框和自定义视图的其他几个独特示例。

    https://www.bignerdranch.com/blog/understanding-androids-layoutinflater-inflate/

    【讨论】:

      【解决方案8】:

      attachToRoot 设置为 true 意味着 inflatedView 将被添加到父视图的层次结构中。因此,用户可能会“看到”并感知触摸事件(或任何其他 UI 操作)。否则,它只是被创建,没有被添加到任何视图层次结构中,因此无法看到或处理触摸事件。

      对于刚接触 Android 的 iOS 开发者,attachToRoot 设置为 true 表示您调用此方法:

      [parent addSubview:inflatedView];
      

      如果更进一步你可能会问:如果我将attachToRoot 设置为false,为什么还要传递父视图?这是因为您的 XML 树中的根元素需要父视图来计算一些 LayoutParams(如匹配父级)。

      【讨论】:

        【解决方案9】:

        当您定义父级时,attachToRoot 确定您是否希望充气器实际将其附加到父级。在某些情况下,这会导致问题,例如在 ListAdapter 中它会导致异常,因为列表尝试将视图添加到列表中,但它说它已经附加。在其他情况下,您只是自己膨胀视图以添加到 Activity 中,它可能会很方便并为您节省一行代码。

        【讨论】:

        • 没有给出一个好的答案应该提供的清晰图片。
        【解决方案10】:

        例如,我们有一个 ImageView 、一个 LinearLayout 和一个 RelativeLayout。 LinearLayout 是RelativeLayout 的子级。 视图层次结构将是。

        RelativeLayout
                   ------->LinearLayout
        

        我们有一个单独的 ImageView 布局文件

        image_view_layout.xml

        附加到根目录:

        //here container is the LinearLayout
        
            View v = Inflater.Inflate(R.layout.image_view_layout,container,true);
        
        1. 这里 v 包含容器布局的引用,即 LinearLayout.and 如果你想设置 ImageView 的 setImageResource(R.drawable.np); 之类的参数,你必须通过父级的引用找到它,即 view.findById()
        2. v 的父级将是 FrameLayout。
        3. LayoutParams 将属于 FrameLayout。

        不附加到根目录:

        //here container is the LinearLayout
            View v = Inflater.Inflate(R.layout.image_view_layout,container,false);
        
        1. 这里 v 包含无参考容器布局,但直接 对膨胀的 ImageView 的引用,以便您可以设置其 view.setImageResource(R.drawable.np); 之类的参数没有 像findViewById 这样的裁判。但是指定了容器,以便 ImageView 获取容器的 LayoutParams 所以你可以说 容器的引用仅用于 LayoutParams 没有 否则。
        2. 因此在特定情况下 Parent 将为空。
        3. LayoutParams 将是 LinearLayout。

        【讨论】:

          【解决方案11】:

          attachToRoot 设置为 true:

          如果 attachToRoot 设置为 true,则第一个参数中指定的布局文件会膨胀并附加到第二个参数中指定的 ViewGroup。

          假设我们在 XML 布局文件中指定了一个按钮,其布局宽度和布局高度设置为 match_parent。

          <Button xmlns:android="http://schemas.android.com/apk/res/android"
                      android:layout_width="match_parent"
                      android:layout_height="match_parent"
                      android:id="@+id/custom_button">
          </Button>
          

          我们现在想以编程方式将此按钮添加到片段或活动内的线性布局中。如果我们的LinearLayout已经是一个成员变量MLinearLayout,我们可以简单地添加如下按钮:

          inflater.inflate(R.layout.custom_button, mLinearLayout, true);
          

          我们指定要从其布局资源文件中为 Button 充气;然后我们告诉 LayoutInflater 我们想将它附加到 mLLinearLayout。我们的布局参数得到尊重,因为我们知道 Button 被添加到了 LinearLayout。 Button 的布局参数类型应为 LinearLayout.LayoutParams。

          attachToRoot 设置为 false(不需要使用 false)

          如果 attachToRoot 设置为 false,则第一个参数中指定的布局文件会被膨胀,并且附加到第二个参数中指定的 ViewGroup 但膨胀的 视图获取父级的 LayoutParams 使该视图能够正确地适合父视图。


          让我们看看您何时希望将 attachToRoot 设置为 false。在这种情况下,inflate()的第一个参数中指定的View此时并没有附加到第二个参数中的ViewGroup。

          回想一下前面的 Button 示例,我们希望将自定义 Button 从布局文件附加到 mLinearLayout。我们仍然可以通过为 attachToRoot 传递 false 来将我们的 Button 附加到 mLLinearLayout——我们只是在之后手动添加它。

          Button button = (Button) inflater.inflate(R.layout.custom_button,    mLinearLayout, false);
          mLinearLayout.addView(button);
          

          这两行代码相当于我们之前在一行代码中为 attachToRoot 传入 true 时所写的内容。通过传入 false,我们说我们现在还不想将 View 附加到根 ViewGroup。我们说它会在其他时间点发生。在这个例子中,另一个时间点只是在通货膨胀下面使用的 addView() 方法。

          当我们手动将视图添加到视图组时,错误的 attachToRoot 示例需要更多工作。

          attachToRoot 设置为 false(需要 false)

          在 onCreateView() 中膨胀和返回 Fragment 的 View 时,一定要为 attachToRoot 传入 false。如果你传入 true,你会得到一个 IllegalStateException 因为指定的孩子已经有一个父母。您应该已经指定了 Fragment 视图将放回 Activity 的位置。添加、删除和替换 Fragment 是 FragmentManager 的工作。

          FragmentManager fragmentManager = getSupportFragmentManager();
          Fragment fragment =  fragmentManager.findFragmentById(R.id.root_viewGroup);
          
          if (fragment == null) {
          fragment = new MainFragment();
          fragmentManager.beginTransaction()
              .add(R.id.root_viewGroup, fragment)
              .commit();
          }
          

          将在 Activity 中保存 Fragment 的 root_viewGroup 容器是在 Fragment 的 onCreateView() 中提供给您的 ViewGroup 参数。它也是您传递给 LayoutInflater.inflate() 的 ViewGroup。然而,FragmentManager 将处理将 Fragment 的 View 附加到这个 ViewGroup。您不想将其附加两次。将 attachToRoot 设置为 false。

          public View onCreateView(LayoutInflater inflater, ViewGroup  parentViewGroup, Bundle savedInstanceState) {
          View view = inflater.inflate(R.layout.fragment_layout,     parentViewGroup, false);
          …
          return view;
          }
          

          如果我们不想在 onCreateView() 中附加 Fragment 的父 ViewGroup,为什么首先要给它呢?为什么 inflate() 方法请求根 ViewGroup?

          事实证明,即使我们没有立即将新膨胀的 View 添加到其父 ViewGroup 中,我们仍应使用父 View 的 LayoutParams 以便新 View 在最终附加时确定其大小和位置。

          链接:https://youtu.be/1Y0LlmTCOkM?t=409

          【讨论】:

            【解决方案12】:

            只是分享我在研究这个主题时遇到的一些观点,

            除了已接受的答案之外,我还想提出一些可能会有所帮助的观点。

            所以,当我使用 attachToRoot 为 true 时,返回的视图是 ViewGroup 类型,即作为 inflate(layoutResource,ViewGroup,attachToRoot) 方法的参数传递的父级根 ViewGroup,不是传递的布局类型,而是 attachToRoot 为 false,我们得到该 layoutResource 的根 ViewGroup 的函数返回类型。

            让我用一个例子来解释:

            如果我们有一个 LinearLayout 作为 root 布局,然后我们想添加 TextView在里面通过inflate函数。

            然后在使用 attachToRoot 作为 true 膨胀函数返回 View LinearLayout

            类型的 em>

            当使用 attachToRoot 作为 false 膨胀函数返回 View TextView

            类型的 em>

            希望这个发现会有所帮助...

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-07-07
              • 2016-10-17
              • 2015-03-05
              • 2014-05-31
              • 2020-12-16
              • 2017-04-13
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多