LayoutInflater attachToRoot参数是什么意思?


201

对于参数LayoutInflater.inflate的用途,我的文档尚不清楚attachToRoot

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

有人可以详细解释一下,什么是根视图,还可以举例说明truefalse值之间的行为变化吗?



Answers:


157

现在或现在

这是“第三个”参数attachToRoot是true还是false之间的主要区别。

当您放置attachToRoot

true:将子视图添加到父视图RIGHT NOW
false:将子视图添加到父视图NOT NOW
以后添加。`

如果是

那是当你使用例如 parent.addView(childView)

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

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

相当于

inflater.inflate(child,parent,true);

BIG NO-NO
当您不负责将子视图添加到父视图时,绝对不要将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。但是我还没有测试过。


6
非常有帮助,特别是关于的部分FragmentManager,谢谢!
CybeX

94

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

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


17
我很困惑。我得到一个“指定的孩子已经有一个父错误”,直到我读了这个答案,它指示我使用falseattachToRoot过程中我的片段的onCreateView,这解决了这个问题,但该片段的布局是可见和活动的,尽管你的答案。这是怎么回事在这里吗?
Jeff Axelrod

67
因为片段会自动附加从onCreateView返回的布局。因此,如果您在onCreateView中手动将其附加,则您的视图将附加到2个父对象(这会产生您提到的错误)。
约瑟夫伯爵

11
我在这里有点困惑,@ JosephEarl,您说如果设置为true,则视图附加到第二个参数,即container,但是您说片段自动从附加onCreateView(),所以据我所知,第三个参数无用,应设置false总是?
unmultimedio 2014年

5
您在oncreateview中返回视图,然后将其自动附加。如果将attach设置为true,则会引发错误。但是,在独立情况下为视图充气时,可以通过设置为true来选择将视图自动附加到其容器。尽管我总是自己添加视图,但我几乎从未设置为true。
frostymarvelous 2014年

7
@unmultimedio它仅对所返回的根视图无用onCreateView。如果您将更多布局放大到该根视图中,或者在其他上下文中(例如在“活动”中)进行放大,则它很有用。
约瑟夫·厄尔

36

响应中似乎有很多文本,但没有代码,这就是为什么我决定用一个代码示例来重现这个旧问题,在一些人提到的响应中:

如果设置为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);
    }
}

请注意,先前的代码由于param is 将布局R.layout.child_view作为子项添加,并且以与我将以编程方式使用或就像在xml中这样做一样的方式完全分配父项的布局参数:MyCustomLayoutattachToRoottrueaddView

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

以下代码说明了attachRoot以as 传递时的方案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);

要指定它是它自己的根。


1
最重要的是,这是最有用的。
Wahib Ul Haq

26

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

inflate方法用于膨胀布局文件。对于那些膨胀的布局,您必须能够将它们直接附加到父级,ViewGroup或者只是从该布局文件中膨胀视图层次结构,然后在普通视图层次结构之外使用它。

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

对于第二个选项,返回的ViewViewGroup布局文件的根。如果您还记得我们关于结include-merge对问题的最后一次讨论,这是merge限制的原因之一(当merge以root为根的布局文件被夸大时,您必须提供一个父项,并且attachedToRoot必须将其设置为true)。如果您有一个带有根merge标签的布局文件,并将attachedToRoot其设置为,false则该inflate方法将没有任何返回值,因为merge没有等效项。另外,如文档所述inflateattachToRoot设置为的版本false很重要,因为您可以使用正确的视图创建视图层次LayoutParams来自父母。在某些情况下,这一点很重要,最值得注意的是AdapterView的子类(的子类)ViewGroup,对此addView()方法集不支持。我确定您记得在getView()方法中使用以下行:

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

这行代码可以确保膨胀后的R.layout.row_layout文件在根目录上具有正确LayoutParamsAdapterView子类集ViewGroup。如果您不打算这样做,那么如果根目录为,则布局文件可能会出现问题RelativeLayout。该TableLayout/TableRow也有一些特殊的,重要的LayoutParams,你应该确保在他们的观点有正确的LayoutParams


18

我自己也搞不清什么是真正的目的attachToRootinflate方法。经过一些UI研究,我终于得到了答案:

父母:

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

attachToRoot:

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

希望这能消除混乱


你的答案是已经在这里提供:stackoverflow.com/questions/22326314/...
霓虹灯Warge

11

我写了这个答案,因为即使经过几个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)方法手动添加视图。

当将Fragment的attachToRoot设置为true时,为什么人们会收到IllegalStateException。

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

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

插件(INT父母,片段片段)增加了它有它的布局父布局的片段。如果将attachToRoot设置为true,则将得到IllegalStateException:指定的子代已经有一个父代。由于片段布局已通过add()方法添加到父布局中。

放大Fragments时,应始终为attachToRoot传递false。添加,删除和替换Fragments是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)

简单明了的解释,正是我想要的!
flyingAssistant

10

由于有关inflate()方法的文档,因此在此主题上有很多困惑。

通常,如果将attachToRoot设置为true,则此时会膨胀第一个参数中指定的布局文件并将其附加到第二个参数中指定的ViewGroup。当attachToRoot为false时,第一个参数的布局文件会膨胀并作为View返回,并且任何View附件都在其他时间发生。

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

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

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


4

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

对于不熟悉Android的iOS开发人员,attachToRoot设置为true表示您调用此方法:

[parent addSubview:inflatedView];

如果走得更远,您可能会问:如果设置attachToRoot为,为什么要通过父视图false?这是因为XML树中的根元素需要父视图来计算一些LayoutParam(例如匹配父对象)。


0

当您定义父项时,attachToRoot决定是否要让充气机将其实际附加到父项。在某些情况下,这会导致问题,例如在ListAdapter中,这将导致异常,因为列表试图将视图添加到列表中,但它表示视图已附加。在其他情况下,您只是自己扩大视图以添加到“活动”中,这可能很方便,并且可以节省一行代码。


1
没有给出一个清晰的图片,应该提供一个好的答案。
Prakhar1001

0

例如,我们有一个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。如果要设置像setImageResource(R.drawable.np);ImageView 这样的参数,则必须通过父对象的引用来找到它,即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将为null。
  3. LayoutParams将为LinearLayout。

0

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>

现在,我们要以编程方式将此按钮添加到Fragment或Activity内的LinearLayout中。如果我们的LinearLayout已经是成员变量mLinearLayout,则只需添加以下内容即可:

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

我们指定要从其布局资源文件中扩展Button;然后,我们告诉LayoutInflater我们要将其附加到mLinearLayout。我们的布局参数很受重视,因为我们知道Button已添加到LinearLayout中。Button的布局参数类型应为LinearLayout.LayoutParams。

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

如果将attachToRoot设置为false,则将对第一个参数中指定的布局文件进行放大,并且将其附加到第二个参数中指定的ViewGroup,但是该放大后的视图将获取父级的LayoutParams,从而使该视图正确地适合父级。


让我们看一下您何时需要将attachToRoot设置为false。在这种情况下,此时inflate()的第一个参数中指定的视图未附加到第二个参数中的ViewGroup。

回想一下我们之前的Button示例,在该示例中,我们要将自定义Button从布局文件附加到mLinearLayout。我们仍可以通过为attachToRoot传入false来将Button附加到mLinearLayout上-我们只是随后手动添加它。

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

这两行代码等效于我们在为attachToRoot传递true时在一行代码中编写的内容。通过传入false,我们说我们现在还不想将View附加到根ViewGroup。我们说这将在其他时间发生。在此示例中,另一个时间点就是紧随通胀之后使用的addView()方法。

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

attachToRoot设置为false(必需为false)

当在onCreateView()中展开并返回Fragment的View时,请确保为AttachToRoot传递false。如果传入true,则将得到IllegalStateException,因为指定的子代已经有一个父代。您应该已经指定了片段视图在活动中的放置位置。添加,删除和替换Fragments是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();
}

将Fragment保留在Activity中的root_viewGroup容器是Fragment的onCreateView()中给您的ViewGroup参数。这也是您传递给LayoutInflater.inflate()的ViewGroup。但是,FragmentManager将处理将片段的视图附加到此ViewGroup的操作。您不想附加两次。将attachToRoot设置为false。

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

return view;
}

如果不想将片段的父ViewGroup附加在onCreateView()中,为什么要赋予它第一位呢?为什么inflate()方法请求根ViewGroup?

事实证明,即使当我们不立即将我们新添加的View添加到其父ViewGroup时,我们仍然应该使用父View的LayoutParams,以便新View在最终连接时确定其大小和位置。

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


0

只是分享我在处理此主题时遇到的一些要点,

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

因此,当我将attachToRoot用作true时,返回的视图的类型为ViewGroup,即作为参数传递给inflate(layoutResource,ViewGroup,attachToRoot)方法的参数的父级根ViewGroup,而不是所传递的布局类型,而是在attachToRoot上如果为false,则获取该layoutResource的根ViewGroup的函数返回类型。

让我用一个例子来解释:

如果我们将LinearLayout作为布局,那么我们想通过inflate函数在其中添加TextView

然后在使用attachToRoot作为true膨胀函数时返回类型为LinearLayoutView

在将attachToRoot作假膨胀函数时,将返回TextView类型的View

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.