Construyendo una interfaz de usuario flexible

Al diseñar tu aplicación para que soporte un amplio rango de tamaños de pantalla, puedes reutilizar tus fragmentos en distintas configuraciones para optimizar la experiencia de usuario basándote en el espacio disponible.

Por ejemplo, en un móvil es preferible mostrar un único fragmento en una interfaz de usuario de un solo panel. De igual forma, en una tableta, que tiene una pantalla mucho más ancha y puede mostrar más información al usuario, puede que sea mejor mostrar varios fragmentos a la vez, uno al lado del otro.

Figura 1. Dos fragmentos con distintas configuraciones para la misma actividad en distintos tamaños de pantalla. En una pantalla grande, ambos fragmentos caben uno al lado del otro, pero en un móvil, sólo cabe un fragmento y se tienen que reemplazar cuando el usuario navega entre ellos.

La clase FragmentManager proporciona métodos que permiten añadir, quitar y reemplazar fragmentos en una actividad en tiempo de ejecución para crear una experiencia dinámica.

Añadir un fragmento a una actividad en tiempo de ejecución


En lugar de definir los fragmentos de una actividad en un archivo de interfaz de usuario con el elemento <fragment>—como vimos en la lección anterior—puedes añadir un fragmento a la actividad en tiempo de ejecución. Esto es un requisito necesario si tienes pensado sustituir fragmentos en algún momento del ciclo de vida de la actividad.

Para llevar a cabo una transacción como la de añadir o quitar un fragmento, tienes que usar el FragmentManager para crear una FragmentTransaction, que proporciona APIs para añadir, quitar, reemplazar, y llevar a cabo otras transacciones con fragmentos.

Si tu actividad permite quitar y reemplazar fragmentos, deberías añadir el fragmento o los fragmentos iniciales de la actividad en su método onCreate().

Una regla importante a la hora de trabajar con fragmentos—especialmente aquellos que vas a añadir en tiempo de ejecución—es que el fragmento debe tener un elemento View contenedor en la interfaz de la actividad en que va a residir la interfaz del fragmento.

La siguiente interfaz de usuario es una alternativa a la que vimos en la lección anterior en la que se mostraba un único fragmento cada vez. Para reemplazar un fragmento por otro, la interfaz de usuario de la actividad tiene un FrameLayout vacío que actúa como contenedor del fragmento.

Observa que el nombre de archivo es el mismo que el del archivo de interfaz de usuario de la lección anterior, pero el directorio no tiene el calificador large, por lo que esta interfaz de usuario sólo se utilizará cuando la pantalla del dispositivo sea más pequeña que large, ya que en este caso no cabrían ambos fragmentos en pantalla.

res/layout/news_articles.xml:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

En tu actividad, llama a getSupportFragmentManager() para obtener un FragmentManager usando las APIs de la librería de soporte. Una vez hecho esto llama a beginTransaction() para crear una FragmentTransaction y después llama a add() para añadir un fragmento.

Puedes llevar a cabo múltiples transacciones usando la misma FragmentTransaction. Cuando estés listo para aplicar los cambios, debes llamar a commit().

Por ejemplo, así es como añadirías un fragmento a la interfaz de usuario anterior:

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_articles);

        // Comprueba que la actividad está usando la versión de la interfaz
        // que tiene el contendor FrameLayout
        if (findViewById(R.id.fragment_container) != null) {

            // Si se está restaurando desde un estado previo,
            // no necesitamos hacer nada y deberíamos salir del método o
            // podríamos terminar con fragmentos solapados.
            if (savedInstanceState != null) {
                return;
            }

            // Crea una instancia de HeadlinesFragment
            HeadlinesFragment firstFragment = new HeadlinesFragment();
            
            // En caso de que la actividad se iniciara con instrucciones especiales
            // desde una intención le pasamos los extras del Intent al fragmento
            firstFragment.setArguments(getIntent().getExtras());
            
            // Añadimos el fragmento al FrameLayout contenedor
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.fragment_container, firstFragment).commit();
        }
    }
}

Dado que el fragmento se ha añadido al contenedor FrameLayout en tiempo de ejecución—en lugar de definirlo en la interfaz de usuario de la actividad con un elemento <fragment>—la actividad puede quitar el fragmento en cualquier momento y reemplazarlo con otro distinto.

Reemplazar un fragmento por otro


El procedimiento para reemplazar un fragmento es similar a añadirlo, pero requiere utilizar el método replace() en lugar de add().

Ten en cuenta que cuando llevas a cabo transacciones de fragmentos, como reemplazar o quitar un fragmento, a menudo es apropiado permitir al usuario navegar hacia atrás y "deshacer" el cambio. Para permitir al usuario volver atrás en las transacciones de fragmentos, debes llamar a addToBackStack() antes de aplicar la FragmentTransaction.

Nota: Cuando quitas o reemplazas un fragmento y añades la transacción a la pila de vuelta, el fragmento que se está quitando se para (no se destruye). Si el usuario vuelve al paso anterior para restaurar el fragmento, este se reinicia. Si no añades la transacción a la pila de vuelta, entonces se destruye el fragmento cuando este se elimina o se reemplaza.

Ejemplo en el que reemplazamos un fragmento con otro:

// Creamos el fragmento y le pasamos como argumento el artículo a mostrar
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

// Reemplazamos lo que haya en la vista fragment_container con este fragmento,
// y añadimos la transacción a la pila de vuelta para que el usuario pueda volver
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Aplicamos la transacción
transaction.commit();

El método addToBackStack() acepta un parámetro de tipo cadena opcional con un nombre único para la transacción. Este nombre no es necesario a menos que tengas pensado llevar a cabo operaciones avanzadas con fragmentos utilizando las APIs FragmentManager.BackStackEntry.