Última actualización:19/11/2016
En ocasiones tendremos que crear vistas dinámicamente, esto es, desde código, e ir agregando elementos a la interfaz gráfica que originalmente puede estar definida en un layout en XML. En este tip vamos a ver un ejemplo muy sencillo consistente en una aplicación que simula de forma muy básica una interfaz tipo Hangout. En concreto, vamos a construir lo siguiente:
En el proyecto de ejemplo tendremos una única Activity. Su layout asociado mostrará una botonera en la parte inferior para añadir los elementos programáticamente a la interfaz mientras que el resto de la pantalla consistirá en un ScrollView que tendrá por hijo un LinearLayout al que agregaremos los nuevos elementos.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/background" android:paddingLeft="16dp" android:paddingRight="16dp"> <ScrollView android:id="@+id/scrollView" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_above="@+id/buttonsLayout" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:fillViewport="true" android:scrollbarStyle="outsideInset" > <LinearLayout android:id="@+id/content" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > </LinearLayout> </ScrollView> <LinearLayout android:id="@+id/buttonsLayout" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:paddingBottom="3dp"> <Button android:id="@+id/buttonLeft" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/left" android:onClick="addLeft"/> <Button android:id="@+id/buttonReset" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/reset" android:onClick="reset"/> <Button android:id="@+id/buttonRight" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/right" android:onClick="addRight"/> </LinearLayout> </RelativeLayout>
Los «elementos» a agregar consistirán en unos RelativeLayout que definiremos en sus respectivos XML.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end" android:background="@android:color/white" android:layout_marginTop="15dp" android:paddingRight="8dp"> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:src="@drawable/ic_launcher" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_toRightOf="@+id/imageView" android:text="@string/hi" android:textAppearance="?android:attr/textAppearanceLarge" android:textColor="@android:color/black" /> <TextView android:id="@+id/textViewDate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/textView" android:layout_toRightOf="@+id/imageView" android:text="@string/hi" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="@android:color/black" /> </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end" android:layout_marginTop="15dp" android:background="@android:color/white" > <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:text="@string/hi" android:textAppearance="?android:attr/textAppearanceLarge" android:textColor="@android:color/black" /> <TextView android:id="@+id/textViewDate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignRight="@+id/textView" android:layout_below="@+id/textView" android:text="@string/hi" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="@android:color/black" /> <ImageView android:id="@+id/ImageView01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_toRightOf="@+id/textView" android:src="@drawable/ic_android" /> </RelativeLayout>
Para ir añadiendo nuevos layout al ir pulsando los botones creaemos el layout que queremos mostrar «inflándolo» (hacer un inflate, para que nos entendamos), y añadirlo al layout padre invocando el método addView. Para que no sean todos iguales, vamos a añadir un timestamp al TextView
package com.danielme.tipsandroid.dynamiclayouts; import android.app.Activity; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.RelativeLayout; import android.widget.TextView; /** * * @author danielme.com * */ public class MainActivity extends Activity { private ViewGroup layout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); layout = (ViewGroup) findViewById(R.id.content); } public void addRight(View button) { addChild(true); } public void addLeft(View button) { addChild(false); } public void reset(View button) { layout.removeAllViews(); } @SuppressLint("InlinedApi") private void addChild(boolean right) { LayoutInflater inflater = LayoutInflater.from(this); int id = R.layout.layout_left; if (right) { id = R.layout.layout_right; } RelativeLayout relativeLayout = (RelativeLayout) inflater.inflate(id, null, false); TextView textView = (TextView) relativeLayout.findViewById(R.id.textViewDate); textView.setText(String.valueOf(System.currentTimeMillis())); layout.addView(relativeLayout); } }
Si probamos este código veremos dos problemas.
- Los atributos de posicionamiento del layout (gravity, padding…) no se aplican.
- Los elementos se añaden pero el usuario no puede verlos a menos que haga scroll.
Tendremos que escribir algunas líneas más de código en la Activity pero la solución no es muy complicada.
- El posicionamiento lo haremos programáticamente haciendo uso de LinearLayout.LayoutParams.
- Tras añadir el elemento, forzaremos el scroll hasta el mismo. Vamops a recurrir al método post que ya se usó pero para un ListView en el tip #17 para «dibujar» primero los elementos y luego obtener el último y hacer scroll hasta él. La solución está tomada del tema de Stackoverflow indicado en el código.
El código con los cambios quedará tal que así:
package com.danielme.tipsandroid.dynamiclayouts; import android.annotation.SuppressLint; import android.app.ActionBar.LayoutParams; import android.app.Activity; import android.os.Bundle; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.TextView; /** * * @author danielme.com * */ public class MainActivity extends Activity { private ViewGroup layout; private ScrollView scrollView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); layout = (ViewGroup) findViewById(R.id.content); scrollView = (ScrollView) findViewById(R.id.scrollView); } public void addRight(View button) { addChild(true); } public void addLeft(View button) { addChild(false); } public void reset(View button) { layout.removeAllViews(); } @SuppressLint("InlinedApi") private void addChild(boolean right) { LayoutInflater inflater = LayoutInflater.from(this); int id = R.layout.layout_left; if (right) { id = R.layout.layout_right; } RelativeLayout relativeLayout = (RelativeLayout) inflater.inflate(id, null, false); TextView textView = (TextView) relativeLayout.findViewById(R.id.textViewDate); textView.setText(String.valueOf(System.currentTimeMillis())); //layout params LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); if (right) { params.gravity = Gravity.END; } params.topMargin = 15; relativeLayout.setPadding(5, 3, 5, 3); relativeLayout.setLayoutParams(params); /////// layout.addView(relativeLayout); //scroll to last element //http://stackoverflow.com/questions/6438061/can-i-scroll-a-scrollview-programmatically-in-android scrollView.post(new Runnable() { public void run() { scrollView.fullScroll(ScrollView.FOCUS_DOWN); } }); /////// } }
El proyecto completo para Eclipse ADT se encuentra en Github. Para más información sobre cómo utilizar GitHub, consultar este artículo.
Te puedo hacer una consulta? Me resultó bárbaro tu código pero estoy teniendo un problema, a medida que agrego los layouts como que se superpone o se elimina el anterior y se coloca el nuevo no se si me explico. Necesito hacer un listado, creo un elemento en una activity y cuando le doy a crear me lleva a otra que sería la lista de los elementos que voy agregando, y siempre me aparece uno que es el último que creé. La verdad es que ya seguí todos los detalles y no se que falla. Desde ya gracias !
No entiendo muy bien lo que quieres decir, ¿introduces datos en una Activity y los vas mostrando en otra? De alguna forma tendrás que guardar esos datos para mostrarlos y no enviar a la Activity que los muestra sólo el nuevo a través del Intent. Quizás te sea más sencillo utilizar un ListView.
Activity 1 = lista (vacía). Activity 2 = crear elemento. Cuando creo un elemento agrego un layout en la Activity 1 (se agrega bien), pero cuando quiero crear otro elemento en la Activity 1 desaparece el anterior. Y asi siempre, sólo tengo una lista de 1 elemento que es el que agrego al final.
Si en la Activity 2 vuelves a la uno haciendo un startActivity crearás una nueva instancia de la Activity 1 y perderás todos los datos. Este tipo de casuística está contemplado por Android y la manera de proceder es:
-en la Activity 1 invocas a la Activity 2 con un método startActivityForResult
-en la Activity 2, cuando hayas terminado de realizar la operación que sea, vuelves a la Activity 1 haciendo un finish() pero guardando antes los datos que quieras enviar en un intent (getIntent) e invocando setResult.
-Al volver a la Activity 1, no se creará una nueva sino simplemente se ejecutará el método onActivityResult que deberás implementar y en el que recibirás el Intent con los datos que hayas enviado desde la Activity 2.
Siguiendo esta metodología, conservarás los datos de la Activity 1.
PD: Voy a aprovechar tu pregunta y escribiré esto mismo en un tip con una demo.
Ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh no sabía esa historia. Lo hice como dijiste y me funcionó. Muchas gracias Daniel por tus respuestas !!!!!!
Hola Daniel, buenos días. Te hago otra consulta esta vez mucho más sencilla. Al ir agregando los elementos a mi lista, se genera un espacio entre éstos, me fijé en las propiedades del layout que estoy generando y todos los heights son fijos de máx 50dp, sin embargo el espacio es como de unos 300. Te adjunto una foto para que veas si podes ayudarme con esto.
http://imageshack.us/photo/my-images/59/snpt.jpg/
Hola que tal tengo una duda para obtener los valores de un editview una ves generados los layout como se puede hacer???
Puedes guardar la referencia en un atributo de la Activity en el momento de la creación.
hola que tal, muy chevere el articulo… pero quiero ponerle un evento a cada layout creado.
podrias ayudarme un poco?
Muy bueno tu post, estoy haciendo un proyecto y estoy utilizando tu ejemplo pero tengo una pregunta:
Como puedo hacer para agregar un evento de Focus en un textView.
Hola!, genial, me ayudaste mucho, pero tengo una duda, como podria eliminar un layout creado programaticamente?
Eliminándolos del layout padre. Los ViewGroup tienen varios métodos remove.
He añadido un botón reset al ejemplo.
Buenas, tengo una duda, tengo un método prácticamente igual al tuyo (ya que lo cogí de aquí), todo funciona perfectamente a excepción del momento donde trato de hacer un ImageButto.setImageResource(id); y me lanza un error. Te dejo la imagen del método para ver si puedes ayudarme, gracias!! https://s11.postimg.org/mh0yf86xf/Fallo.png
Un saludo!!
hola esta excelente tu código solo tengo una duda,
como podría hacerle para que los activities se agregaran de arriba hacia abajo y no se abajo hacia arriba
Puedes añadir las vistas siempre en la primera posición:
layout.addView(relativeLayout, 0);
En este caso no habría que hacer scroll.