Fragments

Podríamos definir a un fragmento como una porción de interface de usuario o vista que se integra en una activity. Por lo tanto tendremos la posibilidad de combinar múltiples fragmentos en una sola actividad o incluso reutilizar fragmentos en otras actividades. De esta manera cada fragmento tendrá su propio ciclo de vida, recibirá sus propios eventos de entrada y se podrá agregar o eliminar mientras la activity de acogida este en marcha.

En este articulo vamos a ver como crear fragmentos, agregarlos a una actividad, cambiar entre fragmentos y como comunicar fragmentos. Al final del articulo tendremos para descargar el código fuente de una mini aplicación sobre fragmentos.

Toda la información a sido sacada de la pagina de desarrolladores de Android: Fragments.


1. Ciclo de vida

          

La imagen de la izquierda representa el ciclo de vida de un fragmento. Y la imagen de la derecha los estados de sus metodos.

Generalmente a la hora de crear un fragmento se deben implementar al menos los siguientes métodos:
  • onCreate() - El sistema llama a este método a la hora de crear el fragmento, normalmente iniciaremos los componentes esenciales del fragmento.
  • onCreateView() - El sistema llamara al método cuando sea la hora de crear la interface de usuario o vista, normalmente se devuelve la view del fragmento.
  • onPause() - El sistema llamara a este método en el momento que el usuario abandone el fragmento, por lo tanto es un buen momento para guardar información.



2. Crear un fragmento

Para crear un fragmento primero deberemos extender la clase "Fragment" y sobreescribir el método "onCreateView()" en el que devolveremos la vista de dicho fragmento. Vamos a ver el ejemplo y lo explicamos a continuación:
public class FragmentUNO extends Fragment {
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, 
            Bundle savedInstanceState) {

        return inflater.inflate(R.layout.fragment_uno, container, false);
    }
 
}
Comentar que previamente se a creado el layout para este fragmento y se corresponde con "fragment_uno.xml".

Al sobreescribir el metodo "onCreateView()" podemos observar que de serie nos da la posibilidad de utilizar un LayoutInflater, un ViewGroup y un Bundle. El LayoutInflater normalmente lo utilizaremos para inflar el layout de nuestro fragment. El ViewGroup sera la vista padre donde se insertara el layout de nuestro fragment. Y por ultimo el Bundle podremos utilizarlo para recuperar datos de una instancia anterior de nuestro fragment.

El método "inflate()" toma tres parámetros. En el primero indicamos la id del layout de nuestro fragment. En el segundo indicaremos la view padre de nuestro fragmento. Y por ultimo un booleano que nos servirá para indicar si el inflado del layout debe ser insertado en el ViewGroup (En este caso es false porque directamente se esta insertando el layout en el ViewGroup).

De esta manera ya tendremos creado un fragment que nos devolverá una vista y que podremos insertar en cualquier activity de nuestro proyecto.


2.1 SubClases de Fragment

A parte de crear un Fragment directamente, Android nos ofrece la posibilidad de utilizar las siguientes subclases de Fragment:
  • DialogFragment - Muestra un cuadro de dialogo flotante. 
  • ListFragment - Muestra una lista de elementos.
  • PreferenceFragment - Muestra una lista de preferencias.



3. Agregar un Fragment a una Activity

A la hora de agregar un fragmento a una actividad lo podremos realizar de dos maneras:
  1. Declarar el fragmento en el layout de la activity.
  2. Agregar directamente el Fragment mediante programación Android.


3.1 Agregar Fragment mediante layout

Lo primero que tenemos que hacer es crear el layout de nuestra activity especificando un elemento fragment:
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <fragment 
        android:name="com.datohosting.fragments.FRAGMENTOS.FragmentUNO"
        android:id="@+id/fragment_uno"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    
</LinearLayout>
Simplemente creamos el elemento "fragment" y especificamos a través del atributo "android:name" la ubicación de nuestro fragmento (nombre de paquete donde esta ubicado el fragmento).

Es recomendable crear un identificador único para cada fragmento que nos puede servir para restaurar fragmentos o incluso para realizar transacciones o eliminación de fragmentos. Hay tres maneras de declarar un identificador:
  1. Establecer el atributo "android:id".
  2. Establecer el atributo "android:tag"
  3. No establecer atributos y el sistema cojera automáticamente la id de la vista contenedora.

Una vez creado el layout de la activity simplemente creamos una actividad y le aplicamos el metodo "setContentView()" indicando la id del layout que acabamos de crear. El resultado puede ser el siguiente (en la imagen de ejemplo se han declarado dos elementos fragment en el layout de la activity):




3.2 Agregar Fragment mediante programación

De esta manera podemos agregar un fragment a una activity en cualquier momento. Simplemente indicaremos la id de una vista padre (ViewGroup) donde deberá colocarse el fragment.

Para ello tenemos que utilizar la API FragmentTransaction y a través de su método "add()" añadiremos el fragmento a la vista padre. Despues de añadir el fragment tendremos que terminar la transacción a través del método "commit()".

Vamos a ver el ejemplo y lo explicamos a continuación:
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  
    setContentView(R.layout.mostrar_fragment_dos);
  
    FragmentManager FM = getSupportFragmentManager();
    FragmentTransaction FT = FM.beginTransaction();
  
    Fragment fragment = new FragmentUNO();
    FT.add(R.id.fragment_container, fragment);
    FT.commit();
}
En este caso nuestra activity muestra por defecto un layout "mostrar_fragment_dos.xml" que contiene un ViewGroup, en este caso en un RelativeLayout que no muestra nada pero nos va a servir para agregar nuestro fragment en dicha vista padre.

Lo primero que hacemos es crear una instancia de "FragmentManager" a través de su método "getSupportFragmentManaget()" (dicho método corresponde a la librería de compatibilidad v4). De esta manera estamos creando un objeto que nos servirá para manejar los fragmentos.

A continuación creamos una instancia de "FragmentTransaction" para realizar la transacción de fragmentos. A través del método "beginTransaction()" indicamos que vamos a realizar una transacción de fragmentos.

Lo siguiente es crear una instancia de nuestro "FragmentUNO" y añadirla a la transacción a través del método "add()" que nos pide como parámetros la id del ViewGroup o vista padre donde se colocara dicho fragmento (en este caso es la id del RelativeLayout que comentábamos antes) y como segundo parámetro nos pide la instancia del fragmento que se mostrara dicha vista.

Para terminar la transacción siempre deberemos declarar el método "commit()". Y como resultado podremos ver lo siguiente:




4. Gestionar Fragments

En el punto anterior hemos hablado de transacciones, una transacción simplemente es una acción que nos permite agregar, reemplazar, eliminar o incluso realizar otras acciones cuando trabajamos con fragmentos. Estas transacciones pueden ser apiladas por la activity de acogida, permitiendo así al usuario navegar entre fragmentos mientras la activity siga en ejecución.

Cada transacción es un conjunto de cambios que se realizan al mismo tiempo. Podremos realizar dichos cambios a través de los métodos "add()", "replace()", "remove()" terminando la transacción con el método "commit()".

Para añadir la transacción a la pila de retroceso de la activity utilizaremos el método "addToBackStack()" para cada transacción que realicemos. Esta pila sera administrada por la activity y permitirá al usuario volver a un fragmento anterior pulsando la tecla volver del smartphone o tablet.

Por otra parte a través del método "setTransaction()"  podemos establecer el tipo de animación para cada transacción.

Para este caso se a creado un ejemplo que muestra un layout con un ViewGroup para mostrar los fragmentos y un botón que servirá para añadir fragmentos a la pila de retroceso. Empezaremos creando el layout que mostrara la activity:
<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <RelativeLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/boton"
        android:layout_below="@+id/texto"/>
    
    <Button 
        android:id="@+id/boton"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="CAMBIA FRAGMENT"/>

</RelativeLayout>
Una vez creado el layout deberemos crear la siguiente activity:
private boolean bol = false;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  
    setContentView(R.layout.reemplazar_fragments);
  
    final Fragment fragmentUNO = new FragmentUNO();
    final Fragment fragmentDOS = new FragmentDOS();
  
    Button boton = (Button) findViewById(R.id.boton);
    boton.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            FragmentTransaction FT = getSupportFragmentManager().beginTransaction();
    
            if (bol) {
                FT.replace(R.id.fragment_container, fragmentUNO);
            } else {
                FT.replace(R.id.fragment_container, fragmentDOS);
            }
    
            FT.addToBackStack(null);

            FT.commit();
    
            bol = (bol) ? false : true;
        }
    });
}
Comentar que previamente se han creado dos fragmentos "FragmentUNO" y "FragmentDOS", cada uno con su layout.

En la activity primero creamos un valor booleano que nos servirá para cambiar entre un fragmento u otro cada vez que el usuario pulse el botón.

Establecemos el layout de la activity y creamos dos instancias de nuestros fragmentos. A continuación declaramos nuestro botón y le aplicamos un listener.

Cada vez que el usuario pulse el botón se creara una transacción añadiendo a la pila un fragmento u otro a través del método "addToBackStack()" que pide como parámetro un tag o identificador para la transacción que se va a realizar.

Se finaliza la transacción con el método "commit()" y cambiamos el valor booleano.

El resultado puede ser el siguiente:


Cada vez que el usuario pulse el botón, se creara una nueva transacción que se ira acumulando en la pila de retroceso. Simplemente tocando la tecla volver de nuestro smartphone o tablet iremos retrocediendo los fragments.



5. Comunicar Fragments y Activities

Para comunicar un fragmento con su activity de acogida podemos utilizar el método "getActivity()", por lo tanto podremos localizar una vista de la activity de la siguiente manera:
View vista = getActivity().findViewById(R.id.lista);

Por el contrario si queremos comunicar una Activity con un Fragment, deberemos crear una instancia del fragmento aplicándole uno de los métodos "findFragmentById()" o "findFragmentByTag()" de la siguiente manera:
FragUNO fragment = (FragUNO) getFragmentManager().findFragmentById(R.id.frag_UNO);


5.1 Eventos de llamada

En este punto vamos a ver como podemos pasar valores de un fragmento a otro. Por lo tanto en algunos casos sera necesario que el fragment comparta eventos con la activity. La manera ideal es crear una interface en el fragmento y exigir a la activity que la implemente. De esta manera cuando el fragmento reciba un evento también lo hará la activity, que se encargara de recibir los datos de ese evento y compartirlos con otros fragmentos.

Vamos a empezar creando un fragmento "ListFragment" y declararando la interface:
public interface OnArticuloSelectedListener {
    public void onArticuloSelected(String str);
}
A esta interface la hemos llamado "onArticuloSelectedListener" y simplemente contiene un método llamado "onArticuloSelected" con un único parámetro.

Lo siguiente es sobreescribir el método "onListItemClick" en el mismo fragmento, que se encargara de enviar el evento y los datos del evento a la interface. De esta manera cada vez que el fragmento reciba un evento, automáticamente lo mandara a la interface que a su vez lo recibirá la activity:
private OnArticuloSelectedListener listener;

@Override
public void onListItemClick(ListView l, View v, int position, long id) {
    listener.onArticuloSelected(valores[position]);
}
Lo primero que necesitamos es una instancia de nuestra interface "listener". Una vez creada sobreescribimos el método "onListItemClick" y le aplicamos el método de la interface indicando como argumento los datos que se compartirán por cada evento del usuario.

Para comprobar de que la Activity de acogida implementa la interface vamos a sobreescribir el método "onAttach()" que sera llamado cada vez que la activity cree una instancia del fragmento.
@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        listener = (OnArticuloSelectedListener) activity;
    } catch (ClassCastException e) {}
}

Para terminar de completar el código de nuestro ListFragment vamos a crear la vista:
private String[] valores = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setListAdapter(new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1, valores));
}

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

Simplemente creamos un array string con los valores de la lista. Lo aplicamos en el "onCreate" y establecemos la vista en el "onCreateView()".


Para terminar deberemos crear la activity e implementar la interface:
               .... implements OnArticuloSelectedListener {
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.comunicar_fragments);
  
    FragmentManager FM = getSupportFragmentManager();
    FragmentTransaction FT = FM.beginTransaction();
  
    Fragment fragmentTRES = new FragmentTRES();
    FT.replace(R.id.fragment_container, fragmentTRES);
    FT.commit();
}

public void onArticuloSelected(String str) {
    Fragment fragmentCUATRO = new FragmentCUATRO();
  
    Bundle args = new Bundle();
    args.putString("str", str);
    fragmentCUATRO.setArguments(args);
  
    FragmentManager FM = getSupportFragmentManager();
    FragmentTransaction FT = FM.beginTransaction();
  
    transaction.replace(R.id.fragment_container, fragmentCUATRO);
    transaction.addToBackStack(null);

    transaction.commit();
}
Implementamos la interface del fragment y en el onCreate cargamos su layout agregando el fragment.

En el método que nos obliga a sobreescribir la interface guardamos los datos que recibimos de cada evento del fragment en un Bundle y se lo aplicamos a un nuevo fragment. A continuación se crea una transacción a este nuevo fragment y se añade a la pila de retroceso.

Para terminar, el nuevo fragment recuperara los datos de la siguiente manera:
Bundle bundle = getArguments();
String recuperada = bundle.getString("str");

El resultado de todo esto puede ser el siguiente:


Tenemos un Fragmento TRES con una lista, cada vez que el usuario pulse un ítem de la lista se cargara un Fragmento CUATRO mostrando un texto con la opción elegida.



6. Añadir elementos de acción

Los fragmentos son capaces de añadir elementos de acción a la ActionBar o en su defecto al menú overflow. Para ello simplemente tendrán que implementar el método "onCreateOptionsMenu()". Para que este método surja efecto, el fragmento deberá llamar en el onCreate a "setHasOptionsMenu()" indicando como parámetro "true". Todos los elementos que desee añadir el fragmento se sumaran a los ya existentes.

El resultado de crear un menu.xml e inflarlo en el método onCreateOptionsMenu puede ser el siguiente:


A parte también podremos crear menús contextuales registrando una vista del layout de nuestro fragment a través del método "registerForContextMenu()". Para crear el menú contextual utilizaremos el método "onCreateContextMenu()". Y para registrar los eventos de ese menú "onContextItemSelected()".

Si juntamos todo lo anterior podremos crear un menú como el de la imagen:




7. Mini aplicación Fragments

Para importar esta mini aplicación a Eclipse deberemos agregar al proyecto la librería de compatibilidad v7. En el siguiente enlace explica como hacerlo: Añadir librería de compatibilidad.

DESCARGA: EJEMPLO FRAGMENTS


22 comentarios:

  1. Muy bien explicado.

    ResponderEliminar
  2. Gracias por el tutorial, lo quería seguir, pero al importar tu proyecto dentro de eclipse me da errores te dejo una captura de pantalla, por si me puedes ayudar por favor y gracias.
    http://prntscr.com/3clu8v

    ResponderEliminar
    Respuestas
    1. Revisa las pestañas de "Problems" "Error Log" "Console" "Logcat" Haber si encuentras el fallo.

      Hay veces que la libreria "android-support-v4.jar" es distinta en los dos proyectos y puede dar lugarr a problemas similares.

      Eliminar
    2. Hola disculpa, como lograste quitar esos errores?.. Gracias :)

      Eliminar
  3. amigo, he tenido muchos problemas con los Fragmentos en Android. No puedo crear un proyecto que no los tenga, y me ocasiona muchos problemas al momento de querer correr una aplicación, Me gusto tu explicación entendí muy bien. pero quiero hacer una App sin fragmentos, me podrías explicar como es que creo un proyecto sin fragmentos??

    ResponderEliminar
    Respuestas
    1. Yo opino lo contrario, jejej. Los fragmentos me encantan porque puedes agrupar aun mas las cosas.

      Pero si lo que quieres es no utilizarlos, simplemente tienes que ir creando activities y ya esta

      Eliminar
  4. Buen tuto, sobre todo para los no expertos como yo.
    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...

    ResponderEliminar
    Respuestas
    1. Hola.... disculpa....yo tengo dudas.... sabes como abrir una nueva actividad (layout) por medio de un botón que se encuentra en un Fragment...

      Por favor.

      Eliminar
    2. Con un Intent básico no te funciona?

      Eliminar
  5. Hola, excelente excelente explicacion, tengo una duda si quiero mostrar la camara en tiempo real de fondo y en unos label los datos del acelerometro tambien en tiempo real, me serviria usar fragments ?? puedo poner uno sobre otro y hacer un fondo transparente para poder ver la camara ??

    ResponderEliminar
  6. Gracias, muy bueno yo me apoye con esto para el 4.4 cambia un poco pero funciona

    ResponderEliminar
  7. baul de android...... excelente aporte grax

    ResponderEliminar
  8. El mejor aporte de fragment que he visto, llevo dos días peleandome con ellos, gracias a ti, he conseguido enterderlos y solucionar mi problema. Es de agradecer gente que sepa tanto (nivel maestro) y que lo comparta.

    De corazón, gracias... ;-)

    ResponderEliminar
  9. Muchas gracias por tu explicación. Tengo una consulta, cuando tienes un Activity A y dentro un Fragment B que se encuentra activo; entonces al rotar el dispositivo ¿Cómo se ejecuta el ciclo de vida de ambos: de A y B? ¿Tendría que volver añadir B en A o lo maneja el sistema operativo?

    A mi entender B se destruye y A se reinicia. Entonces, a mi entender, tendría que volver a añadir B con los datos que haya usado inicialmente y guardados en el onSaveInstanceState de A. ¿O cual sería la mejor práctica al respecto?

    Gracias por tu apoyo.

    ResponderEliminar
  10. Si necesito acceder a las propiedades o al elemento en si (digamos el ListView) dentro del fragment, como lo hago?
    Necesito colocarle el adapter al listView que tengo dentro del Fragment

    ResponderEliminar
  11. Muchas Gracias, creo que es la mejor explicación de todas las que he vistos. Además, el ejemplo en código implementado me sirvió demasiado.

    ResponderEliminar
  12. Hola .... Buenas madrugadas....

    El código que has expuesto me ha servido para entender el funcionamiento de los Fragments...

    Necesito abrir una nueva actividad (layout) por medio de un botón que se encuentra en un Fragment...

    Por favor... esto ya me esta atormentando.... :'(

    ResponderEliminar
  13. Excelente explicaciòn la parte del ciclo de vida de una aplicaciòn en android !! Me encanto lo puedo recomendar mucho, gracias. Espero contribuir para la comunidad yo tome este curso de android http://www.grupocodesi.com/cursos/curso-de-android.html y aprendi muchisimo

    ResponderEliminar
  14. Hola, buena guía...
    Deseo saber a que te refieres exactamente con FragmentUNO
    "final Fragment fragmentUNO = new FragmentUNO();"

    ResponderEliminar
  15. Tengo una duda, se puede realizar búsquedas sobre fragmemts?

    ResponderEliminar
  16. Hola, tengo varios fragments que los voy llamando dentro de un activity. Lo que pasa es que cuando se bloquea el celular y se vuelve a la actividad los fragments se muestran todos a la misma vez, en capas. ¿Cómo podría solucionar eso? Saludos.

    ResponderEliminar
  17. ¿Existen los ejemplo resueltos en algún sitio?

    ResponderEliminar