Tip Android #29: Layouts dinámicos (añadir vistas programáticamente)

Última actualización:19/11/2016

android tip

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.

left

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

right

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

  1. Los atributos de posicionamiento del layout (gravity, padding…) no se aplican.
  2. 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.

  1. El posicionamiento lo haremos programáticamente haciendo uso de LinearLayout.LayoutParams.

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

<< TIPS ANDROID

16 Responses to Tip Android #29: Layouts dinámicos (añadir vistas programáticamente)

  1. Tano dice:

    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 !

    • danielme.com dice:

      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.

  2. Tano dice:

    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.

    • danielme.com dice:

      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.

  3. Tano dice:

    Ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh no sabía esa historia. Lo hice como dijiste y me funcionó. Muchas gracias Daniel por tus respuestas !!!!!!

  4. Tano dice:

    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/

  5. Luis dice:

    Hola que tal tengo una duda para obtener los valores de un editview una ves generados los layout como se puede hacer???

  6. gerson dice:

    hola que tal, muy chevere el articulo… pero quiero ponerle un evento a cada layout creado.
    podrias ayudarme un poco?

  7. Lusi dice:

    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.

  8. Oscar dice:

    Hola!, genial, me ayudaste mucho, pero tengo una duda, como podria eliminar un layout creado programaticamente?

  9. ONAJANO dice:

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

  10. Carlos dice:

    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

  11. Pingback: Android Dynamic layouts (old version) | Coding Tweaks

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: