Menús y Toolbar
en Android
Todo el tema ordenado paso a paso: de dibujar la barra en el layout a responder a los clics del menú. Con el código listo para copiar y simuladores para entenderlo.
La Toolbar
Las Activity tienen una barra arriba, justo debajo de la barra de estado, llamada barra de aplicación. La dibujamos con el widget Toolbar en el fichero layout de la Activity.
<androidx.appcompat.widget.Toolbar
android:id="@+id/my_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
style="@style/Widget.MaterialComponents.Toolbar.Primary"/>Qué hace cada atributo
android:id="@+id/my_toolbar"Id para encontrarla luego desde Kotlin con findViewById.layout_width="match_parent"Ocupa todo el ancho de la pantalla.layout_height="?attr/actionBarSize"Altura estándar de barra de acción (la define el tema).android:elevation="4dp"Sombra para que "flote" sobre el contenido.app:layout_constraint…Anclada arriba, izquierda y derecha del padre (ConstraintLayout).style="…Toolbar.Primary"Estilo Material: fondo colorPrimary y texto colorOnPrimary.Los atributos app: necesitan xmlns:app="http://schemas.android.com/apk/res-auto" en la raíz del layout (Android Studio lo añade solo).
Definir un tema
El tema de la app está en dos ficheros bajo res/values/themes: uno para el modo claro y otro para el oscuro.
▸ drawable
▸ layout
▸ menu
▸ mipmap
▾ values
colors.xml
strings.xml
▾ themes (2)
themes.xml
themes.xml (night)
themes.xml
Tema claro (light) · res/values/themes.xml
themes.xml (night)
Tema oscuro · res/values-night/themes.xml
La vista Android de Android Studio agrupa los dos en una carpeta virtual themes. En disco están en carpetas distintas.
Contenido del fichero de tema
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.NOM_DEL_TEMA_DE_LA_NOSTRA_APP" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>Nombre del tema
Theme.NOM_DEL_TEMA… cambia según el nombre del proyecto.
<style>
Un fichero puede tener varios temas, uno por etiqueta <style>.
<item>
Define el color de un elemento concreto del tema.
Los atributos de color
colorPrimaryColor principal de la marca. Por defecto, el fondo de la Toolbar.colorPrimary · colorSecondaryRepresentan los colores de tu marca.colorPrimaryVariant · colorSecondaryVariantVariantes más claras u oscuras de los colores de marca (la primaria tiñe la barra de estado).android:colorBackgroundColor de fondo de las ventanas de la app.colorErrorPara errores o advertencias.colorOn… (colorOnPrimary…)Pintan el primer plano (texto, iconos) encima de otros colores. Necesitan contraste suficiente (accesibilidad).Más detalles en material.io.
Cambiar los colores a verde
res/values/colors.xml<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="green_200">#A5D6A7</color>
<color name="green_500">#4CAF50</color>
<color name="green_700">#388E3C</color>
...
</resources><item name="colorPrimary">@color/green_500</item> <item name="colorPrimaryVariant">@color/green_700</item>
<item name="colorPrimary">@color/green_200</item> <item name="colorPrimaryVariant">@color/green_700</item>
En tema oscuro se usan tonos más claros para que no deslumbren sobre fondo negro.
¿Qué pinta cada atributo?
Activar la Toolbar
Para que la barra aparezca al ejecutar, en onCreate la buscamos por su id y la convertimos en la barra de acción de la Activity.
val myToolbar: Toolbar = findViewById(R.id.my_toolbar) setSupportActionBar(myToolbar)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val myToolbar: Toolbar = findViewById(R.id.my_toolbar)
setSupportActionBar(myToolbar)
}Resultado esperado
findViewById(R.id.my_toolbar)
Obtiene la Toolbar del layout a partir de su id.
setSupportActionBar(...)
La convierte en la barra de acción: ya puede mostrar título y menú.
Usa androidx.appcompat.widget.Toolbar (no android.widget.Toolbar). La Activity debe heredar de AppCompatActivity.
Si el tema ya trae ActionBar (…DarkActionBar) y llamas a setSupportActionBar, puede petar con "This Activity already has an action bar supplied by the window decor". Solución: tema padre …NoActionBar (ver paso 06).
Añadir acciones: el recurso de menú
Las acciones de la Toolbar se definen en un recurso de menú: un fichero .xml dentro de res/menu.
Etiquetas
<menu> OBLIGATORIO
Tiene que ser el nodo raíz. Contiene <item> o <group>.
<item>
Un elemento del menú. Puede contener un <menu> → submenú. Debe ser hijo de <menu> o <group>.
<group>
Agrupa varios <item> para compartir propiedades (visibilidad, activación…).
<menu> <!-- raíz obligatoria -->
<item />
<group>
<item />
<item />
</group>
<item>
<menu> <!-- submenú -->
<item />
</menu>
</item>
</menu>Atributos de <item>
android:idId del recurso, para referenciarlo (p. ej. en onOptionsItemSelected).android:titleTítulo de la opción.android:titleCondensedTítulo corto para cuando el normal es demasiado largo.android:iconImagen usada como icono del elemento.android:onClickMétodo que se ejecuta al pulsar. Público en la Activity y con un MenuItem como único parámetro.showAsActionCuándo y cómo aparece como botón de acción en la barra (en la práctica app:showAsAction).fun onFavoriteClick(item: MenuItem) {
Toast.makeText(this, "Favorito", Toast.LENGTH_SHORT).show()
}Valores de showAsAction
En la barra solo si hay espacio. Si no, al menú ⋮.
Muestra también el título. Se combina: ifRoom|withText.
Nunca en la barra. Siempre en el menú ⋮.
Siempre en la barra. Evitar salvo lo esencial: se pueden superponer.
Juega con showAsAction y el ancho de pantalla
Creación del menú
Creamos el fichero de menú, lo inflamos en la Activity y gestionamos qué pasa al pulsar cada opción.
resNew → Android Resource File
File name: my_toolbar_menu · Resource type: Menu
En res/menu/my_toolbar_menu.xml
onCreateOptionsMenu
onOptionsItemSelected
ic_baseline_favorite_24: descárgalo de fonts.google.com/icons o créalo con Vector Asset de Android Studio.
Los ficheros del menú
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_favorite"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_baseline_favorite_24"
android:title="@string/action_favorite" />
<!-- Settings, should always be in the overflow -->
<item
android:id="@+id/action_settings"
app:showAsAction="never"
android:title="@string/action_settings" />
</menu><string name="action_favorite">Favorite</string> <string name="action_settings">Settings</string>
Los títulos usan @string/…: hay que crearlos en strings.xml o no compila.
El código de la Activity
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.my_toolbar_menu, menu)
return true
}override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_favorite -> {
Toast.makeText(this, "You have selected Favorite", Toast.LENGTH_SHORT).show()
true
}
R.id.action_settings -> {
Toast.makeText(this, "You have selected Settings", Toast.LENGTH_SHORT).show()
true
}
else -> super.onOptionsItemSelected(item)
}
}supportActionBar?.hide() // ocultar la Toolbar supportActionBar?.show() // mostrarla otra vez
inflate()
menuInflater.inflate convierte el XML en MenuItems. return true = se muestra.
item.itemId
El android:id del item pulsado. Devolvemos true si lo hemos gestionado.
else → super
Si no es nuestro, lo gestiona el padre. El ?. de supportActionBar es porque puede ser null.
Igual que en el emulador
Pulsa ♥ o ⋮ → Settings en el móvil y mira qué se ejecuta:
Personalización de la Toolbar
Podemos definir temas distintos al principal de la app solo para la Toolbar y para su menú desplegable.
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.NOM_DEL_TEMA_DE_LA_NOSTRA_APP" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- The App Theme -->
...
</style>
<style name="Theme.Custom.Toolbar" parent="ThemeOverlay.MaterialComponents.Dark">
<item name="android:background">@color/customGray</item>
<item name="android:textColor">@color/black</item>
<item name="android:textColorPrimary">@color/black</item>
<item name="android:textColorSecondary">@color/black</item>
</style>
<style name="Theme.Custom.Toolbar.Popup" parent="ThemeOverlay.MaterialComponents.Dark">
<item name="colorSurface">@color/customGray</item>
<item name="android:background">@color/customGray</item>
<item name="android:textColor">@color/black</item>
</style>
</resources><androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:theme="@style/Theme.Custom.Toolbar"
app:popupTheme="@style/Theme.Custom.Toolbar.Popup"/>android:theme
Tema de la Toolbar en sí: fondo, título, iconos.
app:popupTheme
Tema de su menú desplegable (overflow ⋮).
Ahora el padre es …DayNight.NoActionBar: la barra la pone nuestra Toolbar, no el sistema. Y customGray hay que definirlo en colors.xml.
Tema por defecto vs personalizado
Toolbar con colorPrimary y menú con el tema por defecto.
Chuleta rápida
Todo el tema en una pantalla, para repasar antes del examen.
<androidx.appcompat.widget.Toolbar/>colorPrimary colorPrimaryVariant colorOnPrimary<color name="green_500">setSupportActionBar(myToolbar)<menu><item app:showAsAction/>menuInflater.inflate(R.menu.x, menu)when (item.itemId) {…}supportActionBar?.hide() show()android:theme app:popupThemealways siempre · ifRoom si cabe · never al ⋮ · withText + título
<menu> raíz obligatoria · <item> opción / submenú · <group> agrupa
Quiz tipo Kahoot
20 preguntas en orden aleatorio. 20 segundos cada una: cuanto más rápido aciertes, más puntos.