Fragments en Android

De ChuWiki

Ya hemos visto como cambiar de Activity en Android, cambiando la ventana completa de la pantalla. A veces nos interesa tener trozos de ventana que reutilizaremos en varias Activity, esto es lo que Android conoce como Fragments. Es decir, fragmentos de ventana que podemos reutilizar en varias ventanas más grandes. Por ejemplo, diálogos de aviso, listas con elementos, formularios para introducir determinados datos simples como dirección de correo, teléfono, etc.

Veamos aquí un pequeño ejemplo. Una ventana principal con un texto y botón "Pulsame". Al pulsar el botón aparecerá dentro de la misma ventana un formulario consistente en una caja de texto para rellenar y dos botones "Ok" y "Cancel". Escribiendo algo en la caja de texto y pulsando "Ok", el formulario desaparecerá y el texto escrito reemplazará al de la ventana principal.

Todo el código está en fragment-propio-android e iremos enlazando los distintos ficheros, pero en este tutorial sólo se copiarán los trozos más importantes de dicho código.


Diseño del Fragment[editar]

El trozo de ventana que queremos reutilizar en varios sitios de nuestra aplicación se puede diseñar desde un XML, igual que una Activity de Android. El de nuestro ejemplo puedes verlo en res/layout/formulario.xml. No es más que un LinearLayout vertical con dos componentes. El primero es la caja de texto editable EditText y el segundo es otro LinearLayout, esta vez horizontal, para poder poner dentro los botones de Ok y Cancel.

<?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="wrap_content"
    android:orientation="vertical" >

    <EditText
        android:id="@+id/textoForumulario"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:text="hola"/>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/aceptar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Ok" >
        </Button>

        <Button
            android:id="@+id/cancelar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Cancel" >
        </Button>
    </LinearLayout>
</LinearLayout>

Aquí unicamente mencionar que en los botones hemos puesto android:layout_width="wrap_content" de forma que cada botón tenga de ancho lo necesario para mostrar el texto, pero que hemos añadido también en ambos android:layout_weight="1", de esta forma cada uno tendrá el mismo "peso" en horizontal y se expandirán hasta ocupar el 50% del ancho disponible cada uno, es decir, el 100% entre los dos


Añadir el Fragment en nuestra aplicación Android[editar]

Podemos añadir el Fragment en nuestra aplicación de dos formas. Por un lado, podemos añadirlo de forma fija desde el fichero layout xml de la aplicación, de forma que estará presente desde que lo arranquemos. O bien podemos no añadirlo en ese momento y añadirlo más adelante desde código java. Veamos ambas posibilidades, aunque seguiremos el ejemplo con la segunda.

Añadirlo de forma fija en el layout xml[editar]

Para añadirlo en el layout xml, no hay más que usarlo como un componente más usando el tag <fragment> e indicando cual es la clase asociada a ese Fragment (la veremos más adelante). En el xml de nuestra ventana principal tendremos que poner algo como

<?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="vertical" >

    <!-- Otros componentes que queramos -->

    <fragment
        class="com.chuidiang.ejemplos.android.FormularioFragment" 
        android:id="@+id/formulario"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"/>

    <!-- Otros componentes que queramos -->
</LinearLayout>

La clase sería en este ejemplo com.chuidiang.ejemplos.android.FormularioFragment y la veremos más adelante.


Añadir el Fragmente dinámicamente desde código java[editar]

Si queremos añadirlo desde código java de forma dinámica, en el xml de nuestra ventana principal debemos dejar el "hueco" para poder añadirlo. Esto se hace usando el tag <FrameLayout>. En nuestra ventana para seguir el ejemplo propuesto al principio, hemos añadido también un texto no editable (TextView) y un botón que será el encargado de visualizar el Fragment cuando lo pulsemos (Button).

<?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="vertical" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

    <Button
        android:id="@+id/buton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Pulsame" >
    </Button>

    <FrameLayout
        android:id="@+id/contendorFormulario"
        android:layout_width="fill_parent"
        android:layout_height="match_parent" />
</LinearLayout>

Al FrameLayout le hemos puesto un id para poder acceder a él desde código.


La clase java del Fragment[editar]

Como toda vista, el Fragment tiene una clase asociada que es a la que llamará Android cuando sucedan "cosas" en nuestro Fragment (lo creen, se visualice, se oculte, lo añadan a una ventana, etc). Esta clase java debe heredar de Fragment y hay un método que es obligatorio redefinir, el método onCreateView(). Tiene el código completo en FormularioFragment.java. Vamos a ver qué tiene onCreateView()

   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container,
         Bundle savedInstanceState) {
      View vista = inflater.inflate(R.layout.formulario, container, false);
      ((Button) vista.findViewById(R.id.aceptar))
            .setOnClickListener(new OnClickListener() {

               @Override
               public void onClick(View v) {
                  pulsadoBoton(v);
               }
            });
      ((Button) vista.findViewById(R.id.cancelar))
            .setOnClickListener(new OnClickListener() {

               @Override
               public void onClick(View v) {
                  pulsadoBoton(v);
               }
            });

      return vista;
   }

Este método debe crear la vista (la View) y devolverla. Android lo llamará cuando necesite visualizar este Fragment por primera vez. Como tenemos la vista en un fichero res/layout/formulario.xml, podemos obtenerla con el método inflate() de la clase inflater que nos pasan

View vista = inflater.inflate(R.layout.formulario, container, false);

Los parámetros son

  • R.layout.formulario es el identificador del fichero formulario.xml, que se genera automáticamente en la clase R.layout
  • container es el contenedor donde se va a meter nuestro fragment. Como nos lo pasan como parámetro en el método onCreateView(), sólo tenemos que usarlo.
  • Un flag que indica si el método inflate() debe meter el fragment en el contendor. Como esto ya lo va a hacer Android (para eso nos ha llamado a este método onCreateView()), ponemos false. Si pusieramos true, se añadiría dos veces.

Y una vez que tenemos la vista, aprovechamos para hacer con ella cualquier cosa que necesitemos. En este ejemplo, añadir los onClickListener a ambos botones. Usamos vista.findViewById() para obtener los botones y les añadimos los onClickListener. En estos Listener llamamos a un método pulsadoBoton() que veremos más adelante y que está en esta misma clase.


Visualizar el Fragment desde nuestra Activity[editar]

Para visualizar el Fragmente desde nuestra Activity (ventana principal), tenemos el código java de la clase asociada a la ventana principal AndroidHolaMundoActivity.java y en concreto, el código del botón

      Button boton = (Button) findViewById(R.id.buton);
      boton.setOnClickListener(new OnClickListener() {

         @Override
         public void onClick(View v) {
            FragmentManager fm = getFragmentManager();
            Fragment editor = fm.findFragmentByTag("editor");
            if (null == editor) {
               FragmentTransaction ft = fm.beginTransaction();
               ft.add(R.id.contendorFormulario, new FormularioFragment(),
                     "editor");
               ft.commit();
            }
         }
      });

El proceso básico es

  • FragmentManager fm = getFragmentManager(); --> Obtener el FragmentManager con el método getFragmentManager()
  • FragmentTransaction ft = fm.beginTransaction(); --> Comenzar una "transacción", es decir, indicar que vamos a empezar a "mover" trozos de ventana de sitio
  • ft.add(R.id.contendorFormulario, new FormularioFragment(), "editor"); --> Añadir el Fragment al "hueco" que deseemos y conviente dar un texto cualquiera para identificar ese Fragment recién creado, en este caso "editor". Veamos los detalles
    • R.id.contendorFormulario es el identificador del hueco que creamos en la vetana principal para poner el Fragment. ¿Recuerdas el FrameLayout que habíamos puesto en nuestro layout xml?. Pues ese mismo.
    • new FormularioFragment() Una instancia de la clase asociada al Fragment,
    • "texto" Un texto cualquiera que nos permita identificar a este Fragment más adelante.
  • ft.commit(); --> Para hacer efectivos los cambios.

Bueno, esto es lo básico. Pero debemos protegernos si se pulsa el botón varias veces sin haber ocultado el Fragment previamente. Por ello, al pulsar el botón, buscamos si el Fragment ya está añadido con el método Fragment editor = fm.findFragmentByTag("editor");, usando el texto "texto" que hayamos decidido para identificar el Fragment cuando lo añadimos. Si esta llamada devuelve null, es que el Fragment no ha sido añadido a la ventana principal y entonces comenzamos todo el proceso de añadirlo. Si este método no devuelve null, es que el Fragment ya está visible y no tenemos que hacer nada.

Una vez hecho todo esto y si funciona bien, pulsando el botón debería visualizarse nuestro fragment.


Recuperar datos del Fragment[editar]

Una vez que el usuario meta algo en la caja de texto del Fragment y pulse "Ok" o "Cancelar", el Fragment debería desaparecer y en el caso de "Ok", el texto introducido debería pasarse a la ventana principal para que reemplace al texto fijo de esta (el del TextView).

Cuando un Fragment se mete dentro de una Activity (ventana), Android avisa al Fragment llamando a su método public void onAttach(Activity activity). Podemos redefinir este método para guardarnos la Activity y así poder pasarle el texto cuando se pulse "Ok" y avisarle cuando se pulse "Cancel". La mejor forma de hacer esto es definir una interfaz

   public interface FormularioListener {
      public void pulsado(int resultado, String texto);
   }

En la que int resultado será, por ejemplo, un 0 para OK o un 1 para CANCEL. El texto será lo que ha escrito el usuario. Nuestra Activity deberá implementar esta interfaz

public class AndroidHolaMundoActivity extends Activity implements FormularioListener {
   ...
   @Override
   public void pulsado(int resultado, String texto) {
      ...
   }
}

y de esta forma, nuestro Fragment, en su método onAttach, podrá guardarse la Activity y tendrá metodos para avisarle cuando el usuario cierra el Fragment

public class FormularioFragment extends Fragment {

   @Override
   public void onAttach(Activity activity) {
      super.onAttach(activity);
      if (activity instanceof FormularioListener) {
         listener = (FormularioListener) activity;
      }
   }
   ...
   public final static int OK = 0;
   public final static int CANCEL = 1;
   private FormularioListener listener;

Habíamos visto que los onClickListener de los botones "Ok" y "Cancel" llamaban a un método pulsadoBoton() de FormularioFragment. Ahora es el momento de ver su contenido

public class FormularioFragment extends Fragment {
   ...
   public void pulsadoBoton(View v) {
      if (null == listener) {
         return;
      }
      if (((Button) v).getText().equals("Ok")) {
         listener.pulsado(OK,
               ((EditText) getActivity().findViewById(R.id.textoForumulario))
                     .getText().toString());
      } else {
         listener.pulsado(CANCEL, "");
      }
   }
   ...
}

Comprobamos si tenemos algún "listener" (Activity o ventana pendiente del resultado del Fragment). Este listener es la Activity que recibimos en onAttach() y de la que hemos comprobado que implementa la interfaz FormularioListener. Si no tenemos ningún Listener, no hay que hacer nada.

Si tenemos Listener, simplemente verificamos si el botón pulsado (el View que recibimos como parámetro) es el de "Ok" o el de "Cancel". Si es "Ok" llamamos al listener (la Activity ventana principal) diciéndole OK y devolviendo el texto de la caja de texto editable. Puesto que esta caja de texto está dentro de la Activity ventana principal, es en ella donde debemos buscar el componente EditText, por ello lo de getActivity().findViewById() siendo getActivity() un método de Fragment que nos devuelve la Activity en la que está incluido nuestro Fragment. También podríamos haber usado el listener con el correspondiente "cast" a Activity


Ocultar el Fragment[editar]

Seguimos el código, vamos ahora a ver cómo el Activity principal implementa el método pulsado() de la interfaz

   @Override
   public void pulsado(int resultado, String texto) {
      TextView tv = (TextView) findViewById(R.id.textView);
      if (resultado == FormularioFragment.OK) {
         tv.setText(texto);
      }
      FragmentManager fm = getFragmentManager();
      Fragment editor = fm.findFragmentByTag("editor");
      FragmentTransaction ft = fm.beginTransaction();
      ft.remove(editor);
      ft.commit();
   }

Si el resultado es OK, entonces hay que poner el texto recibido en nuestro TextView, así que lo obtenemos con findViewById() y le fijamos el texto.

Ahora, independientemente de que sea "Ok" o "Cancel", debemos eliminar el Fragment. Igual que antes

  • FragmentManager fm = getFragmentManager(); --> Se obtiene el FragmentManager
  • Fragment editor = fm.findFragmentByTag("editor"); --> Se obtiene el Fragment utilizando el "texto" que habíamos usado para identificarlo.
  • FragmentTransaction ft = fm.beginTransaction(); --> Se avisa que vamos a empezar a hacer cambios
  • ft.remove(editor); --> Se borra el Fragment
  • ft.commit(); --> Se hacen efectivos los cambios

Y esto es todo de momento. Android nos ofrece varios Fragment útiles ya hechos.