Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

Fragmentos   Part of Android Jetpack.

Un Fragment representa un comportamiento o una parte de la interfaz de usuario en una FragmentActivity. Puedes combinar varios fragmentos en una sola actividad para crear una IU multipanel y volver a usar un fragmento en diferentes actividades. Puedes pensar en un fragmento como una sección modular de una actividad que tiene un ciclo de vida propio, que recibe sus propios eventos de entrada y que puedes agregar o quitar mientras la actividad se esté ejecutando (algo así como una "subactividad" que puedes volver a usar en diferentes actividades).

Un fragmento siempre debe estar alojado en una actividad y el ciclo de vida del fragmento se ve afectado directamente por el ciclo de vida de la actividad anfitriona. Por ejemplo, cuando la actividad está pausada, también lo están todos sus fragmentos, y cuando la actividad se destruye, lo mismo ocurre con todos los fragmentos. Sin embargo, mientras una actividad se está ejecutando (está en el estado del ciclo de vida reanudada), puedes manipular cada fragmento de forma independiente, por ejemplo, para agregarlo o quitarlo. Cuando realizas una transacción de fragmentos como esta, también puedes agregarlos a una pila de actividades administrada por la actividad; cada entrada de la pila de actividades en la actividad es un registro de la transacción de fragmentos realizada. La pila de actividades le permite al usuario invertir una transacción de fragmentos (navegar hacia atrás) al presionar el botón Atrás.

Cuando se agrega un fragmento como parte de la presentación de la actividad, se encuentra en ViewGroup dentro de la jerarquía de vistas de la actividad y el fragmento define su propio diseño de vistas. Puedes insertar un fragmento en el diseño de la actividad declarando el fragmento en el archivo de diseño, como elemento <fragment>, o desde el código de tu aplicación agregándolo a un archivo existente ViewGroup.

Este documento describe cómo crear tu aplicación para usar fragmentos, incluido cómo los fragmentos pueden mantener su estado cuando se agregan a la pila de actividades de la actividad, cómo pueden compartir eventos con la actividad y con otros fragmentos en la actividad, cómo pueden contribuir con la barra de app de la actividad, etc.

Para obtener información sobre el manejo de los ciclos de vida, incluida orientación sobre prácticas recomendadas, consulta los siguientes recursos:

Filosofía de diseño

Android introduce los fragmentos en Android 3.0 (nivel de API 11), principalmente para admitir diseños de IU más dinámicos y flexibles en pantallas grandes, como las de las tablets. Como la pantalla de una tablet es mucho más grande que la de un teléfono, hay más espacio para combinar e intercambiar componentes de la IU. Los fragmentos admiten esos diseños sin la necesidad de que administres cambios complejos en la jerarquía de vistas. Al dividir el diseño de una actividad en fragmentos, puedes modificar el aspecto de la actividad durante el tiempo de ejecución y conservar esos cambios en una pila de actividades administrada por la actividad. Ahora están disponibles mediante la biblioteca de compatibilidad de fragmentos.

Por ejemplo, una aplicación de noticias puede usar un fragmento para mostrar una lista de artículos a la izquierda y otro fragmento para mostrar un artículo a la derecha; ambos fragmentos aparecen en una actividad, uno al lado del otro, y cada fragmento tiene su propio conjunto de métodos de devolución de llamada del ciclo de vida y administra sus propios eventos de entrada del usuario. Entonces, en lugar de usar una actividad para seleccionar un artículo y otra actividad para leer el artículo, el usuario puede seleccionar un artículo y leerlo dentro de la misma actividad, tal como se ilustra en el diseño de tablet en la figura 1.

Debes diseñar cada fragmento como un componente modular y reutilizable de la actividad. Como cada fragmento define su propio diseño y su propio comportamiento con sus propias devoluciones de llamada del ciclo de vida, puedes incluir un fragmento en múltiples actividades; por lo tanto, debes diseñarlo para volver a utilizarlo y evitar la manipulación directa de un fragmento desde otro fragmento. Esto es muy importante porque un fragmento modular te permite cambiar tus combinaciones de fragmentos para diferentes tamaños de pantalla. Cuando diseñas tu aplicación para que admita tablets y teléfonos, puedes reutilizar tus fragmentos en diferentes configuraciones de diseño para optimizar la experiencia del usuario en función del espacio de pantalla disponible. Por ejemplo, en un teléfono, podría ser necesario separar los fragmentos para proporcionar una IU de panel único cuando no quepa más de uno en la misma actividad.

Figura 1: Ejemplo de la manera en que dos módulos de la IU definidos por fragmentos se pueden combinar en una actividad para un diseño de tablet y se presentan por separado para un diseño de teléfono.

Por ejemplo, para continuar con el ejemplo de la aplicación de noticias, la aplicación puede integrar dos fragmentos en la Actividad A cuando se ejecuta en un dispositivo del tamaño de una tablet. Sin embargo, en una pantalla de teléfono, no hay suficiente espacio para ambos fragmentos, por lo que la Actividad A incluye solo el fragmento para la lista de artículos, y cuando el usuario selecciona un artículo, se inicia la Actividad B, que incluye el segundo fragmento para poder leer el artículo. Por lo tanto, la aplicación admite tablets y teléfonos al reutilizar fragmentos en diferentes combinaciones, como se ilustra en la figura 1.

Para obtener más información acerca de cómo diseñar tu aplicación con diferentes combinaciones de fragmentos para distintas configuraciones de pantalla, lee la guía Descripción general de la compatibilidad de pantallas.

Cómo crear un fragmento

Figura 2: Ciclo de vida de un fragmento (mientras su actividad está ejecución).

Para crear un fragmento, debes crear una subclase Fragment (o una subclase existente de ella). La clase Fragment tiene un código que se asemeja bastante a una Activity. Contiene métodos de devolución de llamada similares a los de una actividad, como onCreate(), onStart(), onPause() y onStop(). De hecho, si estás convirtiendo una aplicación de Android existente para utilizar fragmentos, deberías simplemente trasladar código de los métodos de devolución de llamada de tu actividad a los métodos respectivos de tu fragmento.

Generalmente, debes implementar al menos los siguientes métodos del ciclo de vida:

onCreate()
El sistema lo llama cuando crea el fragmento. En tu implementación, debes inicializar componentes esenciales del fragmento que quieras conservar cuando el fragmento se pause o se detenga, y luego se reanude.
onCreateView()
El sistema lo llama cuando el fragmento debe diseñar su interfaz de usuario por primera vez. A fin de diseñar una IU para tu fragmento, debes mostrar un View desde este método, que será la raíz del diseño de tu fragmento. Puedes mostrar un valor nulo si el fragmento no proporciona una IU.
onPause()
El sistema llama a este método como el primer indicador de que el usuario está abandonando el fragmento (aunque no siempre significa que el fragmento se esté destruyendo). Generalmente, este es el momento en el que debes confirmar los cambios que deban conservarse más allá de la sesión de usuario actual (porque es posible que el usuario no vuelva).

La mayoría de las aplicaciones deben implementar al menos estos tres métodos para cada fragmento, pero hay muchos otros métodos de devolución de llamada que también debes usar para manipular varias fases del ciclo de vida del fragmento. Todos los métodos de devolución de llamada del ciclo de vida se discuten más detalladamente en la sección Cómo controlar el ciclo de vida de un fragmento.

Ten en cuenta que el código que implementa las acciones del ciclo de vida de un componente dependiente debe colocarse en el propio componente, y no en las implementaciones de devolución de llamada de los fragmentos. Consulta Cómo controlar ciclos de vida con componentes de ciclos de vida para obtener más información sobre cómo hacer que los componentes de tus dependencias tengan en cuenta los ciclos de vida.

Existen también algunas subclases que quizá desees extender, en lugar de la clase de base Fragment:

DialogFragment
Muestra un diálogo flotante. Usar esta clase para crear un diálogo es una buena alternativa al uso de métodos del asistente de diálogos en la clase Activity, ya que puedes incorporar un diálogo del fragmento en la pila de actividades de fragmentos administrados por la actividad, lo que le permite al usuario volver a un fragmento descartado.
ListFragment
Muestra una lista de elementos administrados por un adaptador (como un SimpleCursorAdapter), al igual que ListActivity. Proporciona varios métodos para administrar una vista de lista, como la devolución de llamada onListItemClick() para manipular eventos de clic. (Ten en cuenta que el método preferido para mostrar una lista es utilizar RecyclerView en lugar de ListView. En este caso, necesitarías crear un fragmento que incluya un RecyclerView en su diseño. Consulta Cómo crear una lista con RecyclerView para ver cómo hacerlo).
PreferenceFragmentCompat
Muestra una jerarquía de objetos Preference en forma de lista. Este objeto se usa a fin de crear una pantalla de configuración para tu app.

Cómo agregar una interfaz de usuario

Un fragmento generalmente se usa como parte de la interfaz de usuario de una actividad y le aporta su propio diseño.

Si quieres proporcionar un diseño para un fragmento, debes implementar el método de devolución de llamada onCreateView(), al cual el sistema Android llama cuando llega el momento de que el fragmento defina su diseño. Tu implementación de este método debe mostrar un View, que es la raíz del diseño de tu fragmento.

Nota: Si tu fragmento es una subclase de ListFragment, la implementación predeterminada muestra un ListView desde onCreateView(), de modo que no necesitas implementarla.

Para mostrar un diseño desde onCreateView(), puedes agrandarlo desde un recurso de diseño definido en XML. Para ayudarte a hacerlo, onCreateView() proporciona un objeto LayoutInflater.

Por ejemplo, a continuación, se muestra una subclase de Fragment, que carga un diseño desde el archivo example_fragment.xml:

Kotlin

class ExampleFragment : Fragment() {

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.example_fragment, container, false)
    }
}

Java

public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}

Nota: En el ejemplo anterior, R.layout.example_fragment es una referencia al recurso de diseño llamado example_fragment.xml y guardado en los recursos de la aplicación. Para obtener información acerca de cómo crear un diseño en XML, lee la documentación Interfaz de usuario.

El parámetro container que se pasa a onCreateView() es el ViewGroup principal (del diseño de la actividad) en el cual se insertará el diseño de tu fragmento. El parámetro savedInstanceState es un Bundle que proporciona datos acerca de la instancia previa del fragmento si el fragmento se está reanudando (la restauración del estado se discute más detalladamente en la sección Cómo controlar el ciclo de vida de un fragmento).

El método inflate() adopta tres argumentos:

  • El ID del recurso de diseño que quieres agrandar
  • El ViewGroup que será el elemento principal del diseño agrandado (es importante pasar container para que el sistema aplique parámetros de diseño a la vista de raíz del diseño agrandado, especificada por la vista principal a la que se integra)
  • Un valor booleano que indica si se debe anexar el diseño aumentado al ViewGroup (el segundo parámetro) durante el agrandamiento (en este caso, es falso porque el sistema ya está insertando el diseño aumentado al container; al pasar "true", se crearía un grupo de vistas redundante en el diseño final)

Ya viste cómo crear un fragmento que proporcione un diseño. Ahora deberás agregar el fragmento a tu actividad.

Cómo agregar un fragmento a una actividad

Generalmente, un fragmento aporta una parte de la IU a la actividad anfitriona, que se integra como parte de la jerarquía de vistas general de la actividad. Existen dos maneras de agregar un fragmento al diseño de la actividad:

  • Declarar el fragmento en el archivo de diseño de la actividad.

    En este caso, puedes especificar propiedades de diseño para el fragmento como si fueran una vista. Por ejemplo, aquí te mostramos un archivo de diseño para una actividad con dos fragmentos:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <fragment android:name="com.example.news.ArticleListFragment"
                android:id="@+id/list"
                android:layout_weight="1"
                android:layout_width="0dp"
                android:layout_height="match_parent" />
        <fragment android:name="com.example.news.ArticleReaderFragment"
                android:id="@+id/viewer"
                android:layout_weight="2"
                android:layout_width="0dp"
                android:layout_height="match_parent" />
    </LinearLayout>
    

    El atributo android:name de <fragment> especifica la clase Fragment para crear una instancia en el diseño.

    Cuando el sistema crea el diseño de esta actividad, crea una instancia para cada fragmento especificado en el diseño, además de llamar al método onCreateView(), con el objetivo de recuperar el diseño de cada fragmento. El sistema inserta el objeto View que muestra el fragmento directamente en lugar del elemento <fragment>.

    Nota: Cada fragmento requiere un identificador único que el sistema puede usar para restaurar el fragmento si se reinicia la actividad (y que tú puedes usar para que el fragmento realice transacciones, como quitarlo). Hay tres formas de proporcionarle un ID a un fragmento:

    • Proporciona el atributo android:id con un ID único.
    • Proporciona el atributo android:tag con una string única.
  • O bien, guarda el fragmento de forma programática en un ViewGroup existente.

    Mientras tu actividad se está ejecutando, puedes agregar fragmentos al diseño. Solo necesitas especificar un ViewGroup en el que se colocará el fragmento.

    Para realizar transacciones de fragmentos en tu actividad (como agregar, quitar o reemplazar un fragmento), debes usar las API de FragmentTransaction. Puedes obtener una instancia de FragmentTransaction de tu FragmentActivity como esta:

    Kotlin

    val fragmentManager = supportFragmentManager
    val fragmentTransaction = fragmentManager.beginTransaction()
    

    Java

    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    

    Luego, puedes agregar un fragmento usando el método add() y especificando el fragmento que se agregará, así como la vista en la que se insertará. Por ejemplo:

    Kotlin

    val fragment = ExampleFragment()
    fragmentTransaction.add(R.id.fragment_container, fragment)
    fragmentTransaction.commit()
    

    Java

    ExampleFragment fragment = new ExampleFragment();
    fragmentTransaction.add(R.id.fragment_container, fragment);
    fragmentTransaction.commit();
    

    El primer argumento que se pasa a add() es ViewGroup, en el que se debe colocar el fragmento especificado por el ID de recurso, y el segundo parámetro es el fragmento que quieres agregar.

    Una vez que realices los cambios con FragmentTransaction, deberás llamar a commit() para que se apliquen esos cambios.

Cómo administrar fragmentos

Para administrar los fragmentos de tu actividad, debes usar FragmentManager. Para obtenerlo, llama a getSupportFragmentManager() desde tu actividad.

Entre las cosas que puedes hacer con FragmentManager, se incluyen las siguientes:

  • Obtener fragmentos que ya existen en la actividad con findFragmentById() (para fragmentos que proporcionan una IU en el diseño de la actividad) o findFragmentByTag() (para fragmentos con o sin IU)
  • Activar fragmentos de la pila de retroceso con popBackStack() (simulando el uso del comando Atrás por parte del usuario)
  • Registrar un receptor para cambios realizados en la pila de retroceso con addOnBackStackChangedListener()

Para obtener más información acerca de estos y otros métodos, consulta la documentación sobre la clase FragmentManager.

Como se mostró en la sección anterior, también puedes usar FragmentManager para abrir un FragmentTransaction, que te permite realizar transacciones como agregar y quitar fragmentos.

Cómo realizar transacciones de fragmentos

Un excelente aspecto acerca del uso de fragmentos en tu actividad es la capacidad de agregar, quitar, reemplazar y realizar otras acciones con ellos en respuesta a la interacción del usuario. Cada conjunto de cambios que confirmas en la actividad recibe la denominación de transacción, y puedes realizar una usando las API de FragmentTransaction. También puedes guardar cada transacción en una pila de actividades administrada por la actividad, lo que le permitirá al usuario navegar hacia atrás por los cambios realizados en el fragmento (como navegar hacia atrás en las actividades).

Puedes adquirir una instancia de FragmentTransaction de FragmentManager como esta:

Kotlin

val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()

Java

FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

Cada transacción es un conjunto de cambios que quieres realizar al mismo tiempo. Puedes configurar todos los cambios que desees realizar en una transacción determinada con métodos como add(), remove() y replace(). Luego, para aplicar la transacción a la actividad, debes llamar a commit().

Sin embargo, antes de llamar a commit() probablemente te convenga llamar a addToBackStack() para agregar la transacción a una pila de retroceso de transacciones de fragmentos. Esta pila de actividades está administrada por la actividad y le permite al usuario volver a un estado anterior del fragmento presionando el botón Atrás.

Por ejemplo, aquí te mostramos cómo puedes reemplazar un fragmento por otro y conservar el estado anterior en la pila de actividades:

Kotlin

val newFragment = ExampleFragment()
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, newFragment)
transaction.addToBackStack(null)
transaction.commit()

Java

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

En este ejemplo, newFragment reemplaza al fragmento (si lo hay) que se encuentra actualmente en el contenedor de diseño identificado con el ID R.id.fragment_container. Al llamar a addToBackStack(), la transacción de reemplazo se guarda en la pila de retroceso para que el usuario pueda revertir la transacción y recuperar el fragmento previo presionando el botón Atrás.

FragmentActivity luego obtiene automáticamente fragmentos de la pila de retroceso mediante onBackPressed().

Si agregas varios cambios a la transacción (como otro add() o remove()) y llamas a addToBackStack(), todos los cambios aplicados antes de llamar a commit() se agregarán a la pila de retroceso como una transacción única, y el botón Atrás los revertirá juntos.

No importa el orden en que agregues cambios a un FragmentTransaction, excepto por lo siguiente:

  • Debes llamar en último lugar a commit().
  • Si estás agregando múltiples fragmentos al mismo contenedor, el orden en el que los agregas determina el orden en el que aparecen en la jerarquía de vistas.

Si no llamas a addToBackStack(), cuando realices una transacción que quite un fragmento, ese fragmento se destruirá cuando se confirme la transacción y el usuario no podrá regresar a él. Sin embargo, si llamas a addToBackStack(), cuando se elimine un fragmento, el fragmento se detendrá y se reanudará si el usuario retrocede en la navegación.

Sugerencia: Para cada transacción de fragmentos, puedes aplicar una animación de transición llamando a setTransition() antes de confirmar.

Llamar a commit() no realiza la transacción inmediatamente. Se programa para ejecutarse en el subproceso de la IU de la actividad (el subproceso "principal") tan pronto el subproceso pueda hacerlo. Sin embargo, si es necesario, puedes llamar a executePendingTransactions() desde el subproceso de tu IU para ejecutar de inmediato transacciones enviadas por commit(). Generalmente, no es necesario hacer esto, a menos que la transacción sea una dependencia para tareas de otros subprocesos.

Advertencia: Puedes confirmar una transacción con commit() solo antes de que la actividad guarde su estado (cuando el usuario la abandone). Si intentas confirmar después de ese momento, se producirá una excepción. Esto ocurre porque se podría perder el estado después de la confirmación si fuera necesario restaurar la actividad. En situaciones en las cuales no importe perder la confirmación, usa commitAllowingStateLoss().

Cómo comunicarse con la actividad

Si bien un Fragment se implementa como un objeto dependiente de un FragmentActivity y puede usarse dentro de múltiples actividades, una instancia determinada de un fragmento está directamente vinculada a la actividad que la contiene.

Específicamente, el fragmento puede acceder a la instancia FragmentActivity con getActivity() y realizar tareas de manera sencilla, como buscar una vista en el diseño de la actividad:

Kotlin

val listView: View? = activity?.findViewById(R.id.list)

Java

View listView = getActivity().findViewById(R.id.list);

De la misma manera, tu actividad puede llamar a métodos del fragmento mediante la adquisición de una referencia a Fragment desde FragmentManager usando findFragmentById() o findFragmentByTag(). Por ejemplo:

Kotlin

val fragment = supportFragmentManager.findFragmentById(R.id.example_fragment) as ExampleFragment

Java

ExampleFragment fragment = (ExampleFragment) getSupportFragmentManager().findFragmentById(R.id.example_fragment);

Cómo crear devoluciones de llamadas de eventos a la actividad

En algunos casos, es posible que necesites un fragmento para compartir eventos o datos con la actividad o los otros fragmentos alojados en ella. Para compartir datos, crea un ViewModel compartido, como se describe en la sección "Cómo compartir datos entre fragmentos" en la guía de ViewModel. Si necesitas propagar eventos que no pueden controlarse con un ViewModel, define una interfaz de devolución de llamada dentro del fragmento y fuerza a la actividad anfitriona para que la implemente. Cuando la actividad reciba una devolución de llamada a través de la interfaz, podrá compartir la información con otros fragmentos del diseño según sea necesario.

Por ejemplo, si una aplicación de noticias tiene dos fragmentos en una actividad, uno para mostrar una lista de artículos (fragmento A) y otro para exhibir un artículo (fragmento B), el fragmento A debe indicarle a la actividad en qué momento se selecciona un elemento de la lista para que pueda informarle al fragmento B que debe exhibir el artículo. En este caso, la interfaz OnArticleSelectedListener se declara dentro del fragmento A:

Kotlin

public class FragmentA : ListFragment() {
    ...
    // Container Activity must implement this interface
    interface OnArticleSelectedListener {
        fun onArticleSelected(articleUri: Uri)
    }
    ...
}

Java

public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}

Luego, la actividad que aloja al fragmento implementa la interfaz OnArticleSelectedListener y anula onArticleSelected() para notificar al fragmento B sobre el evento del fragmento A. Para garantizar que la actividad anfitriona implemente esta interfaz, el método de devolución de llamada onAttach() del fragmento A (al cual el sistema llama cuando agrega el fragmento a la actividad) crea una instancia de OnArticleSelectedListener ordenando la Activity que se pasa a onAttach():

Kotlin

public class FragmentA : ListFragment() {

    var listener: OnArticleSelectedListener? = null
    ...
    override fun onAttach(context: Context) {
        super.onAttach(context)
        listener = context as? OnArticleSelectedListener
        if (listener == null) {
            throw ClassCastException("$context must implement OnArticleSelectedListener")
        }

    }
    ...
}

Java

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener listener;
    ...
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            listener = (OnArticleSelectedListener) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString() + " must implement OnArticleSelectedListener");
        }
    }
    ...
}

Si la actividad no implementó la interfaz, el fragmento arrojará ClassCastException. Si se realiza con éxito, el miembro mListener conserva una referencia a la implementación de OnArticleSelectedListener por parte de la actividad para que el fragmento A pueda compartir eventos con esta llamando a los métodos definidos por la interfaz OnArticleSelectedListener. Por ejemplo, si el fragmento A es una extensión de ListFragment, cada vez que el usuario haga clic en un elemento de la lista, el sistema llamará a onListItemClick() en el fragmento, que luego llamará a onArticleSelected() para compartir el evento con la actividad.

Kotlin

public class FragmentA : ListFragment() {

    var listener: OnArticleSelectedListener? = null
    ...
    override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) {
        // Append the clicked item's row ID with the content provider Uri
        val noteUri: Uri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id)
        // Send the event and Uri to the host activity
        listener?.onArticleSelected(noteUri)
    }
    ...
}

Java

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener listener;
    ...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Append the clicked item's row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
        listener.onArticleSelected(noteUri);
    }
    ...
}

El parámetro id que se pasa a onListItemClick() es el ID de la fila del elemento en el cual se hizo clic, que la actividad (u otro fragmento) usa para capturar el artículo del ContentProvider de la aplicación.

Puedes encontrar más información acerca de cómo usar un proveedor de contenido en el documento Proveedores de contenido.

Cómo agregar artículos a la barra de app

Tus fragmentos pueden aportar elementos de menú al menú de opciones de la actividad (y, en consecuencia, a la barra de app) implementando onCreateOptionsMenu(). Sin embargo, para que este método pueda recibir llamadas, debes llamar a setHasOptionsMenu() durante onCreate() para indicar que el fragmento intenta agregar elementos al menú de opciones. De lo contrario, el fragmento no recibirá una llamada a onCreateOptionsMenu().

Los elementos que agregues al menú de opciones desde el fragmento se anexarán a los elementos de menú existentes. El fragmento también recibe devoluciones de llamadas a onOptionsItemSelected() cuando se selecciona un elemento de menú.

También puedes registrar una vista en el diseño de tu fragmento para proporcionar un menú contextual llamando a registerForContextMenu(). Cuando el usuario abre el menú contextual, el fragmento recibe una llamada a onCreateContextMenu(). Cuando el usuario selecciona un elemento, el fragmento recibe una llamada a onContextItemSelected().

Nota: Si bien tu fragmento recibe una devolución de llamada en relación con el elemento seleccionado para cada elemento de menú que agrega, la actividad será la que primero reciba la devolución de llamada correspondiente cuando el usuario seleccione un elemento de menú. Si la implementación que realiza la actividad de la devolución de llamada en relación con el elemento seleccionado no aborda el elemento seleccionado, el evento se pasa a la devolución de llamada del fragmento. Esto ocurre con el menú de opciones y los menús contextuales.

Para obtener más información acerca de los menús, consulta la guía para desarrolladores Menús y la clase de capacitación Barra de app.

Cómo controlar el ciclo de vida de un fragmento

Figura 3: Efecto del ciclo de vida de la actividad en el ciclo de vida del fragmento.

La administración del ciclo de vida de un fragmento se parece mucho a la del ciclo de vida de una actividad. Al igual que una actividad, un fragmento puede tener tres estados:

Reanudado
El fragmento está visible en la actividad que se está ejecutando.
Pausado
Otra actividad se encuentra en primer plano y tiene el foco, pero la actividad en la que reside este fragmento aún está visible (la actividad en segundo plano es parcialmente transparente o no cubre toda la pantalla).
Detenido
El fragmento no es visible. O bien se detuvo la actividad anfitriona, o bien se quitó el fragmento de la actividad, pero se agregó a la pila de actividades. Un fragmento detenido aún está activo (el sistema conserva el estado y la información de miembro). No obstante, ya no está visible para el usuario y se cerrará si finaliza la actividad.

Al igual que como una actividad, puedes preservar el estado de la interfaz de usuario de un fragmento a través de los cambios de configuración y la finalización del proceso utilizando una combinación de onSaveInstanceState(Bundle), ViewModel y almacenamiento local persistente. Para obtener más información sobre cómo preservar el estado de la IU, consulta Cómo guardar los estados de IU.

La diferencia más importante en el ciclo de vida entre una actividad y un fragmento es cómo cada uno se almacena en su pila de actividades respectiva. Cuando se detiene una actividad, de forma predeterminada, se dispone en una pila de actividades administrada por el sistema (de modo que el usuario pueda navegar hasta ella con el botón Atrás, como se observa en Tareas y pila de retroceso). Sin embargo, un fragmento se dispone en una pila de retroceso administrada por la actividad anfitriona solo cuando solicitas explícitamente que se guarde la instancia mediante la llamada a addToBackStack() durante una transacción que elimina el fragmento.

De lo contrario, la administración del ciclo de vida del fragmento es muy similar a la del ciclo de vida de la actividad. Para obtener más información sobre el ciclo de vida de las actividades y las prácticas para administrarlas, consulta la guía Ciclo de vida de una actividad y Cómo administrar ciclos de vida y sus componentes.

Advertencia: Si necesitas un objeto Context en Fragment, puedes llamar a getContext(). Sin embargo, solo debes llamar a getContext() cuando el fragmento está adjunto a una actividad. Cuando el fragmento aún no está adjunto, o se desprendió al final de su ciclo de vida, getContext() muestra un valor nulo.

Cómo coordinar con el ciclo de vida de la actividad

El ciclo de vida de la actividad en la que reside el fragmento afecta directamente al ciclo de vida del fragmento, de modo que cada devolución de llamada del ciclo de vida de la actividad genera una devolución de llamada similar para cada fragmento. Por ejemplo, cuando la actividad recibe onPause(), cada fragmento en la actividad recibe onPause().

No obstante, los fragmentos tienen algunas devoluciones de llamada del ciclo de vida adicionales que abordan la interacción única con la actividad para poder realizar acciones como crear y destruir la IU del fragmento. Estos métodos de devolución de llamada adicionales son los siguientes:

onAttach()
Reciba una llamada cuando se asocia el fragmento con la actividad (aquí se pasa Activity).
onCreateView()
Se lo llama para crear la jerarquía de vistas asociada con el fragmento.
onActivityCreated()
Se lo llama cuando se muestra el método onCreate() de la actividad.
onDestroyView()
Se lo llama cuando se quita la jerarquía de vistas asociada con el fragmento.
onDetach()
Se lo llama cuando se desasocia el fragmento de la actividad.

En la figura 3, se ilustra el flujo del ciclo de vida de un fragmento, afectado por la actividad que lo aloja. En esta figura, puedes ver la manera en que cada estado sucesivo de la actividad determina los métodos de devolución de llamada que un fragmento puede recibir. Por ejemplo, cuando la actividad recibe su devolución de llamada de onCreate(), un fragmento en la actividad recibe la devolución de llamada de onActivityCreated().

Una vez que la actividad alcanza el estado de "reanudada", puedes agregar y quitar libremente fragmentos en esa actividad. De este modo, solo cuando la actividad se encuentra en estado de "reanudada" el ciclo de vida de un fragmento puede cambiar de forma independiente.

No obstante, cuando la actividad sale del estado de "reanudada" hace que el fragmento vuelva a atravesar su ciclo de vida.

Ejemplo

Para agrupar todo lo que se discutió en este documento, aquí te mostramos un ejemplo de una actividad que usa dos fragmentos para crear un diseño con dos paneles. La actividad que se muestra a continuación incluye un fragmento para mostrar una lista de títulos de obras de Shakespeare y otro para mostrar un resumen de la obra que se seleccione de la lista. También demuestra cómo proporcionar diferentes configuraciones de fragmentos en función de la configuración de pantalla.

Nota: El código fuente completo de esta actividad está disponible en la app de ejemplo que muestra el uso de una clase FragmentLayout de ejemplo.

La actividad principal aplica un diseño de la forma habitual, durante onCreate():

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.fragment_layout)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.fragment_layout);
}

El diseño aplicado es fragment_layout.xml:

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

    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent" />

    <FrameLayout android:id="@+id/details" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent"
            android:background="?android:attr/detailsElementBackground" />

</LinearLayout>

Con este diseño, el sistema crea una instancia de TitlesFragment (que incluye los títulos de las obras) no bien la actividad carga el diseño, mientras FrameLayout (donde irá el fragmento para mostrar el resumen de la obra) ocupa espacio en el lado derecho de la pantalla, pero primero permanece vacío. Como verás a continuación, el fragmento se coloca en el FrameLayout solo cuando el usuario selecciona un elemento de la lista.

Sin embargo, no todas las configuraciones de pantalla son lo suficientemente anchas para mostrar la lista de obras y, al lado, el resumen. Por lo tanto, el diseño anterior se usa únicamente para la configuración de pantalla horizontal al guardarse en res/layout-land/fragment_layout.xml.

De este modo, cuando la pantalla se encuentra en posición vertical, el sistema aplica el siguiente diseño, que se guarda en res/layout/fragment_layout.xml:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles"
            android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>

Este diseño incluye únicamente TitlesFragment. Esto significa que, cuando el dispositivo esté en orientación vertical, solo estará visible la lista de títulos de obras. Entonces, cuando el usuario haga clic en un elemento de la lista en esta configuración, la aplicación iniciará una nueva actividad para mostrar el resumen, en lugar de cargar un segundo fragmento.

A continuación, puedes ver cómo se logra esto en las clases de fragmentos. El primer fragmento es TitlesFragment, que muestra la lista de títulos de obras de Shakespeare. Este fragmento extiende ListFragment y depende de él para administrar gran parte del trabajo de la vista de lista.

Mientras analizas este código, ten en cuenta que, cuando el usuario hace clic en un elemento de la lista, pueden tener lugar dos comportamientos: según cuál de los dos diseños esté activo, se puede crear y exhibir un nuevo fragmento para mostrar los detalles en la misma actividad (agregar el fragmento a FrameLayout) o se puede iniciar una nueva (en la que se puede mostrar el fragmento).

Kotlin

class TitlesFragment : ListFragment() {

    private var dualPane: Boolean = false
    private var curCheckPosition = 0

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        // Populate list with our static array of titles.
        listAdapter = ArrayAdapter<String>(
                activity,
                android.R.layout.simple_list_item_activated_1,
                Shakespeare.TITLES
        )

        // Check to see if we have a frame in which to embed the details
        // fragment directly in the containing UI.
        val detailsFrame: View? = activity?.findViewById(R.id.details)
        dualPane = detailsFrame?.visibility == View.VISIBLE

        curCheckPosition = savedInstanceState?.getInt("curChoice", 0) ?: 0

        if (dualPane) {
            // In dual-pane mode, the list view highlights the selected item.
            listView.choiceMode = ListView.CHOICE_MODE_SINGLE
            // Make sure our UI is in the correct state.
            showDetails(curCheckPosition)
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putInt("curChoice", curCheckPosition)
    }

    override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) {
        showDetails(position)
    }

    /**
     * Helper function to show the details of a selected item, either by
     * displaying a fragment in-place in the current UI, or starting a
     * whole new activity in which it is displayed.
     */
    private fun showDetails(index: Int) {
        curCheckPosition = index

        if (dualPane) {
            // We can display everything in-place with fragments, so update
            // the list to highlight the selected item and show the data.
            listView.setItemChecked(index, true)

            // Check what fragment is currently shown, replace if needed.
            var details = fragmentManager?.findFragmentById(R.id.details) as? DetailsFragment
            if (details?.shownIndex != index) {
                // Make new fragment to show this selection.
                details = DetailsFragment.newInstance(index)

                // Execute a transaction, replacing any existing fragment
                // with this one inside the frame.
                fragmentManager?.beginTransaction()?.apply {
                    if (index == 0) {
                        replace(R.id.details, details)
                    } else {
                        replace(R.id.a_item, details)
                    }
                    setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                    commit()
                }
            }

        } else {
            // Otherwise we need to launch a new activity to display
            // the dialog fragment with selected text.
            val intent = Intent().apply {
                setClass(activity, DetailsActivity::class.java)
                putExtra("index", index)
            }
            startActivity(intent)
        }
    }
}

Java

public static class TitlesFragment extends ListFragment {
    boolean dualPane;
    int curCheckPosition = 0;

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

        // Populate list with our static array of titles.
        setListAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));

        // Check to see if we have a frame in which to embed the details
        // fragment directly in the containing UI.
        View detailsFrame = getActivity().findViewById(R.id.details);
        dualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

        if (savedInstanceState != null) {
            // Restore last state for checked position.
            curCheckPosition = savedInstanceState.getInt("curChoice", 0);
        }

        if (dualPane) {
            // In dual-pane mode, the list view highlights the selected item.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            // Make sure our UI is in the correct state.
            showDetails(curCheckPosition);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", curCheckPosition);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        showDetails(position);
    }

    /**
     * Helper function to show the details of a selected item, either by
     * displaying a fragment in-place in the current UI, or starting a
     * whole new activity in which it is displayed.
     */
    void showDetails(int index) {
        curCheckPosition = index;

        if (dualPane) {
            // We can display everything in-place with fragments, so update
            // the list to highlight the selected item and show the data.
            getListView().setItemChecked(index, true);

            // Check what fragment is currently shown, replace if needed.
            DetailsFragment details = (DetailsFragment)
                    getSupportFragmentManager().findFragmentById(R.id.details);
            if (details == null || details.getShownIndex() != index) {
                // Make new fragment to show this selection.
                details = DetailsFragment.newInstance(index);

                // Execute a transaction, replacing any existing fragment
                // with this one inside the frame.
                FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
                if (index == 0) {
                    ft.replace(R.id.details, details);
                } else {
                    ft.replace(R.id.a_item, details);
                }
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }

        } else {
            // Otherwise we need to launch a new activity to display
            // the dialog fragment with selected text.
            Intent intent = new Intent();
            intent.setClass(getActivity(), DetailsActivity.class);
            intent.putExtra("index", index);
            startActivity(intent);
        }
    }
}

El segundo fragmento, DetailsFragment, muestra el resumen de la obra para el elemento seleccionado de la lista de TitlesFragment:

Kotlin

    class DetailsFragment : Fragment() {

        val shownIndex: Int by lazy {
            arguments?.getInt("index", 0) ?: 0
        }

        override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
        ): View? {
            if (container == null) {
                // We have different layouts, and in one of them this
                // fragment's containing frame doesn't exist. The fragment
                // may still be created from its saved state, but there is
                // no reason to try to create its view hierarchy because it
                // isn't displayed. Note this isn't needed -- we could just
                // run the code below, where we would create and return the
                // view hierarchy; it would just never be used.
                return null
            }

            val text = TextView(activity).apply {
                val padding: Int = TypedValue.applyDimension(
                        TypedValue.COMPLEX_UNIT_DIP,
                        4f,
                        activity?.resources?.displayMetrics
                ).toInt()
                setPadding(padding, padding, padding, padding)
                text = Shakespeare.DIALOGUE[shownIndex]
            }
            return ScrollView(activity).apply {
                addView(text)
            }
        }

        companion object {
            /**
             * Create a new instance of DetailsFragment, initialized to
             * show the text at 'index'.
             */
            fun newInstance(index: Int): DetailsFragment {
                val f = DetailsFragment()

                // Supply index input as an argument.
                val args = Bundle()
                args.putInt("index", index)
                f.arguments = args

                return f
            }
        }
    }
}

Java

public static class DetailsFragment extends Fragment {
    /**
     * Create a new instance of DetailsFragment, initialized to
     * show the text at 'index'.
     */
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();

        // Supply index input as an argument.
        Bundle args = new Bundle();
        args.putInt("index", index);
        f.setArguments(args);

        return f;
    }

    public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist. The fragment
            // may still be created from its saved state, but there is
            // no reason to try to create its view hierarchy because it
            // isn't displayed. Note this isn't needed -- we could just
            // run the code below, where we would create and return the
            // view hierarchy; it would just never be used.
            return null;
        }

        ScrollView scroller = new ScrollView(getActivity());
        TextView text = new TextView(getActivity());
        int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                4, getActivity().getResources().getDisplayMetrics());
        text.setPadding(padding, padding, padding, padding);
        scroller.addView(text);
        text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
        return scroller;
    }
}

Recuerda, de la clase TitlesFragment, que, si el usuario hace clic en un elemento de la lista y el diseño actual no incluye la vista R.id.details (a la que DetailsFragment pertenece), la aplicación inicia la actividad DetailsActivity para mostrar el contenido del elemento.

Aquí está DetailsActivity, que simplemente integra el DetailsFragment para mostrar el resumen de la obra seleccionada cuando la orientación de la pantalla es vertical:

Kotlin

class DetailsActivity : FragmentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish()
            return
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            val details = DetailsFragment().apply {
                arguments = intent.extras
            }
            supportFragmentManager.beginTransaction()
                    .add(android.R.id.content, details)
                    .commit()
        }
    }
}

Java

public static class DetailsActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getSupportFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    }
}

Observa que esta actividad termina por sí misma si la configuración es horizontal, de modo que la actividad principal pueda mostrar DetailsFragment junto con TitlesFragment. Esto puede suceder si el usuario inicia el DetailsActivity mientras está en orientación vertical, pero luego rota a horizontal (lo que reinicia la actividad actual).

Recursos adicionales

Se usa Fragment en la app de demostración de Sunflower.