O que significa o parâmetro LayoutInflater attachToRoot?

201

A LayoutInflater.inflatedocumentação não está exatamente clara para mim sobre o objetivo do attachToRootparâmetro.

attachToRoot : se a hierarquia inflada deve ser anexada ao parâmetro raiz? Se falso, a raiz será usada apenas para criar a subclasse correta de LayoutParams para a visualização raiz no XML.

Alguém poderia explicar com mais detalhes, especificamente qual é a visão raiz, e talvez mostrar um exemplo de mudança de comportamento entre valores truee false?

Jeff Axelrod
fonte
1
Relacionados: Fazer o sentido de LayoutInflater
blahdiblah

Respostas:

157

AGORA OU NÃO AGORA

A principal diferença entre o "terceiro" parâmetro attachToRoot ser verdadeiro ou falso é esse.

Quando você coloca o attachToRoot

true: adicione a visão filho ao pai RIGHT NOW
false: adicione a visão filho ao pai NOT NOW .
Adicione depois. `

Quando é que depois ?

Mais tarde é quando você usa, por exemplo, parent.addView(childView)

Um equívoco comum é que, se o parâmetro attachToRoot for false, a visualização filho não será adicionada ao pai. ERRADO
Nos dois casos, a exibição filho será adicionada ao parentView. É apenas uma questão de tempo .

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

é equivalente a

inflater.inflate(child,parent,true);

UM GRANDE NÃO-NÃO
Você nunca deve passar como attachToRoot como verdadeiro quando não for responsável por adicionar a exibição filho ao pai.
Por exemplo, ao adicionar fragmento

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

se você passar o terceiro parâmetro como verdadeiro, receberá IllegalStateException por causa desse cara.

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

Como você já adicionou o fragmento filho no onCreateView () por engano. Chamar add informará que a exibição filho já foi adicionada ao pai. Portanto, IllegalStateException .
Aqui você não é responsável por adicionar childView, o FragmentManager é responsável. Portanto, sempre passe falso neste caso.

NOTA: Também li que parentView não obterá childView touchEvents se o attachToRoot for falso. Mas eu ainda não testei.

Rohit Singh
fonte
6
Muito útil, principalmente a parte referente a FragmentManager, obrigado!
CybeX
94

Se definido como true, quando o layout for inflado, ele será automaticamente adicionado à hierarquia de exibição do ViewGroup especificado no segundo parâmetro como filho. Por exemplo, se o parâmetro raiz era a LinearLayout, sua visualização inflada será automaticamente adicionada como filha dessa visualização.

Se estiver definido como false, seu layout será inflado, mas não será anexado a nenhum outro layout (para que não seja desenhado, receba eventos de toque etc.).

Joseph Earl
fonte
17
Estou confuso. Eu estava ficando um "filho especificado já tem um erro de pai” até que eu li esta resposta , que dirigiu o meu usar falsepara attachToRootdurante a minha Fragmento de onCreateView. Isso resolveu o problema e ainda o layout do fragmento é visível e ativa, apesar de sua resposta. O que está acontecendo aqui?
Jeff Axelrod
67
Como um fragmento anexa automaticamente o layout retornado do onCreateView. Portanto, se você o anexar manualmente no onCreateView, sua visualização será anexada a 2 pais (o que produz o erro mencionado).
31812 Joseph Earl
11
Estou um pouco confuso aqui, @JosephEarl, você disse que, se definido como true, a visualização é anexada ao segundo parâmetro, que é o container, mas você diz que o fragmento é automaticamente anexado a partir de onCreateView(), de acordo com o meu entendimento, o terceiro parâmetro é inútil e deve ser definido falsesempre?
Unmultimedio 17/07/2014
5
Você retorna a exibição na visualização de criação, que é automaticamente anexada. Se você configurar o anexo como true, um erro será gerado. No entanto, quando você aumenta a exibição em uma situação independente, pode optar por anexar a exibição ao seu contêiner automaticamente, definindo como true. Eu quase nunca me tornei verdade, pois sempre adiciono a visualização.
Frostymarvelous
7
@unmultimedio é apenas inútil para a visualização raiz retornada por onCreateView. Se você inflar layouts adicionais nessa visualização raiz ou se estiver inflando em um contexto diferente (por exemplo, em uma Atividade), será útil.
Joseph Earl
36

Parece muito texto nas respostas, mas nenhum código, por isso decidi reviver essa pergunta antiga com um exemplo de código, em várias respostas mencionadas pelas pessoas:

Se definido como true, quando o layout for inflado, ele será automaticamente adicionado à hierarquia de exibição do ViewGroup especificado no segundo parâmetro como filho.

O que isso realmente significa no código (o que a maioria dos programadores entende) é:

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

Observe que o código anterior está adicionando o layout R.layout.child_viewcomo filho por MyCustomLayoutcausa de attachToRootparam is truee atribui os parâmetros de layout do pai exatamente da mesma maneira como se eu estivesse usando addViewprogramaticamente ou como se fizesse isso em xml:

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

O código a seguir explica o cenário ao passar attachRootcomo 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);

No código anterior, você especifica que deseja myViewser seu próprio objeto raiz e não o anexa a nenhum pai, posteriormente o adicionamos como parte da LinearLayoutmas, por um momento, foi uma exibição independente (sem pai).

O mesmo acontece com os fragmentos, você pode adicioná-los a um grupo já existente e fazer parte dele ou apenas passar os parâmetros:

inflater.inflate (R.layout.fragment, nulo, falso);

Para especificar que será sua própria raiz.

Martin Cazares
fonte
1
Fora de tudo, este foi o mais útil.
Wahib Ul Haq
26

A documentação e as duas respostas anteriores devem ser suficientes, apenas alguns pensamentos meus.

O inflatemétodo é usado para aumentar os arquivos de layout. Com esses layouts inflados, você tem a possibilidade de anexá-los diretamente a um pai ViewGroupou simplesmente aumentar a hierarquia de exibição desse arquivo de layout e trabalhar com ele fora da hierarquia de exibição normal.

No primeiro caso, o attachToRootparâmetro deverá ser definido como true(ou muito simples, use o inflatemétodo que utiliza um arquivo de layout e uma raiz pai ViewGroup(não null)). Nesse caso, o Viewretorno é simplesmente o ViewGroupque foi passado no método, ViewGroupao qual a hierarquia de exibição inflada será adicionada.

Para a segunda opção, o retornado Viewé a raiz ViewGroupdo arquivo de layout. Se você se lembra da nossa última discussão da include-mergequestão do par, este é um dos motivos da mergelimitação (quando um arquivo de layout com mergeraiz é inflado, você deve fornecer um pai e attachedToRootdeve estar definido como true). Se você tinha um arquivo de layout com a raiz uma mergetag e attachedToRootfoi definido como false, o inflatemétodo não terá nada para retornar, pois mergenão tem um equivalente. Além disso, como diz a documentação, a inflateversão attachToRootconfigurada como falseé importante porque você pode criar a hierarquia de visualizações com a corretaLayoutParamsdos pais. Isso é importante em alguns casos, principalmente com os filhos de AdapterView, uma subclasse de ViewGroup, para os quais o addView()conjunto de métodos não é suportado. Tenho certeza de que você se lembra de usar esta linha no getView()método:

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

Essa linha garante que o R.layout.row_layoutarquivo inflado tenha o correto LayoutParamsda AdapterViewsubclasse definida em sua raiz ViewGroup. Se você não estiver fazendo isso, poderá ter alguns problemas com o arquivo de layout se a raiz for a RelativeLayout. Eles TableLayout/TableRowtambém têm algumas coisas especiais e importantes LayoutParamse você deve garantir que as visualizações estejam corretas LayoutParams.

Luksprog
fonte
18

Eu também estava confuso sobre o que era o verdadeiro propósito attachToRootno inflatemétodo. Depois de um pouco de estudo da interface do usuário, finalmente recebi a resposta:

pai:

nesse caso, é o widget / layout que envolve os objetos de exibição que você deseja aumentar usando findViewById ().

attachToRoot:

anexa as visualizações aos pais (inclui-as na hierarquia dos pais), para que qualquer evento de toque recebido pelas visualizações também seja transferido para a visualização pai. Agora cabe aos pais se eles querem entreter esses eventos ou ignorá-los. se definido como false, eles não serão adicionados como filhos diretos do pai e o pai não receberá nenhum evento de toque das visualizações.

Espero que isso limpe a confusão

Umer Farooq
fonte
Sua
11

Escrevi esta resposta porque, mesmo depois de passar por várias páginas do StackOverflow, não consegui entender claramente o significado de attachToRoot. Abaixo está o método inflate () na classe LayoutInflater.

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

Dê uma olhada no arquivo activity_main.xml , no layout button.xml e no arquivo MainActivity.java que criei.

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

Quando executamos o código, não vemos o botão no layout. Isso ocorre porque o layout do nosso botão não é adicionado ao layout principal da atividade, pois o attachToRoot está definido como false.

O LinearLayout possui um método addView (exibição de exibição) que pode ser usado para adicionar Views ao LinearLayout. Isso adicionará o layout do botão ao layout principal da atividade e tornará o botão visível quando você executar o código.

root.addView(view);

Vamos remover a linha anterior e ver o que acontece quando configuramos attachToRoot como true.

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

Mais uma vez, vemos que o layout do botão é visível. Isso ocorre porque attachToRoot anexa diretamente o layout inflado ao pai especificado. Que neste caso é LinearLayout raiz. Aqui, não precisamos adicionar as visualizações manualmente, como fizemos no caso anterior, com o método addView (View view).

Por que as pessoas estão obtendo IllegalStateException ao definir attachToRoot como true para um Fragmento.

Isso ocorre porque, para um fragmento, você já especificou onde colocar seu layout de fragmento em seu arquivo de atividade.

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

O add (int parent, Fragment fragment) adiciona o fragmento que possui seu layout ao layout pai. Se definirmos attachToRoot como true, você receberá IllegalStateException: O filho especificado já tem um pai. Como o layout do fragmento já foi adicionado ao layout pai no método add ().

Você sempre deve passar false para attachToRoot ao inflar fragmentos. O trabalho do FragmentManager é adicionar, remover e substituir fragmentos.

De volta ao meu exemplo. E se fizermos as duas coisas?

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

Na primeira linha, LayoutInflater anexa o layout do botão ao layout raiz e retorna um objeto View que contém o mesmo layout do botão. Na segunda linha, adicionamos o mesmo objeto View ao layout raiz pai. Isso resulta na mesma IllegalStateException que vimos com fragmentos (o filho especificado já tem um pai).

Lembre-se de que existe outro método inflate () sobrecarregado, que define o padrão attachToRoot como true por padrão.

View inflate (int resource, ViewGroup root)
capt.swag
fonte
Explicação simples e clara, exatamente o que eu estava procurando!
flyingAssistant
10

Há muita confusão sobre esse tópico devido à documentação do método inflate ().

Em geral, se attachToRoot estiver configurado como true, o arquivo de layout especificado no primeiro parâmetro será inflado e anexado ao ViewGroup especificado no segundo parâmetro naquele momento. Quando attachToRoot é false, o arquivo de layout do primeiro parâmetro é inflado e retornado como um View e qualquer anexo do View acontece em outro momento.

Provavelmente isso não significa muito, a menos que você veja muitos exemplos. Ao chamar LayoutInflater.inflate () dentro do método onCreateView de um fragmento, você desejará passar false para attachToRoot porque a atividade associada a esse fragmento é realmente responsável por adicionar a exibição desse fragmento. Se você estiver inflando e adicionando manualmente uma View a outra View em algum momento posterior, como com o método addView (), convém passar false para attachToRoot porque o anexo será posteriormente.

Você pode ler sobre vários outros exemplos exclusivos sobre caixas de diálogo e visualizações personalizadas em uma postagem de blog que escrevi sobre esse mesmo tópico.

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

seanjfarrell
fonte
4

attachToRootdefinido como true significa inflatedViewque será adicionado à hierarquia da visualização pai. Assim, pode ser "visto" e detectar eventos de toque (ou quaisquer outras operações da interface do usuário) pelos usuários. Caso contrário, ele será criado, não será adicionado a nenhuma hierarquia de exibição e, portanto, não poderá ser visto ou manipular eventos de toque.

Para desenvolvedores iOS novos no Android, attachToRootdefinir como true significa que você chama este método:

[parent addSubview:inflatedView];

Se for mais longe, você poderá perguntar: Por que devo passar na visualização dos pais se eu definir attachToRootcomo false? Isso ocorre porque o elemento raiz em sua árvore XML precisa da visualização pai para calcular alguns LayoutParams (como combinar pai).

Alston
fonte
0

Quando você define o pai, o attachToRoot determina se você deseja que o inflador realmente o anexe ao pai ou não. Em alguns casos, isso causa problemas, como em um ListAdapter, causa uma exceção, porque a lista tenta adicionar a exibição à lista, mas diz que já está anexada. Em outros casos em que você está apenas inflando a visualização para adicionar a uma Atividade, pode ser útil e economizar uma linha de código.

CaseyB
fonte
1
não fornece uma imagem clara que uma boa resposta deve fornecer.
Prakhar1001
0

Por exemplo, temos um ImageView, um LinearLayoute um RelativeLayout. LinearLayout é o filho de RelativeLayout. a hierarquia de exibição será.

RelativeLayout
           ------->LinearLayout

e temos um arquivo de layout separado para o ImageView

image_view_layout.xml

Anexar à raiz:

//here container is the LinearLayout

    View v = Inflater.Inflate(R.layout.image_view_layout,container,true);
  1. Aqui v contém a referência do layout do contêiner, ou seja, o LinearLayout. E se você deseja definir os parâmetros como o setImageResource(R.drawable.np);ImageView, será necessário encontrá-lo pela referência de pai, ou sejaview.findById()
  2. O pai de v será o FrameLayout.
  3. LayoutParams será de FrameLayout.

Não anexar à raiz:

//here container is the LinearLayout
    View v = Inflater.Inflate(R.layout.image_view_layout,container,false);
  1. Aqui v contém o layout do contêiner sem referência, mas a referência direta ao ImageView inflado para que você possa definir seus parâmetros como view.setImageResource(R.drawable.np);sem indicar como findViewById. Mas container é especificado para que o ImageView obtenha os LayoutParams do container, para que você possa dizer que a referência do container é apenas para LayoutParams.
  2. portanto, em caso particular, o pai será nulo.
  3. LayoutParams será de LinearLayout.
Faisal Naseer
fonte
0

attachToRoot Defina como true:

Se attachToRoot estiver configurado como true, o arquivo de layout especificado no primeiro parâmetro será inflado e anexado ao ViewGroup especificado no segundo parâmetro.

Imagine que especificamos um botão em um arquivo de layout XML com a largura e a altura do layout definidas como 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>

Agora, queremos adicionar programaticamente esse botão a um LinearLayout dentro de um fragmento ou atividade. Se nosso LinearLayout já é uma variável de membro, mLinearLayout, podemos simplesmente adicionar o botão com o seguinte:

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

Especificamos que queremos aumentar o Button a partir de seu arquivo de recursos de layout; então dizemos ao LayoutInflater que queremos anexá-lo ao mLinearLayout. Nossos parâmetros de layout são respeitados porque sabemos que o botão é adicionado a um LinearLayout. O tipo de parâmetro de layout do Button deve ser LinearLayout.LayoutParams.

attachToRoot Defina como false (não é necessário usar false)

Se attachToRoot estiver definido como false, o arquivo de layout especificado no primeiro parâmetro será inflado e não anexado ao ViewGroup especificado no segundo parâmetro, mas essa exibição inflada adquirirá LayoutParams do pai, o que permite que essa exibição se ajuste corretamente ao pai.


Vamos dar uma olhada em quando você deseja definir o attachToRoot como false. Nesse cenário, a exibição especificada no primeiro parâmetro de inflate () não é anexada ao ViewGroup no segundo parâmetro neste momento.

Lembre-se do nosso exemplo de botão anterior, onde queremos anexar um botão personalizado de um arquivo de layout ao mLinearLayout. Ainda podemos anexar nosso Button ao mLinearLayout, passando false para attachToRoot - apenas adicionamos manualmente depois.

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

Essas duas linhas de código são equivalentes ao que escrevemos anteriormente em uma linha de código quando passamos true para attachToRoot. Ao passar em false, dizemos que não queremos anexar nossa View ao ViewGroup raiz ainda. Estamos dizendo que isso acontecerá em algum outro momento. Neste exemplo, o outro momento é simplesmente o método addView () usado imediatamente abaixo da inflação.

O exemplo false attachToRoot requer um pouco mais de trabalho quando adicionamos manualmente a View a um ViewGroup.

attachToRoot Defina como false (false é obrigatório)

Ao inflar e retornar a Visualização de um fragmento em onCreateView (), certifique-se de passar false para attachToRoot. Se você for verdadeiro, você receberá uma IllegalStateException porque o filho especificado já possui um pai. Você deveria ter especificado onde a visão do seu Fragmento será colocada novamente em sua Atividade. O trabalho do FragmentManager é adicionar, remover e substituir fragmentos.

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

O contêiner root_viewGroup que manterá seu Fragmento em sua Atividade é o parâmetro ViewGroup fornecido a você em onCreateView () em seu Fragmento. É também o ViewGroup que você passa para LayoutInflater.inflate (). No entanto, o FragmentManager tratará de anexar a visualização do seu fragmento a este grupo de visualizações. Você não deseja anexá-lo duas vezes. Defina attachToRoot como false.

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

return view;
}

Por que recebemos o ViewGroup pai do nosso Fragment em primeiro lugar, se não queremos anexá-lo ao onCreateView ()? Por que o método inflate () solicita um ViewGroup raiz?

Acontece que, mesmo quando não estamos adicionando imediatamente nosso recém-inflado View ao seu ViewGroup pai, ainda devemos usar o LayoutParams do pai para que o novo View determine seu tamanho e posição sempre que for anexado.

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

Utshaw
fonte
0

Apenas compartilhando alguns pontos que encontrei ao trabalhar neste tópico,

Além da resposta aceita, quero alguns pontos que podem ser de alguma ajuda.

Portanto, quando usei o attachToRoot como true, a exibição retornada é do tipo ViewGroup, ou seja, o ViewGroup raiz dos pais, que foi passado como parâmetro para o método inflate (layoutResource, ViewGroup, attachToRoot) , não do tipo do layout que foi transmitido, mas no attachToRoot como false, obtemos o tipo de retorno de função do ViewGroup raiz desse layoutResource .

Deixe-me explicar com um exemplo:

Se temos um LinearLayout como o layout raiz e queremos adicionar o TextView nele através da função inflar .

em seguida, usando attachToRoot como verdadeira função inflar retorna uma Visualização do tipo LinearLayout

enquanto estiver usando o attachToRoot como função de inflamento falso , retorna uma Visualização do tipo TextView

Espero que este achado seja de alguma ajuda ...

HARIS UMAID
fonte