Cómo trabajar con la barra de la app

La barra de la app superior proporciona un lugar coherente en la parte superior de la ventana de la app para mostrar información y acciones desde la pantalla actual.

Ejemplo de una barra de la app superior
Figura 1: Ejemplo de una barra de la app superior

Cuando usas fragmentos, la barra de la app se puede implementar como una ActionBar que pertenece a la actividad del host o a una barra de herramientas dentro del diseño de tu fragmento. La propiedad de la barra de la app varía según las necesidades de tu app.

Si todas tus pantallas usan la misma barra de la app que siempre está en la parte superior y abarca el ancho de la pantalla, debes usar una barra de acciones proporcionada por el tema y alojada por la actividad. El uso de barras de la app temáticas ayuda a mantener una apariencia coherente y proporciona un lugar para alojar menús de opciones y un botón hacia arriba.

Usa una barra de herramientas alojada por el fragmento si deseas tener más control sobre el tamaño, la ubicación y la animación de la barra de la app en varias pantallas. Por ejemplo, quizás necesites una barra de la app que se contraiga o que solo use la mitad del ancho de la pantalla y que esté centrada verticalmente.

Comprender los diferentes enfoques y usar uno correcto te permite ahorrar tiempo y garantizar que tu app funcione de forma correcta. En diferentes situaciones, se requieren enfoques distintos para cuestiones como aumentar menús y responder a la interacción del usuario.

Los ejemplos de este tema hacen referencia a un ExampleFragment que contiene un perfil editable. El fragmento aumenta el siguiente menú definido por XML en la barra de la app:

<!-- sample_menu.xml -->
<menu
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_settings"
        android:icon="@drawable/ic_settings"
        android:title="@string/settings"
        app:showAsAction="ifRoom"/>
    <item
        android:id="@+id/action_done"
        android:icon="@drawable/ic_done"
        android:title="@string/done"
        app:showAsAction="ifRoom|withText"/>

</menu>

El menú contiene dos opciones: una para navegar a una pantalla del perfil y otra para guardar los cambios de perfil que se realicen.

Barra de la app perteneciente a una actividad

La barra de la app suele ser propiedad de la actividad del host. Cuando esto ocurre, los fragmentos pueden interactuar con la barra de la app anulando los métodos del framework que son llamados durante la creación del fragmento.

Cómo registrarse con la actividad

Debes informar al sistema que el fragmento de la barra de la app participa en la propagación del menú de opciones. Para ello, llama a setHasOptionsMenu(true) en el método onCreate(Bundle) de tu fragmento, como se muestra en el siguiente ejemplo:

Kotlin

class ExampleFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setHasOptionsMenu(true)
    }
}

Java

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

setHasOptionsMenu(true) indica al sistema que tu fragmento desea recibir devoluciones de llamada relacionadas con el menú. Cuando ocurre un evento relacionado con el menú (creación, clic, etc.), se llama primero al método de control de eventos en la actividad antes de que se lo llame en el fragmento. Ten en cuenta que la lógica de tu aplicación no debe depender de ese orden. Si varios fragmentos están alojados en la misma actividad, cada uno puede proporcionar opciones de menú. En ese caso, el orden de la devolución de llamada dependerá del orden en que se hayan agregado los fragmentos.

Cómo aumentar un menú

Para combinar tu menú con el menú de opciones de la barra de la app, anula onCreateOptionsMenu() en tu fragmento. Ese método recibe el menú actual de la barra de la app y un MenuInflater como parámetros. Usa el amplificador de menú a fin de crear una instancia del menú del fragmento y, luego, combinarla con el menú actual, como se muestra en el siguiente ejemplo:

Kotlin

class ExampleFragment : Fragment() {
    ...

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.sample_menu, menu)
    }
}

Java

public class ExampleFragment extends Fragment {
    ...

    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
       inflater.inflate(R.menu.sample_menu, menu);
    }
}

En la Figura 2, se muestra el menú actualizado.

El menú de opciones ahora contiene tu fragmento de menú
Figura 2: El menú de opciones ahora contiene tu fragmento de menú

Cómo manejar eventos de clic

Cada actividad y fragmento que participa en el menú de opciones puede responder a toques. El objeto onOptionsItemSelected() del fragmento recibe el elemento de menú seleccionado como un parámetro y muestra un valor booleano para indicar si se consumió el toque. Una vez que una actividad o un fragmento muestre true desde onOptionsItemSelected(), ningún otro fragmento participante recibirá la devolución de llamada.

En tu implementación de onOptionsItemSelected(), usa una declaración switch en el itemId del elemento de menú. Si el elemento seleccionado es tuyo, controla el toque de forma adecuada y muestra true para indicar que se gestionó el evento de clic. Si el elemento seleccionado no es tuyo, llama a la implementación de super. De forma predeterminada, la implementación de super muestra false a fin de permitir que el procesamiento del menú continúe.

Kotlin

class ExampleFragment : Fragment() {
    ...

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.action_settings -> {
                // navigate to settings screen
                true
            }
            R.id.action_done -> {
                // save profile changes
                true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }
}

Java

public class ExampleFragment extends Fragment {
    ...

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_settings:  {
                // navigate to settings screen
                return true;
            }
            case R.id.action_done: {
                // save profile changes
                return true;
            }
            default:
                return super.onOptionsItemSelected(item);
        }

    }

}

Cómo modificar un menú de forma dinámica

La lógica para ocultar o mostrar un botón o para cambiar el ícono debe colocarse en onPrepareOptionsMenu(). Se llama a este método justo antes de cada instancia en la que se muestra el menú.

Siguiendo con el ejemplo anterior, el botón Guardar debe ser invisible hasta que el usuario comienza a editar y debería desaparecer después de que el usuario efectúe la operación de guardar. Si agregas esta lógica a onPrepareOptionsMenu(), se garantiza que el menú siempre se presente de manera correcta:

Kotlin

class ExampleFragment : Fragment() {
    ...

    override fun onPrepareOptionsMenu(menu: Menu){
        super.onPrepareOptionsMenu(menu)
        val item = menu.findItem(R.id.action_done)
        item.isVisible = isEditing
    }
}

Java

public class ExampleFragment extends Fragment {
    ...

    @Override
    public void onPrepareOptionsMenu(@NonNull Menu menu) {
        super.onPrepareOptionsMenu(menu);
        MenuItem item = menu.findItem(R.id.action_done);
        item.setVisible(isEditing);
    }
}

Cuando necesites actualizar el menú (por ejemplo, para editar la información del perfil cuando un usuario presiona el botón Editar), debes llamar a invalidateOptionsMenu() en la actividad del host a fin de solicitar que el sistema llame a onCreateOptionsMenu(). Luego de realizar la invalidación, puedes hacer las actualizaciones en onCreateOptionsMenu(). Una vez que haya aumentado el menú, el sistema llamará a onPrepareOptionsMenu() y lo actualizará a los efectos de reflejar el estado actual del fragmento.

Kotlin

class ExampleFragment : Fragment() {
    ...

    fun updateOptionsMenu() {
        isEditing = !isEditing
        requireActivity().invalidateOptionsMenu()
    }
}

Java

public class ExampleFragment extends Fragment {
    ...

    public void updateOptionsMenu() {
        isEditing = !isEditing;
        requireActivity().invalidateOptionsMenu();
    }
}

Barra de la app perteneciente a un fragmento

Si la mayoría de las pantallas de tu app no necesitan una barra, o bien si una de ellas necesita una barra significativamente diferente, puedes agregar una Toolbar a tu diseño del fragmento. Si bien puedes agregar una Toolbar en cualquier lugar de la jerarquía de vistas del fragmento, en general, debes mantenerlo en la parte superior de la pantalla. Para usar la Toolbar en el fragmento, proporciona un ID y obtén una referencia a ella en tu fragmento, como lo harías con cualquier otra vista.

<androidx.appcompat.widget.Toolbar
    android:id="@+id/myToolbar"
    ... />

Cuando uses una barra de la app perteneciente a un fragmento, recomendamos que utilices directamente las API de Toolbar. No uses setSupportActionBar() ni las API de menú de Fragment, que son adecuadas solo para las barras de la app pertenecientes a una actividad.

Cómo aumentar un menú

El método de conveniencia inflateMenu(int) de Toolbar toma el ID de un recurso de menú como parámetro. Para aumentar un recurso de menú XML en tu barra de herramientas, pasa el resId a este método, como se muestra en el siguiente ejemplo:

Kotlin

class ExampleFragment : Fragment() {
    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...

        viewBinding.myToolbar.inflateMenu(R.menu.sample_menu)
    }
}

Java

public class ExampleFragment extends Fragment {
    ...

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        ...

        viewBinding.myToolbar.inflateMenu(R.menu.sample_menu);
    }

}

Para aumentar otro recurso de menú XML, llama de nuevo al método con el resId del menú nuevo. Los nuevos elementos de menú se agregan a este y los elementos de menú existentes no se modifican ni se quitan.

Si deseas reemplazar el conjunto de menús existente, borra el menú antes de llamar a inflateMenu(int) con el ID de menú nuevo.

Kotlin

class ExampleFragment : Fragment() {
    ...

    fun clearToolbarMenu() {
        viewBinding.myToolbar.menu.clear()
    }
}

Java

public class ExampleFragment extends Fragment {

    ...
    public void clearToolbarMenu() {

        viewBinding.myToolbar.getMenu().clear()

    }

}

Cómo manejar eventos de clic

Puedes pasar un OnMenuItemClickListener directamente a la barra de herramientas mediante el método setOnMenuItemClickListener(). Este objeto de escucha se invoca cada vez que un usuario selecciona un elemento de menú desde los botones de acción que se presentan al final de la barra de herramientas o del menú ampliado asociado. El MenuItem seleccionado se pasa al método onMenuItemClick() del objeto de escucha y se puede usar para consumir la acción, como se muestra en el siguiente ejemplo:

Kotlin

class ExampleFragment : Fragment() {
    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...

        viewBinding.myToolbar.setOnMenuItemClickListener {
            when (it.itemId) {
                R.id.action_settings -> {
                    // Navigate to settings screen
                    true
                }
                R.id.action_done -> {
                    // Save profile changes
                    true
                }
                else -> false
            }
        }
    }
}

Java

public class ExampleFragment extends Fragment {
    ...

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        ...

        viewBinding.myToolbar.setOnMenuItemClickListener(item -> {
            switch (item.getItemId()) {
                case R.id.action_settings:
                    // Navigate to settings screen
                    return true;
                case R.id.action_done:
                    // Save profile changes
                    return true;
                default:
                    return false;
            }
        });
    }
}

Cómo modificar un menú de forma dinámica

Cuando la barra de la app es propiedad de tu fragmento, puedes modificar la Toolbar en el tiempo de ejecución de la misma manera que lo harías para cualquier otra vista.

Siguiendo con el ejemplo anterior, la opción Guardar del menú debe ser invisible hasta que el usuario comience a editar y debe desaparecer nuevamente cuando se la presione:

Kotlin

class ExampleFragment : Fragment() {
    ...

    fun updateToolbar() {
        isEditing = !isEditing

        val saveItem = viewBinding.myToolbar.menu.findItem(R.id.action_done)
        saveItem.isVisible = isEditing

    }
}

Java

public class ExampleFragment extends Fragment {
    ...

    public void updateToolbar() {
        isEditing = !isEditing;

        MenuItem saveItem = viewBinding.myToolbar.getMenu().findItem(R.id.action_done);
        saveItem.setVisible(isEditing);
    }

}

Si está presente, el botón de navegación aparecerá al comienzo de la barra de herramientas. Este botón será visible si estableces un ícono de navegación en la barra de herramientas. También puedes configurar un onClickListener() específico de la navegación, al que se llama cada vez que el usuario hace clic en el botón de navegación, como se muestra en el siguiente ejemplo:

Kotlin

class ExampleFragment : Fragment() {
    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...

        myToolbar.setNavigationIcon(R.drawable.ic_back)

        myToolbar.setNavigationOnClickListener { view ->
            // Navigate somewhere
        }
    }
}

Java

public class ExampleFragment extends Fragment {
    ...

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        ...

        viewBinding.myToolbar.setNavigationIcon(R.drawable.ic_back);
        viewBinding.myToolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Navigate somewhere
            }
        });
    }
}