studio - inflate layout in custom view android
¿Qué significa el parámetro LayoutInflater attachToRoot? (11)
La documentación de LayoutInflater.inflate
no es exactamente clara para mí sobre el propósito del parámetro attachToRoot
.
attachToRoot : si la jerarquía inflada debe estar asociada al parámetro raíz? Si es falso, root 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 qué es la vista raíz, y quizás mostrar un ejemplo de un cambio en el comportamiento entre los valores true
y false
?
AHORA O NO AHORA
La principal diferencia entre el "tercer" parámetro attachToRoot es verdadero o falso es esto.
Cuando pones attachToRoot
true: agregue la vista secundaria al padre AHORA MISMO
falso: agregue la vista secundaria al padre NO AHORA .
Añádelo más tarde. `
¿Cuándo es eso más tarde ?
Que más tarde es cuando se utiliza, por ejemplo, parent.addView(childView)
Una idea errónea común es que si el parámetro attachToRoot es falso, la vista secundaria no se agregará al 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 principal.
Por ejemplo, al agregar Fragmento
public View onCreateView(LayoutInflater inflater,ViewGroup parent,Bundle bundle)
{
super.onCreateView(inflater,parent,bundle);
View v = inflater.inflate(R.layout.image_fragment,parent,false);
.....
return true
}
si pasa el tercer parámetro como verdadero obtendrá IllegalStateException por este tipo.
getSupportFragmentManager()
.beginTransaction()
.add(parent, childFragment)
.commit();
Dado que ya ha agregado el fragmento hijo en onCreateView () por error. Al llamar a agregar, se le indicará que la vista secundaria ya se ha agregado al principal, por lo tanto, IllegalStateException .
Aquí no eres responsable de agregar ChildView, FragmentManager es el responsable. Por lo tanto, siempre pase 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.
Cuando define el elemento primario, attachToRoot determina si desea que el dispositivo de inflado realmente lo conecte al elemento principal 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á adjuntada. En otro caso en el que solo está inflando la vista para agregarlo a una Actividad, puede ser útil y guardar una línea de código.
Escribí esta respuesta porque, incluso después de revisar varias páginas de , no pude entender 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 de button.xml y el archivo MainActivity.java 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 nuestro diseño de botones no se agrega al diseño de la actividad principal, ya que attachToRoot se establece 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 cuando configuran attachToRoot como verdadero para un fragmento?
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 principal. Si establecemos attachToRoot como verdadero, obtendrá IllegalStateException: el elemento secundario especificado ya tiene un elemento primario. Ya que el diseño de fragmento ya se ha agregado al diseño principal en el método add ().
Siempre debe pasar false para attachToRoot cuando está inflando Fragments. El trabajo de FragmentManager es agregar, eliminar y reemplazar fragmentos.
Volver a mi ejemplo ¿Qué pasa si hacemos ambas cosas?
View view = inflater.inflate(R.layout.button, root, true);
root.addView(view);
En la primera línea, LayoutInflater asocia el diseño de los botones al diseño de la raíz y devuelve un objeto View que mantiene el mismo diseño de botón. En la segunda línea, agregamos el mismo objeto de Vista al diseño de raíz padre. Esto da como resultado la misma IllegalStateException que vimos con Fragments (el hijo especificado ya tiene un padre).
Tenga en cuenta que hay otro método inflate sobrecargado (), que establece attachToRoot como verdadero de manera predeterminada.
View inflate (int resource, ViewGroup root)
Existe mucha confusión sobre este tema debido a la documentación del método inflate ().
En general, 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 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 Ver aparece en otro momento.
Esto probablemente no significa mucho a menos que vea muchos ejemplos. Cuando llame a LayoutInflater.inflate () dentro del método onCreateView de un Fragment, querrá pasar un valor falso para attachToRoot porque la Actividad asociada con ese Fragmento es en realidad responsable de agregar la vista de ese Fragmento. Si está inflando y agregando manualmente una Vista a otra Vista en algún momento posterior, como con el método addView (), querrá pasar el valor falso para attachToRoot porque el archivo adjunto aparece en un momento posterior.
Puede leer acerca de 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/
La documentación y las dos respuestas anteriores deberían ser suficientes, solo algunos de mis pensamientos.
El método inflate
se usa para inflar los archivos de diseño. Con esos diseños inflados, tienes la posibilidad de adjuntarlos directamente a un ViewGroup
padre o simplemente inflar la jerarquía de vistas desde ese archivo de diseño y trabajar con él fuera de la jerarquía de vista normal.
En el primer caso, el parámetro attachToRoot
tendrá que establecerse en true
(o mucho más simple utilizar el método ViewGroup
que toma un archivo de diseño y un grupo raíz ViewGroup
(no null
)). En este caso, la View
devuelta es simplemente el ViewGroup
que se pasó en el método, el ViewGroup
al que se agregará la jerarquía de vistas infladas.
Para la segunda opción, la View
devuelta es la raíz del ViewGroup
de ViewGroup
del archivo de diseño. Si recuerda nuestra última discusión de la pregunta de par de include-merge
esta es una de las razones de la limitación de la merge
(cuando un archivo de diseño con merge
como raíz está inflado, debe proporcionar un padre y attachedToRoot
debe establecerse en true
) . Si tenía un archivo de diseño con la raíz una etiqueta de merge
y attachedToRoot
se estableció en false
entonces el método de inflate
no tendrá nada que devolver ya que la merge
no tiene un equivalente. Además, como dice la documentación, la versión attachToRoot
con attachToRoot
establecido en false
es importante porque puede crear la jerarquía de vista con los LayoutParams
correctos del LayoutParams
primario. Esto es importante en algunos casos, más notable con los hijos de AdapterView
, una subclase de ViewGroup
, para la cual no se admiten los métodos addView()
. Estoy seguro de que recuerdas usar esta línea en el método getView()
:
convertView = inflater.inflate(R.layout.row_layout, parent, false);
Esta línea garantiza que el archivo inflado R.layout.row_layout
tenga los LayoutParams
correctos de la subclase AdapterView
configurada en su ViewGroup
raíz. Si no lo hiciera, podría tener algunos problemas con el archivo de diseño si el directorio raíz fuera un RelativeLayout
. TableLayout/TableRow
también tiene algunos TableLayout/TableRow
especiales e importantes y debe asegurarse de que las vistas en ellos tengan los LayoutParams
correctos.
Parece que hay un montón de texto en las respuestas pero no un código, por eso decidí revivir esta vieja pregunta con un ejemplo de código, en varias respuestas que mencionaron las personas:
Si se establece en verdadero, cuando su diseño esté inflado, se agregará automáticamente a la jerarquía de vistas del grupo de vistas especificado en el segundo parámetro como un 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);
}
}
Observe que el código anterior agrega el diseño R.layout.child_view
como hijo de MyCustomLayout
porque attachToRoot
param es true
y asigna los parámetros de diseño del elemento primario exactamente de la misma manera que si estuviera usando addView
mediante programación, o como si lo hiciera esto en xml:
<LinearLayout> <View.../> ... </LinearLayout>
El siguiente código explica el escenario al pasar attachRoot
como 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 especifica que desea que myView
sea su propio objeto raíz y no lo adjunte a ningún elemento primario, luego lo agregamos como parte de LinearLayout
pero por un momento fue una vista independiente (sin elementos principales) .
Lo mismo ocurre con los Fragmentos, 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.
Por ejemplo, tenemos ImageView
, LinearLayout
y RelativeLayout
. LinearLayout es el hijo de RelativeLayout. la Jerarquía de Vista será.
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);
- Aquí v contiene la referencia del diseño del contenedor, es decir, LinearLayout. Y si desea establecer parámetros como
setImageResource(R.drawable.np);
de ImageView tendrá que encontrarlo por la referencia de padre, es decir,view.findById()
- El padre de v será FrameLayout.
- 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);
- Aquí v contiene el diseño sin contenedor de referencia pero referencia directa al ImageView que está inflado para que pueda establecer sus parámetros como
view.setImageResource(R.drawable.np);
sin referencia comofindViewById
. Pero el contenedor se especifica para que ImageView obtenga los LayoutParams del contenedor para que pueda decir que la referencia del contenedor es solo para LayoutParams. - entonces, en el caso particular, Parent será nulo.
- LayoutParams será de LinearLayout.
Si se establece en verdadero, cuando su diseño esté inflado, se agregará automáticamente a la jerarquía de vistas del grupo de vistas especificado en el segundo parámetro como un elemento secundario. Por ejemplo, si el parámetro raíz era 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.).
Yo mismo también estaba confundido acerca de cuál era el verdadero propósito de attachToRoot
en el método attachToRoot
. 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 primaria. Ahora depende del padre si desea 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 borre la confusión
attachToRoot
establecido en true significa que la vista inflatedView
se agregará a la jerarquía de la vista principal. Por lo tanto, es posible que los usuarios puedan "ver" y detectar eventos táctiles (o cualquier otra operación de IU). De lo contrario, solo se ha creado, no se ha agregado a ninguna jerarquía de vistas y, por lo tanto, no se puede ver ni manejar eventos táctiles.
Para los desarrolladores de iOS nuevos en Android, attachToRoot
establecido en verdadero significa que usted llama a este método:
[parent addSubview:inflatedView];
Si va más allá, podría preguntar: ¿Por qué debería pasar la vista padre si configuro attachToRoot
en 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).
attachToRoot establecido 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 en 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>
Ahora queremos agregar este Botón programáticamente a LinearLayout 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 de su archivo de recursos de diseño; luego le decimos a LayoutInflater que queremos adjuntarlo a mLinearLayout. Nuestros parámetros de diseño se respetan porque sabemos que el botón se agrega a LinearLayout. El tipo de parámetros de diseño del botón debe ser LinearLayout.LayoutParams.
attachToRoot Establecido en falso (no es necesario 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 LayoutParams de los padres que permite que la vista se ajuste correctamente en el elemento primario.
Echemos un vistazo a cuándo querría establecer attachToRoot en falso. En este escenario, la Vista especificada en el primer parámetro de inflate () no se adjunta al ViewGroup en el segundo parámetro en este momento.
Recuerde 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 al pasar en falso para attachToRoot, luego lo agregamos manualmente nosotros mismos luego.
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 en verdadero para attachToRoot. Al pasar en falso, decimos que no queremos adjuntar nuestra Vista a la raíz ViewGroup todavía. Estamos diciendo que sucederá en algún otro momento. En este ejemplo, el otro punto en el tiempo es simplemente el método addView () usado inmediatamente debajo de la inflación.
El ejemplo falso de attachToRoot requiere un poco más de trabajo cuando agregamos manualmente View a un ViewGroup.
attachToRoot establecido en falso (se requiere falso)
Al inflar y devolver la Vista de un Fragmento en onCreateView (), asegúrese de pasar el valor falso para attachToRoot. Si pasa verdadero, obtendrá una IllegalStateException porque el hijo especificado ya tiene un padre. Debes haber especificado dónde se colocará la vista de tu Fragmento en tu Actividad. El trabajo de 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 asigna en onCreateView () en su Fragmento. También es el ViewGroup que pasa a LayoutInflater.inflate (). Sin embargo, 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é se nos da el Grupo de Vista padre de nuestro Fragmento en primer lugar si no queremos adjuntarlo en CreateView ()? ¿Por qué el método inflate () solicita un Root ViewGroup?
Resulta que incluso cuando no agreguemos de manera inmediata nuestra Vista recién inflada a su ViewGroup padre, aún deberíamos usar los LayoutParams de los padres para que la nueva Vista determine su tamaño y posición cada vez que se adjunte.