¿Qué significa el parámetro LayoutInflater attachToRoot?

201

La LayoutInflater.inflatedocumentación no es exactamente clara para mí sobre el propósito del attachToRootparámetro.

attachToRoot : si la jerarquía inflada debe estar asociada al parámetro raíz? Si es falso, la raíz solo se usa para crear la subclase correcta de LayoutParams para la vista raíz en el XML.

¿Podría alguien explicar con más detalle, específicamente cuál es la vista raíz, y tal vez mostrar un ejemplo de un cambio en el comportamiento entre truey los falsevalores?

Jeff Axelrod
fuente
1
Relacionado: Dar sentido a LayoutInflater
blahdiblah

Respuestas:

157

AHORA O NO AHORA

La principal diferencia entre el "tercer" parámetro attachToRoot es verdadero o falso es esta.

Cuando pones attachToRoot

verdadero: agregue la vista secundaria al principal AHORA MISMO
falso: agregue la vista secundaria al principal NO AHORA .
Añádelo más tarde. ``

¿Cuándo es eso más tarde ?

Eso más tarde es cuando usas por ejemplo parent.addView(childView)

Un error común es que, si el parámetro attachToRoot es falso, la vista secundaria no se agregará a la principal. Incorrecto
En ambos casos, la vista secundaria se agregará a parentView. Es solo cuestión de tiempo .

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

es equivalente a

inflater.inflate(child,parent,true);

UN GRAN NO-NO
Nunca debe pasar attachToRoot como verdadero cuando no es responsable de agregar la vista secundaria a primaria.
Por ejemplo, al agregar un 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;
  }

si pasa el tercer parámetro como verdadero, obtendrá IllegalStateException debido a este tipo.

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

Como ya ha agregado el fragmento secundario en onCreateView () por error. Llamar a add le dirá que la vista secundaria ya está agregada a la primaria Por lo tanto, IllegalStateException .
Aquí no es responsable de agregar childView, FragmentManager es responsable. Así que siempre pasa falso en este caso.

NOTA: También he leído que parentView no obtendrá childView touchEvents si attachToRoot es falso. Pero no lo he probado sin embargo.

Rohit Singh
fuente
66
Muy útil, especialmente la parte sobre el FragmentManager, gracias!
CybeX
94

Si se establece en verdadero, cuando su diseño esté inflado, se agregará automáticamente a la jerarquía de vistas del ViewGroup especificado en el segundo parámetro como elemento secundario. Por ejemplo, si el parámetro raíz era un LinearLayout, su vista inflada se agregará automáticamente como elemento secundario de esa vista.

Si se establece en falso, su diseño se inflará pero no se adjuntará a ningún otro diseño (por lo que no se dibujará, recibirá eventos táctiles, etc.).

Joseph Earl
fuente
17
Estoy confundido. Me estaba poniendo un "niño especificado ya tiene un error de los padres” hasta que leí esta respuesta , que dirigió mi de usar falsepara attachToRootdurante mi Fragmento de onCreateView. Esto resolvió el problema y sin embargo, la disposición del fragmento es visible y activa, a pesar de su respuesta. ¿Qué está pasando aquí?
Jeff Axelrod
67
Debido a que un Fragmento adjunta automáticamente el diseño devuelto por onCreateView. Entonces, si lo adjunta manualmente en onCreateView, su vista se adjunta a 2 padres (lo que produce el error que menciona).
Joseph Earl
11
Estoy un poco confundido aquí, @JosephEarl dijiste que si se establece en true, la vista se adjunta al segundo parámetro, que es el container, pero luego dices que el fragmento se adjunta automáticamente onCreateView(), por lo que entiendo, el tercer parámetro es inútil y debe establecerse false¿siempre?
unmultimedio
55
Devuelve la vista en oncreateview, que luego se adjunta automáticamente. Si establece adjuntar a verdadero, se genera un error. Sin embargo, cuando infla la vista en una situación independiente, puede optar por adjuntar la vista a su contenedor automáticamente estableciéndola en verdadero. Aunque casi nunca me pongo en verdad, ya que siempre agrego la vista a mí mismo.
frostymarvelous
77
@unmultimedio solo es inútil para la vista raíz devuelta por onCreateView. Si infla más diseños en esa vista raíz, o está inflando en un contexto diferente (por ejemplo, en una Actividad), entonces es útil.
Joseph Earl
36

Parece que hay mucho texto en las respuestas pero no hay código, por eso decidí revivir esta vieja pregunta con un ejemplo de código, en varias respuestas las personas mencionaron:

Si se establece en verdadero, cuando su diseño esté inflado, se agregará automáticamente a la jerarquía de vistas del ViewGroup especificado en el segundo parámetro como elemento secundario.

Lo que eso realmente significa en el código (lo que la mayoría de los programadores entienden) es:

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

Tenga en cuenta que el código anterior está agregando el diseño R.layout.child_viewcomo hijo de MyCustomLayoutdebido a attachToRootparam is truey asigna los parámetros de diseño del padre exactamente de la misma manera que si estuviera usando addViewprogramáticamente, o como si hiciera esto en xml:

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

El siguiente código explica el escenario al pasar 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);

En el código anterior, especificó que deseaba myViewser su propio objeto raíz y no lo adjunte a ningún padre, más tarde lo agregamos como parte de la vista, LinearLayoutpero por un momento fue una vista independiente (sin padre).

Lo mismo sucede con Fragments, puede agregarlos a un grupo ya existente y ser parte de él, o simplemente pasar los parámetros:

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

Para especificar que será su propia raíz.

Martín Cazares
fuente
1
Fuera de todo, esto fue lo más útil.
Wahib Ul Haq
26

La documentación y las dos respuestas anteriores deberían ser suficientes, solo algunas reflexiones mías.

El inflatemétodo se utiliza para inflar archivos de diseño. Con esos diseños inflados, tiene la posibilidad de adjuntarlos directamente a un padre ViewGroupo simplemente inflar la jerarquía de vista desde ese archivo de diseño y trabajar con él fuera de la jerarquía de vista normal.

En el primer caso, el attachToRootparámetro tendrá que establecerse en true(o simplemente usar el inflatemétodo que toma un archivo de diseño y una raíz principal ViewGroup(no null)). En este caso, la Viewdevolución es simplemente la ViewGroupque se pasó en el método, ViewGroupa la que se agregará la jerarquía de vista inflada.

Para la segunda opción, se devuelve Viewla raíz ViewGroupdel archivo de diseño. Si recuerda nuestra última discusión de la include-mergepregunta del par, esta es una de las razones de la mergelimitación de (cuando un archivo de diseñomerge se infla raíz, debe proporcionar un padre y attachedToRootdebe estar configurado en true). Si tenía un archivo de diseño con la raíz una mergeetiqueta y attachedToRootse configuró, falseentonces el inflatemétodo no tendrá nada que devolver, ya mergeque no tiene un equivalente. Además, como dice la documentación, la inflateversión con attachToRootset to falsees importante porque puede crear la jerarquía de vistas con la correctaLayoutParamsdel padre Esto es importante en algunos casos, más notable con los hijos de AdapterView, una subclase de ViewGroup, para la cual el addView()conjunto de métodos no es compatible. Estoy seguro de que recuerdas haber usado esta línea en el getView()método:

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

Esta línea garantiza que el R.layout.row_layoutarchivo inflado tenga el correcto LayoutParamsde la AdapterViewsubclase establecida en su raíz ViewGroup. Si no haría esto, podría tener algunos problemas con el archivo de diseño si la raíz fuera a RelativeLayout. El TableLayout/TableRowtambién tienen algún especial e importanteLayoutParams y usted debe asegurarse de que los puntos de vista en ellas tienen la correcta LayoutParams.

Luksprog
fuente
18

Yo mismo también estaba confundido acerca de lo que era el verdadero propósito de attachToRootde inflatemétodo. Después de un poco de estudio de IU, finalmente obtuve la respuesta:

padre:

en este caso es el widget / diseño que rodea los objetos de vista que desea inflar usando findViewById ().

attachToRoot:

adjunta las vistas a su elemento primario (las incluye en la jerarquía principal), por lo que cualquier evento táctil que reciban las vistas también se transferirá a la vista principal. Ahora depende de los padres si quieren entretener esos eventos o ignorarlos. si se establece en falso, no se agregan como hijos directos del padre y el padre no recibe ningún evento táctil de las vistas.

Espero que esto aclare la confusión

Umer Farooq
fuente
Su
11

Escribí esta respuesta porque, incluso después de pasar por varias páginas de StackOverflow, no pude comprender claramente qué significaba attachToRoot. A continuación se muestra el método inflate () en la clase LayoutInflater.

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

Eche un vistazo al archivo activity_main.xml , el diseño button.xml y MainActivity.java archivo que creé.

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

Cuando ejecutamos el código, no veremos el botón en el diseño. Esto se debe a que el diseño de nuestro botón no se agrega al diseño de actividad principal, ya que attachToRoot está establecido en falso.

LinearLayout tiene un método addView (vista de vista) que se puede usar para agregar vistas a LinearLayout. Esto agregará el diseño del botón al diseño de la actividad principal y hará que el botón sea visible cuando ejecute el código.

root.addView(view);

Eliminemos la línea anterior y veamos qué sucede cuando establecemos attachToRoot como verdadero.

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

Nuevamente vemos que el diseño del botón es visible. Esto se debe a que attachToRoot adjunta directamente el diseño inflado al elemento primario especificado. Que en este caso es root LinearLayout. Aquí no tenemos que agregar las vistas manualmente como lo hicimos en el caso anterior con el método addView (Vista de vista).

¿Por qué las personas obtienen IllegalStateException al establecer attachToRoot como verdadero para un Fragment?

Esto se debe a que para un fragmento ya ha especificado dónde colocar su diseño de fragmento en su archivo de actividad.

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

El complemento (int parent, Fragment fragment) agrega el fragmento que tiene su diseño al diseño padre. Si configuramos attachToRoot como verdadero, obtendrá IllegalStateException: el elemento secundario especificado ya tiene un elemento primario. Dado que el diseño de fragmentos ya se ha agregado al diseño primario en el método add ().

Siempre debe pasar false para attachToRoot cuando esté inflando Fragments. El trabajo del FragmentManager es agregar, eliminar y reemplazar Fragmentos.

De vuelta a mi ejemplo. ¿Qué pasa si hacemos las dos cosas?

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

En la primera línea, LayoutInflater adjunta el diseño del botón al diseño raíz y devuelve un objeto Ver que contiene el mismo diseño del botón. En la segunda línea, agregamos el mismo objeto Ver al diseño raíz principal. Esto da como resultado la misma IllegalStateException que vimos con Fragments (el elemento secundario especificado ya tiene un elemento primario).

Tenga en cuenta que existe otro método inflate () sobrecargado, que establece attachToRoot como verdadero de forma predeterminada.

View inflate (int resource, ViewGroup root)
capt.swag
fuente
Explicación simple y clara, ¡justo lo que estaba buscando!
flyingAssistant
10

Hay mucha confusión sobre este tema debido a la documentación para el método inflate ().

En general, si attachToRoot se establece en verdadero, el archivo de diseño especificado en el primer parámetro se infla y se adjunta al ViewGroup especificado en el segundo parámetro en ese momento. Cuando attachToRoot es falso, el archivo de diseño del primer parámetro se infla y se devuelve como una Vista y cualquier archivo adjunto de Vista ocurre en otro momento.

Esto probablemente no signifique mucho a menos que vea muchos ejemplos. Al llamar a LayoutInflater.inflate () dentro del método onCreateView de un Fragment, querrá pasar false para attachToRoot porque la Actividad asociada con ese Fragment es realmente responsable de agregar la vista de ese Fragment. Si está inflando manualmente y agregando una Vista a otra Vista en algún momento posterior, como con el método addView (), querrá pasar false para attachToRoot porque el archivo adjunto llega en un momento posterior.

Puede leer sobre varios otros ejemplos únicos sobre Diálogos y Vistas personalizadas en una publicación de blog que escribí sobre este mismo tema.

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

seanjfarrell
fuente
4

attachToRootestablecido en verdadero significa inflatedViewque se agregará a la jerarquía de la vista principal. Por lo tanto, los usuarios pueden "ver" y detectar eventos táctiles (o cualquier otra operación de IU). De lo contrario, se acaba de crear, no se ha agregado a ninguna jerarquía de vista y, por lo tanto, no se puede ver ni manejar eventos táctiles.

Para los desarrolladores de iOS nuevos en Android, attachToRootestablecer en verdadero significa que llama a este método:

[parent addSubview:inflatedView];

Si ir más lejos usted puede preguntar: ¿Por qué debería pasar vista padre si me puse attachToRoota false? Esto se debe a que el elemento raíz en su árbol XML necesita la vista principal para calcular algunos LayoutParams (como match parent).

Alston
fuente
0

Cuando define el elemento primario, attachToRoot determina si desea que el inflador realmente lo adjunte al elemento primario o no. En algunos casos esto causa problemas, como en un ListAdapter causará una excepción porque la lista intenta agregar la vista a la lista pero dice que ya está adjunta. En otro caso en el que solo está inflando la vista para agregarla a una Actividad, podría ser útil y ahorrarle una línea de código.

CaseyB
fuente
1
no da una imagen clara que debería proporcionar una buena respuesta.
Prakhar1001
0

Por ejemplo tenemos un ImageView, a LinearLayouty a RelativeLayout. LinearLayout es el hijo de RelativeLayout. Verá la jerarquía.

RelativeLayout
           ------->LinearLayout

y tenemos un archivo de diseño separado para ImageView

image_view_layout.xml

Adjuntar a la raíz:

//here container is the LinearLayout

    View v = Inflater.Inflate(R.layout.image_view_layout,container,true);
  1. Aquí v contiene la referencia del diseño del contenedor, es decir, LinearLayout. Y si desea establecer los parámetros como setImageResource(R.drawable.np);de ImageView, deberá encontrarlo mediante la referencia de padre, es decirview.findById()
  2. El padre de v será el FrameLayout.
  3. LayoutParams será de FrameLayout.

No adjuntar a la raíz:

//here container is the LinearLayout
    View v = Inflater.Inflate(R.layout.image_view_layout,container,false);
  1. Aquí v contiene el diseño del contenedor sin referencia, pero la referencia directa al ImageView que se infla para que pueda establecer sus parámetros como view.setImageResource(R.drawable.np);sin referenciar comofindViewById . Pero el contenedor se especifica para que ImageView obtenga los LayoutParams del contenedor, por lo que puede decir que la referencia del contenedor es solo para LayoutParams y nada más.
  2. entonces, en particular, Parent será nulo.
  3. LayoutParams será de LinearLayout.
Faisal Naseer
fuente
0

attachToRoot Establecer en verdadero:

Si attachToRoot se establece en true, el archivo de diseño especificado en el primer parámetro se infla y se adjunta al ViewGroup especificado en el segundo parámetro.

Imagine que especificamos un botón en un archivo de diseño XML con su ancho de diseño y altura de diseño establecidos para emparejar_parente.

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

Ahora queremos agregar mediante programación este botón a un diseño lineal dentro de un fragmento o actividad. Si nuestro LinearLayout ya es una variable miembro, mLinearLayout, simplemente podemos agregar el botón con lo siguiente:

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

Especificamos que queremos inflar el botón desde su archivo de recursos de diseño; luego le decimos al LayoutInflater que queremos adjuntarlo a mLinearLayout. Se respetan nuestros parámetros de diseño porque sabemos que el botón se agrega a un diseño lineal. El tipo de parámetros de diseño del botón debe ser LinearLayout.LayoutParams.

attachToRoot Establecer en falso (no se requiere para usar falso)

Si attachToRoot se establece en falso, el archivo de diseño especificado en el primer parámetro se infla y no se adjunta al ViewGroup especificado en el segundo parámetro, pero esa vista inflada adquiere los LayoutParams de los padres que permiten que esa vista se ajuste correctamente en el padre.


Echemos un vistazo a cuándo desea establecer attachToRoot en falso. En este escenario, la Vista especificada en el primer parámetro de inflate () no está adjunta al ViewGroup en el segundo parámetro en este momento.

Recordemos nuestro ejemplo de botón de antes, donde queremos adjuntar un botón personalizado de un archivo de diseño a mLinearLayout. Todavía podemos adjuntar nuestro botón a mLinearLayout pasando falso para attachToRoot; simplemente lo agregamos manualmente nosotros mismos después.

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

Estas dos líneas de código son equivalentes a lo que escribimos anteriormente en una línea de código cuando pasamos true para attachToRoot. Al pasar falso, decimos que todavía no queremos adjuntar nuestra Vista al ViewGroup raíz. Estamos diciendo que sucederá en algún otro momento. En este ejemplo, el otro punto en el tiempo es simplemente el método addView () utilizado inmediatamente debajo de la inflación.

El ejemplo falso attachToRoot requiere un poco más de trabajo cuando agregamos manualmente la vista a un grupo de vista.

attachToRoot Establecer en false (se requiere false)

Al inflar y devolver una vista de fragmento en onCreateView (), asegúrese de pasar false para attachToRoot. Si pasa verdadero, obtendrá una IllegalStateException porque el niño especificado ya tiene un padre. Deberías haber especificado dónde se volverá a colocar la vista de tu Fragmento en tu Actividad. El trabajo del FragmentManager es agregar, eliminar y reemplazar 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();
}

El contenedor root_viewGroup que contendrá su Fragmento en su Actividad es el parámetro ViewGroup que se le proporciona en onCreateView () en su Fragmento. También es el ViewGroup que pasa a LayoutInflater.inflate (). Sin embargo, el FragmentManager se encargará de adjuntar la Vista de su Fragmento a este ViewGroup. No desea adjuntarlo dos veces. Establezca attachToRoot en falso.

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

return view;
}

¿Por qué nos dan el ViewGroup padre de nuestro Fragmento en primer lugar si no queremos adjuntarlo en onCreateView ()? ¿Por qué el método inflate () solicita un ViewGroup raíz?

Resulta que incluso cuando no agreguemos de inmediato nuestra Vista recién inflada a su Grupo de vista padre, aún debemos usar los LayoutParams del padre para que la nueva Vista determine su tamaño y posición cada vez que se adjunte.

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

Utshaw
fuente
0

Solo compartiendo algunos puntos que encontré mientras trabajaba en este tema,

Además de la respuesta aceptada, quiero algunos puntos que podrían ser de alguna ayuda.

Entonces, cuando utilicé attachToRoot como verdadero, la vista que se devolvió fue de tipo ViewGroup, es decir, la raíz de ViewGroup principal que se pasó como parámetro para el método de inflado (layoutResource, ViewGroup, attachToRoot) , no del tipo de diseño que se pasó sino de attachToRoot como falso obtenemos el tipo de retorno de función del ViewGroup raíz de ese layoutResource .

Dejame explicarte con un ejemplo:

Si tenemos un LinearLayout como diseño de raíz y luego queremos agregar TextView a través de inflar función.

luego, al usar attachToRoot como verdadera función de inflado, devuelve una vista de tipo LinearLayout

mientras se usa attachToRoot como función de inflado falso devuelve una vista de tipo TextView

Espero que este hallazgo sea de alguna ayuda ...

HARIS UMAID
fuente