Menú Navegación Páginas

El blog de Antonio Leiva sobre diseño y desarrollo de interfaces en Android

Fragments en Android

Fragments en Android
  • Twitter
  • Facebook
  • Google Plus
  • LinkedIn

Uno de los conceptos más interesantes que introdujo Android 3.0 fue el de los Fragments. Los Fragments nos permiten segmentar la aplicación de tal modo que podemos crear aplicaciones únicas que se comporten de manera diferente en función del dispositivo sobre el que se ejecuten o de la orientación del mismo. Así podremos dar una solución visual para teléfonos y otra para tablets, por poner un ejemplo.

Introducción

Definiéndolos de una manera informal, los Fragments son trozos de vista que se pueden mostrar o no dependiendo de lo que nos interese en cada momento, ya sea basándonos en la orientación o tamaño del dispositivo normalmente, aunque como siempre podremos añadir cualquier calificador a los XML en los que se definen.

Los Fragments tienen su propio ciclo de vida y su propia interfaz. En cualquier caso, su contexto de ejecución es una Activity, por lo que si la Activity a la que pertenecen es destruida, sus Fragments también serán destruidos. Estos Fragments pueden ser añadidos y eliminados de la Activity de forma dinámica siempre que no se hayan definido en el XML.

La mejor forma de entenderlo es, como siempre, mediante ejemplos. Vamos a desarrollar el caso más típico, el de un listado de registros. Al pinchar sobre uno de ellos nos muestra una vista de detalles. En posición horizontal mostraremos sólo el listado, y en vertical el listado más el detalle del elemento seleccionado.

Aquí podéis ver lo que conseguiremos una vez terminado el tutorial:

Fragments en Android

Desarrollo

Creando el proyecto

Como os comentaba al principio, desde Android 3.0 ya tenemos incluidas las clases necesarias en el SDK, pero ya sabemos que aún falta mucho para que la gran mayoría actualice a Android 4 en smartphones, por lo que trabajaremos con el Android Support Package, que añade las clases básicas para realizar esta tarea. Los cambios serían mínimos si trabajásemos con un SDK 3.0 en adelante.

Para empezar, nos creamos un nuevo proyecto. Llámalo por ejemplo FragmentTest y elige el API que prefieras, yo trabajaré con el 8 (Android 2.2). Añadimos la Support Library (botón derecho -> Android Tools -> Add Support Library) para poder trabajar con los Fragments y ya estamos listos para empezar a codificar.

Estructura de archivos del proyecto

Es indispensable entender la estructura:

  • src
    • MainActivity.java -> Clase de la actividad principal
    • DetallesActivity.java -> Actividad de detalles, necesaria para la orientación vertical
    • ListaFragment.java -> Fragment con el listado de registros a elegir
    • DetallesFragment.java -> Fragment que muestra los detalles del registro seleccionado
  • res
    • layout-> Layouts usados en el caso más genérico, en este caso se corresponderá con la orientación horizontal
      • detalles_fragment.xml -> Layout para el Fragment de detalles
      • main.xml -> Layout para la actividad principal
    • layout-port-> Layouts usados con el dispositivo en vertical
      • main.xml -> Layout para la actividad principal en vertical
      • detalles_activity.xml -> Layout para la actividad de detalles, que sólo se utiliza en vertical

Tendremos una actividad principal que en modo horizontal contendrá los dos Fragments, el de Lista y el de Detalles, definido en el main.xml. Si embargo, para la orientación vertical (layout-port) utilizará dos actividades, la Main y la de Detalles, y cada una de ellas contendrá un Fragment, como podrás observar en main.xml y detalles_activity.xml.

Layouts

Comenzamos definiendo todos los layouts:

layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >

    <fragment
        android:id="@+id/listaFragment"
        android:layout_width="150dip"
        android:layout_height="match_parent"
        class="com.limecreativelabs.fragmenttest.ListaFragment" ></fragment>

    <fragment
        android:id="@+id/detalleFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.limecreativelabs.fragmenttest.ListaFragment" >
    </fragment>

</LinearLayout>

layout/detalles_fragment.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/detallesTxt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Texto Detalle"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</LinearLayout>

layout-port/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >

    <fragment
        android:id="@+id/listaFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.limecreativelabs.fragmenttest.ListaFragment" />
</LinearLayout>

detalles_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <fragment
        android:id="@+id/detallesFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.limecreativelabs.fragmenttest.DetallesFragment" />

</LinearLayout> 
Clases Java

Ahora es el turno de las clases.

DetallesFragment simplemente infla su layout cuando se crea la vista y aporta un método para modificar el texto que muestra:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class DetallesFragment extends Fragment {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);

	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		View view = inflater.inflate(R.layout.detalles_fragment, container, false);
		return view;
	}

	public void establecerTexto(String item) {
		TextView view = (TextView) getView().findViewById(R.id.detallesTxt);
		view.setText(item);
	}
}

DetallesActivity se utilizará sólo en orientación vertical. Recupera el texto que le enviará la ListaActivity (enseguida lo vemos) a través del Intent y se lo asigna al TextView. En el onCreate hay que comprobar si se encuentra en orientación horizontal, ya que se puede llegar hasta aquí al rotar la pantalla. Si es el caso, se finaliza la actividad.

import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.widget.TextView;

public class DetallesActivity extends FragmentActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		if (getResources().getConfiguration().orientation == 
				Configuration.ORIENTATION_LANDSCAPE) {
			finish();
			return;
		}

		setContentView(R.layout.detalles_activity);
		Bundle extras = getIntent().getExtras();
		if (extras != null) {
			String s = extras.getString("texto");
			TextView view = (TextView) findViewById(R.id.detallesTxt);
			view.setText(s);
		}
	}
} 

Nuestra clase de Lista extenderá ListFragment, que es un Fragment especializado en el que el elemento básico es un ListView. Cuando se crea la actividad que incluye este Fragment, se crea el listado de elementos a mostrar y un adaptador para la lista con un estilo de fila de entre los predefinidos que aporta Android.

En el evento que se lanza al seleccionar un elemento, obtenemos el texto y buscamos el Fragment ayudándonos del FragmentManager. Si el Fragment está en el layout, entonces quiere decir que se encuentra actualmente cargado en la vista, y por tanto estamos en la disposicion horizontal. Lo único que hacemos es modificar el texto que se muestra en dicho Fragment. En caso contrario, estámos en la situación en la que tenemos que lanzar una actividad para el detalle, a la que le pasaremos el texto a mostrar.

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class ListaFragment extends ListFragment {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		
		String[] listItems = new String[] { "Alfa", "Beta", "Gamma",
				"Delta", "Epsilon", "Dseta", "Eta", "Theta", "Iota" };
		
		ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
				android.R.layout.simple_list_item_1, listItems);
		setListAdapter(adapter);
	}

	@Override
	public void onListItemClick(ListView l, View v, int position, long id) {
		
		String registro = (String) getListAdapter().getItem(position);
		DetallesFragment fragment = (DetallesFragment) getFragmentManager()
				.findFragmentById(R.id.detallesFragment);
		
		if (fragment != null && fragment.isInLayout()) {
			
			fragment.establecerTexto(registro);
		} 
		else {
			
			Intent intent = new Intent(getActivity().getApplicationContext(),
					DetallesActivity.class);
			intent.putExtra("texto", registro);
			startActivity(intent);

		}
	}
} 

A la MainActivity no hay que añadirle nada. Asegúrate de que en el onCreate está inflando el main.xml y que extiende FragmentActivity en lugar de Activity.

Esto es todo lo que necesitas para crear tu primer proyecto con Fragments. Si tienes alguna duda o quieres que haga algún otro tutorial extendiendo este tema para algún caso más concreto, házmelo saber en el área de comentarios.

¿Te ha gustado? Compártelo

48 Comentarios

  1. Muchas gracias por la información, muy buen tutorial, pero en layout-port no sera layout-land

    • Hola Jesús. No, si te fijas el layout general es el que se corresponde con el landscape o apaisado, ya que es el que contiene a la vez los dos fragments en la misma Activity, el de la lista y el de detalle. En la carpeta layout-port está el main con un único fragment correspondiente a cuando tienes el dispositivo en posición retrato o vertical.

  2. No se tiene que agregar nada al Manifest?? ya que lo compilo pero me marca errores :S

    • Sí, hay que añadir alguna cosa. Lo que voy a hacer es subir el código completo. Cuando lo tenga me pongo en contacto contigo y lo añadiré al final de este artículo. Gracias!

  3. Gracias por contestar tan rápido, pero ya pude corregir el proyecto si te interesa pongo el link con el proyecto.
    http://www.mediafire.com/download.php?4j3yn5l0p3epy00

    • Gracias! Había algo mal en el código del tutorial? O algún trozo poco explicado o que falte algo (como lo que me decías del AndroidManifest). Así puedo mejorar la entrada para los próximos que lo utilicen.

  4. Comento arriba

  5. Esta tarde probaré el proyecto subido por Jesus, y comento como me ha ido. La semana pasada intenté realizar lo indicado en el manual y uno de los problemas que tuve era el crear una carpeta layout-port con un main.xml dentro.

    Ya os comento.

    • Si puedes, coméntame con que tienes problemas para modificar el tutorial. Gracias!

      • Nada, lo he hecho desde el principio y todo solucionado. :)
        Ahora a investigar a realizar cosas mas complejas, como por ejemplo en funcion de la lista de la izq abrir distintas activity

        • Me surge un problema.
          He creado una nueva clase que extiende de Fragment, con su correspondiente xml y en la clase ListaFragment y creado un caso en que al pulsar al primer elemento de la lista me cambie el fragment de la siguiente manera:

          DetallesFragment fragment = (DetallesFragment) getFragmentManager().findFragmentById(R.id.detalle_Fragment);

          Fragment newFragment = new NuevaClaseFragment(); FragmentTransaction  transaction = getFragmentManager().beginTransaction(); transaction.replace(fragment.getId(),newFragment); transaction.addToBackStack(null); transaction.commit();

          El problema es que se me muestra los dos fragment, uno encima de otro y no se porqué.

          • Disculpa el retraso en contestar, estuve fuera unos días. Creo que el problema es que no puedes declarar el fragment inicial en el XML, porque sino sigue siendo visible posteriormente. Prueba a crearte una vista contenedor y declararlo e insertarlo programáticamente.

          • Bueno he mejorado algo, comento: He sustituido el Fragment del content por un FrameLayout, sin estar asociado a ninguna clase.
            De este modo se ejecuta la app y no muestra nada en el FrameLayout.
            Otro de los cambios es utilizar
            transaction.add en vez de
            transaction.replace. Ahora puedo ir cambiando de uno a otro, lo que realmente se hace es crear una nueva clase y mostrarla.
            Lo que no he conseguido resolver es lo del pulsar al botón atras, ya que me salta un cierre forzoso.

          • Entiendo que debería ser un add la primera vez en el create, para que cargue la primera, y luego hacer replace, pero suena bien en mi cabeza, igual en el código tiene sentido como dices tú. Lo del cierre no puedes depurarlo? o mirar el TomCat a ver qué error da…

          • En mi cabeza tmb sonaba así, haciendo primero un add y luego remplace, pero siempre me falla. Lo del cierre no lo puedo depurar porque justo salta cuando hago el commit()

          • Solucionado, todo el problema que tenía era a causa de un método(onPause) en uno de los Fragment.java que no hacía nada. Lo he eliminado y ya funciona todo correctamente.

            Gracias de todos modos.

          • Me alegro! Gracias por compartir la solución.

  6. excelente tutorial lo tomare como modelo

  7. Hola, veo que ya hace tiempo que este post ha sido publicado, pero espero tener suerte y que me puedas contestar. A ver, empiezo:

    – Fragments y Action Bar se pueden usar en versiones anteriores a ICS ( Froyo, Gingerbread ) con la libreria ActionBarSherlock?? o mejor usar el Android Support Package?

    – La siguiente no se como explicarla muy bien. Si tengo un ListActivity y a partir de cada uno de los items lanzo una Activity distinta, para este caso es mejor usar Fragments??

    Gracias.

    • Hola, sin problema, aunque sean antiguos los contesto. Para el Action Bar se usa ActionBarSherlock, que necesita el Support Package para funcionar. Resumiendo, necesitas las dos para hacer ambas cosas. Una vez hice en el blog un ejemplo de Action Bar con una librería que viene en el Support Package, pero tras probar ActionBarSherlock no la cambio por nada.

      Creo que sí entiendo lo segundo. Cada elemento de la lista abre una Activity distinta, no es la misma con distintos datos. Eso más que de la funcionalidad, depende del aspecto. Si quieres que en las tablets se vea a la izquierda la lista y a la derecha esa Activity, mientras que en los móviles sólo se vea la lista y al pinchar sólo la Activity, necesitas obligatoriamente Fragments.

      Si no queda algo claro, escribe de nuevo sin problema. Saludos!

      • Joder que rapido!! No me ha quedado claro, puedo usar Fragments en versiones anteriores a ICS? y si es posible, con que librerias? es que antes solo me has respondido solo para Action Bar.
        Gracias.

        • Sí, no me expliqué demasiado bien. Sí que se puede con el Android Support Package, usando la clase FragmentActivity en lugar de Activity.

          • Muchas gracias!! Voy a probar un poco el tema a ver que tal va. Supongo que te preguntare algo mas porque esto me interesa.

            Gracias de nuevo.

          • Hola, disculpame, pero tienes algun enlace con el codigo, es que no entiendo porque no para de fallar el codigo y estoy ya un poco perdido toqueteando por aqui y por alla.

            Gracias.

          • Aquí he encontrado un ejemplo simple (no lo he probado): http://goo.gl/kGYgU
            Luego también puedes seguir este artículo que hice. Te genera un proyecto preparado con Fragments del support package. Si quieres usarlo con el sdk anterior al 3, tendrás que hacer algún apaño, pero como poco lo puedes usar para mirar el código y ver en qué está fallando el tuyo: http://goo.gl/uJGfW

          • Hola, creo que lo que me falla es esto: “A la MainActivity no hay que añadirle nada. Asegúrate de que en el onCreate está inflando el main.xml y que extiende FragmentActivity en lugar de Activity.”
            Como inflo el main.xml??. Porque de hecho me salen “2 columnas” con el alfabeto griego, pero en el momento que pulso sobre algun item, la aplicacion “se rompe” y se cierra.
            Siento tanta insistencia, pero es que me gusta bastante la distribucion de este codigo.

          • La verdad es que yo también estoy un poco despistado. Este código ya está usando el Android Support Package.
            En el código de la actividad principal tienes que usar la siguiente línea:
            setContentView(R.layout.main);
            Eso es inflar, decirle a la actividad que use un layout. El proceso de inflar es convertir el xml en objetos de la interfaz.
            Pero si te está mostrando inicialmente la lista es porque ya lo estás haciendo. Esta tarde busco el código, y si lo localizo lo añado al final del artículo para que lo descargues y lo pruebes. Eso de que te salgan dos columnas no me queda muy claro

          • Muchas gracias, aunque sea por el detalle y el interes mostrado.

            (Aclaracion: dos columnas esta muy mal explicado, perdona, son dos list con el alfabeto griego en cada una, lo digo porque lo he “volcado” en un tablet con froyo)

  8. Hola, ante todo quiero pedir perdon, me he dado cuenta tras mucho trastear que en el codigo habia una cosilla mal, y despues de corregirla, me he dado cuenta que alguien habia puesto un enlace con el proyecto hecho. Muchas Gracias por la paciencia.

    • No te preocupes, discúlpame a mí que con las prisas no recordé que otro compañero ya lo subió en una ocasión. Te espero de nuevo por aquí para lo que quieras!

  9. Hola, una pregunta, se puede usar la fragmentación en una GridLayout?? lo que quiero hacer es lograr controlar el tamaño de los fragment

    • Pues no lo he probado, pero en principio no le veo mayor problema. El fragment se renderizará donde le indiques en el XML o lo cargues por código.

  10. Buenas, muy bueno el tuto, pero me surge una duda.

    Si quiero meter en el fragment de la izquierda, 2 listview o algo más “compuesto”, ya no tendría que extender ese fragment de “ListFragment” no? Tendría que ser un fragment normal?

    Y a la hora de cargar ese layout complejo, como sería? En Oncreate llamo a “setContentView” y cargo ese layout?.

    Un saludo!

    • Correcto, cualquier cosa algo más compuesta necesita un Fragment genérico y que le especifiques el layout en un XML, y lo cargas con el setContentView. Vas por buen camino!

      • Gracias, después de investigar un rato conseguí sacarlo.

        Un saludo.

  11. Hola buenos días, una pregunta, ¿Hay alguna forma de meter una lista personalizada a un fragment, para que muestre mas de dos lineas en la lista.

    • Supongo que te refieres a un ListFragment. Si es así, eso se indica en el adapter, con la función setListAdapter. Independientemente de esto, siempre puedes inflar cualquier vista en el onCreateView para personalizar la ListView todo lo que necesites. Esto último es válido para todo tipo de Fragments.

  12. Hola buenas tardes, mi pregunta es la siguiente, se puede mandar a llamar una clase que implementa una librería dentro de un Fragment?? Tengo el XML donde tengo el Fragment, pero me interesa implementar la clase con la librería externa, en este caso es la de Look

    • No entiendo muy qué quieres hacer, crear el Fragment en una librería externa? O llamar a una librería externa desde un Fragment? Cualquiera de las dos cosas se pueden hacer sin problema.

      • Llamar una librería externa en el Fragment

        • Por ejemplo tengo public class NombreClase extends LookAR y para usar los Fragments tengo que extender de Fragment, lo cual no eh podido hacer, hay alguna forma de implementar esa clase en el Fragment?. De antemano muchas gracias.

  13. Buenas tardes!
    Enhorabuena por la páginas… es realmente útil.
    Simplemente comentar que aunque es una tontería, a lo mejor a alguno le da al ojo… En el código: layout/main.xml hay un error -> en el 2º fragment deberia poner: DetallesFragment.

    Un saludo

  14. EXCELENTE! MUCHISIMAS GRACIAS :D probando apenas, vere si tengo problemas con el manifest! gracias :)

  15. Buenos dias, he leido un poco tarde tu artículo y he pensado que me podrías resolver una duda….necesito actualizar un fragment ya cargado en la activity. Es una aplicación que muestra en un fragment el resultado de una consulta a una BBDD en forma de listado y necesito desarrollar un boton que me actualice el fragment con los nuevos resultados de la BBDD(típico botón refresh). No se exactamente como enfocarlo, quizás puedas echarme una mano y ya abusando de tu amabilidad, que ampliaras el tutorial con este asunto.

    Muchisimas gracias y agradecerte tu labor en esta web.

  16. Buen tuto, sobre todo para los no expertos como yo, con un problema tremendo.
    Tengo el siguiente fragment (con varias imágenes) que proviene del menú:

    import android.annotation.SuppressLint;
    import android.app.Fragment;
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    import android.widget.Toast;

    @SuppressLint(“NewApi”)

    public class Razas extends Fragment

    implements View.OnClickListener {
    ImageView imageAmerica;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.continentes, container, false);

    imageAmerica = (ImageView) rootView.findViewById(R.id.imageAmerica);

    imageAmerica.setOnClickListener(this);

    return rootView;
    }

    @Override
    public void onClick(View ImageView) {

    Toast.makeText(this.getActivity(),
    “Has pulsado America”, Toast.LENGTH_LONG).show();

    }

    }

    Hasta aquí funciona.
    Pero quiero sustituir el Toast y declarar varias imágenes (en este fragment) y que al pulsar cada una me lance a unas activity llamadas america.xml, europa.xml,…etc.
    Estoy perdido…

  17. excelente tutorial ;)
    disculpa yo quisiera saber como lanzar un activity dentro de un fragment usando Onclick

    • Los fragments tienen un startActivity igual que las activities, así que simplemente es usar ese método.

Trackbacks/Pingbacks

  1. Ejemplo de vista de detalle en Android usando Fragments | LiME - [...] Tutorial sobre Fragments en Android [...]
  2. Activities en Android | LiME Creative Labs | LiME Creative Labs - [...] se desarrollan los temas de Services y Broadcasts, quizá quieras echarle un vistazo a los Fragments, las estructuras que …
  3. 10 librerías gratuitas que todo desarrollador Android debe conocer - [...] las innovaciones de los últimos SDK con versiones antiguas. Puedes por ejemplo desarrollar con Fragments o utilizar los fantásticos …

Deja tus comentarios

¿Has probado ya Bandhook? Te presento mi nueva aplicación, en la que podrás consultar información de tus artistas favoritos y descubrir otros nuevos relacionados.

Bandhook - Discover new music