En el artículo anterior vimos cómo incluir una action bar (o app bar) en nuestras aplicaciones haciendo uso de la funcionalidad básica incluida por defecto en nuestras actividades al utilizar uno de los temas de la librería de soporte appcompat y extender nuestras actividades de AppCompatActivity
. También vimos cómo personalizar sus características básicas, como colores, título y menús.
Una forma más flexible y personalizable de añadir una action bar a una aplicación es utilizar el componente Toolbar
proporcionado por la librería appcompat. De esta forma podemos incluir de forma explícita la action bar en nuestros layouts XML como si fuera cualquier otro control, y no sólo en la parte superior de la pantalla a modo de app bar, sino también en cualquier otro lugar de la aplicación donde queramos utilizar esta funcionalidad de barra de acciones.
Veremos primero como utilizar el componente Toolbar
a modo de app bar.
Lo primero que debemos asegurar es que nuestro proyecto incluye la última versión de la librería de soporte appcompat, en la sección de dependencias del fichero build.gradle:
dependencies { .... implementation 'androidx.appcompat:appcompat:1.1.0' .... }
A continuación necesitamos «desactivar» la funcionalidad por defecto que vimos en el artículo anterior configurando un tema para nuestra aplicación que no incluya la action bar. Para ello, editaremos el fichero /res/values/styles.xml para hacer que nuestro tema extienda de alguno de los siguientes (dependiendo si queremos partir del tema oscuro o claro):
Theme.AppCompat.NoActionBar
Theme.AppCompat.Light.NoActionBar
En mi caso utilizaré el segundo:
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> </resources>
Como podéis ver podemos definir también los colores principales a utilizar (colorPrimary
, colorPrimaryDark
y colorAccent
), igual que hicimos en el artículo anterior.
Recordemos también que nuestras actividades deben extender a AppCompatActivity
:
import androidx.appcompat.app.AppCompatActivity //... class MainActivity : AppCompatActivity() { //... }
Hecho esto, ya podemos modificar el layout de nuestra actividad para incluir la action bar utilizando el nuevo componente Toolbar
. Veamos cómo quedaría el código y después comentamos los detalles más importantes:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <androidx.appcompat.widget.Toolbar android:id="@+id/appbar" android:layout_height="wrap_content" android:layout_width="match_parent" android:minHeight="?attr/actionBarSize" android:background="?attr/colorPrimary" android:elevation="4dp" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" > </androidx.appcompat.widget.Toolbar> </LinearLayout>
En mi caso utilizo un LinearLayout
como contenedor principal, sin márgenes interiores, y en su interior incluyo como primer control el Toolbar
que actuará como action bar de la actividad. Entre las propiedades asignadas, además de las habituales id
, layout_height
y layout_width
, vemos las siguientes:
android:minHeight
. Asignando esta propiedad al alto estándar de una action bar (?attr/actionBarSize
) conseguimos que los botones de acción y menú de overflow queden siempre en la parte superior de la barra de herramientas aunque incrementemos su tamaño mediantelayout_height
(referencia).android:background
. Asignaremos a esta propiedad el valor?attr/colorPrimary
de forma que se utilice como color de la barra de herramientas el que hemos definido comocolorPrimary
en el tema de la aplicación (fichero /res/values/styles.xml).android:elevation
. Esta propiedad define la elevación del componente, lo que determina la sombra que proyectará sobre el elemento inferior. La elevación estándar de la action bar definida en las guías de diseño es de 4dp.android:theme
. Mediante esta propiedad definimos el tema a utilizar por laToolbar
(y que heredarán sus controles hijos). No debemos confundir esto con el tema definido a nivel de aplicación en el fichero styles.xml. Para conseguir el mismo efecto que en el artículo anterior, donde utilizamos el tema globalTheme.AppCompat.Light.DarkActionBar
(es decir, un tema claro con actionbar oscura) debemos utilizar un tema claro a nivel de aplicación (en nuestro caso vimos antes como utilizamos el temaTheme.AppCompat.Light.NoActionBar
) y en laToolbar
utilizaremos el tema oscuro asignando la propiedadapp:theme
, en este caso con el temaThemeOverlay.AppCompat.Dark.ActionBar
.app:popupTheme
. Esta propiedad la utilizaremos sólo si es necesario. En nuestro caso particular lo es, para «arreglar» un efecto colateral de utilizar el tema oscuro para laToolbar
. Y es que utilizar este tema también tiene como efecto que el menú de overflow aparezca de color oscuro. Para conseguir que la barra sea oscura pero el menú claro podemos asignar un tema específico sólo a este menú utilizando la propiedadapp:popupTheme
, en nuestro caso el temaThemeOverlay.AppCompat.Light
.
Con esto ya tendríamos finalizado el layout XML de la actividad principal con su action bar correspondiente, por supuesto a falta de incluir el resto de controles necesarios para nuestra aplicación (yo incluiré a modo de ejemplo un cuadro de texto y un checkbox, igual que hicimos en el artículo anterior). Por su parte, el menú de overflow se definiría exactamente igual que en el artículo anterior.
Pero nos queda aún un paso importante. En nuestro código debemos indicar que esta Toolbar
actuará como app bar de la actividad. Para ello, en el método onCreate()
de la actividad haremos una llamada a setSupportActionBar()
con la referencia a la toolbar:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val tbar = findViewById<Toolbar>(R.id.appbar) setSupportActionBar(tbar) } }
Para no tener que reescribir la definición completa del toolbar en todas nuestras actividades también podemos hacer uso de la cláusula include
. Para ello, declaramos primero el toolbar en un layout XML independiente, por ejemplo en un fichero llamado /res/layout/toolbar.xml:
<?xml version="1.0" encoding="utf-8"?> <androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_height="wrap_content" android:layout_width="match_parent" android:minHeight="?attr/actionBarSize" android:background="?attr/colorPrimary" android:elevation="4dp" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" > </androidx.appcompat.widget.Toolbar>
Y posteriormente incluir este fragmento en el layout de nuestras actividades haciendo referencia a él mediante include
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <include android:id="@+id/appbar" layout="@layout/toolbar" /> <!-- Resto de la interfaz de usuario --> </LinearLayout>
Como podéis ver, en este caso definimos la propiedad android:id
del control en en el include
, y no en el layout independiente del toolbar.
Ahora sí podríamos ejecutar la aplicación para ver si todo funciona según lo esperado. Si lo hacemos sobre un emulador/dispositivo con Android 5.0 o posterior veremos lo siguiente:
Después de todo esto no hemos llegado más que al mismo resultado que en el ejemplo del artículo anterior, pero escribiendo más código. Supongo que te preguntarás, ¿y qué ventajas obtengo? Pues bien, una de las más inmediatas es que de esta forma puedo seguir personalizando la action bar muy fácilmente, por ejemplo modificando su tamaño o añadiendo controles en su interior. Para crear por ejemplo una action bar extendida que además muestre dos líneas de texto con título y subtítulo podríamos modificar de la siguiente forma nuestro fichero toolbar.xml:
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_height="128dp" android:layout_width="match_parent" android:minHeight="?attr/actionBarSize" android:background="?attr/colorPrimary" android:elevation="4dp" android:gravity="bottom" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:paddingBottom="16dp" > <TextView android:id="@+id/lblAbTitulo" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/Título" style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" /> <TextView android:id="@+id/lblAbSubTitulo" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/Subtítulo" style="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle" /> </LinearLayout>
Como podéis ver en el ejemplo anterior, hemos asignado dos nuevas propiedades en el control Toolbar
. En primer lugar hemos incrementado el alto del control a 128dp, y además hemos establecido la propiedad gravity al valor bottom
, de forma que los componentes que añadamos en su interior se alineen sobre el borde inferior.
Adicionalmente, dentro del elemento Toolbar
hemos añadido dos etiquetas de texto para mostrar el título y el subtítulo, como si se tratara de cualquier otro contenedor. Lo único a destacar es que he utilizado dos estilos predefinidos para estas etiquetas (AppCompat.Widget.ActionBar.Title
y AppCompat.Widget.ActionBar.Subtitle
) aunque podría utilizarse cualquier formato para estos elementos.
Adicionalmente, tendremos que indicar a Android que no muestre el título por defecto en la action bar, ya que el contenido de la misma lo vamos a proporcionar nosotros. Para ello usaremos el método setDisplayShowTitleEnabled() en el método onCreate():
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val tbar = findViewById<Toolbar>(R.id.appbar) setSupportActionBar(tbar) supportActionBar?.setDisplayShowTitleEnabled(false); }
Con estos cambios nuestra aplicación quedaría así:
Otra ventaja que ya hemos comentado al disponer del control Toolbar
como componente independiente es que podemos utilizarlo en otros lugares de nuestra interfaz, y no siempre como barra de acciones superior.
Así, podríamos por ejemplo utilizar un componente toolbar dentro de una tarjeta (ver artículo sobre CardView). Para ello, añadamos una tarjeta a nuestra aplicación de ejemplo, y simplemente incluyamos en su interior un control Toolbar
de la misma forma que hemos hecho antes:
<androidx.cardview.widget.CardView xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="200dp" app:cardUseCompatPadding="true" app:cardCornerRadius="4dp" android:layout_margin="8dp"> <androidx.appcompat.widget.Toolbar android:id="@+id/tbCard" android:layout_height="?attr/actionBarSize" android:layout_width="match_parent" android:minHeight="?attr/actionBarSize" android:theme="@style/ThemeOverlay.AppCompat.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" > </androidx.appcompat.widget.Toolbar> </androidx.cardview.widget.CardView>
Si ejecutáramos la aplicación en este momento, la toolbar no se vería ya que no le hemos asignado ningún título ni ningún menú. Anteriormente no tuvimos que hacer esto de forma explícita porque al indicar que la toolbar iba a hacer las función de app bar (mediante la llamada a setSupportActionBar()
), el título y el menú lo tomó automáticamente de la actividad asociada. Sin embargo, en esta ocasión la toolbar es independiente de la actividad, por lo que tendremos que asignar estos elementos nosotros mismos.
Para ello, desde el método onCreate()
de la actividad recuperaremos una referencia al control, y usaremos su propiedad title
y el método inflateMenu()
para asignar el título y el menú respectivamente. Para mi caso de ejemplo he definido un nuevo menú menu_tarjeta
(definido en el fichero /res/menu/menu_tarjeta.xml) con dos acciones de muestra:
override fun onCreate(savedInstanceState: Bundle?) { //... val tbCard = findViewById<Toolbar>(R.id.tbCard) tbCard.title = "Mi tarjeta" tbCard.setOnMenuItemClickListener { when (it.itemId) { R.id.action_1 -> { Log.i("ActionBar", "Acción 1") true } R.id.action_2 -> { Log.i("ActionBar", "Acción 2") true } else -> { true } } } tbCard.inflateMenu(R.menu.menu_tarjeta) }
Vemos también en el código anterior cómo utilizaremos el método setOnMenuItemClickListener()
para asignar el listener que responderá a las pulsaciones del usuario sobre las opciones del menú. En mi caso sólo escribo dos mensajes de log, pero obviamente podremos realizar cualquier acción como respuesta.
Si ejecutamos ahora la aplicación de ejemplo debemos ver la nueva tarjeta según lo definido:
Puedes consultar y/o descargar el código completo de los ejemplos desarrollados en este artículo accediendo a la pagina del curso en GitHub.
Con esto, finalizamos todos los aspectos básicos que quería comentar sobre el nuevo componente Toolbar
. En el artículo siguiente veremos dos alternativas de navegación relacionadas con la action bar: filtros (page filter) y pestañas (tabs).
16 comentarios
Un apunte: usas app:theme en alguno de los ejemplos, pero está deprecated. Ahora hay que usar android:theme
Saludos y felicidades por el post
Gracias por el aviso, las cosas que pasan por tener a medio escribir los artículos desde hace meses. Efectivamente es como dices, ahora se puede y se debe utilizar directamente android:theme. Lo corrijo lo antes posible. Saludos.
[…] anteriores aprendimos a hacer uso de la funcionalidad básica de una action bar y utilizar el nuevo componente Toolbar para conseguir el mismo comportamiento e incluso extenderlo a otras partes de la […]
[…] la actividad principal. Para este ejemplo utilizaré un layout muy similar a los ya mostados en los artículos anteriores sobre la action bar. Contendrá tan sólo un Toolbar, que estableceremos como action bar en el […]
Hola, me gustaría saber como podría incluir el titulo en la parte de arriba al lado de donde tendría que estar el menú hamburger? Desde ya, gracias. Sinceramente este blog es de lo mejor que eh encontrado!
[…] como action bar (o app bar, según la nueva terminología). Esto lo explicamos en detalle en el segundo artículo sobre la action bar. Obviaré en esta ocasión los “trucos” para conseguir la sombra […]
Te sobra la línea 14
Corregido. Gracias por el aviso.
[…] Actionbar / Appbar / Toolbar (II): Toolbar [Nuevo!] […]
Muchas gracias por compartir tus conocimientos tan claramente, tu blog es genial!
Muy bueno el tuto!! una duda, que pasa ahora con la contextual action bar o split action bar? como lo logramos con el toolbar
Muy Bueno, muchas gracias.
[…] definir los colores principales de nuestra aplicación. Por ejemplo en los artículos sobre la action bar vimos cómo especificar, en tiempo de diseño, los colores básicos (primary, accent, …) a […]
Es posible centrar el texto de un ActionBar?
Llevo toda la tarde mirando y no se porque me ocurre el siguiente error que a continuación expongo. Agradecería alguna idea para solucionarlo.
La aplicación tiene en el layout de su actividad principal (FavoritosVer) un RecyclerView. La lista la llenamos con CardView, que a su vez tiene también un Toolbar. En resumen el layout del CardView sería de este estilo:
<android.support.v7.widget.CardView
…
<android.support.v7.widget.Toolbar
android:id="@+id/toolbarCardFavoritos"
…
…
Y el problema lo tengo cuando quiero poner un menú en este Toolbar. En la actividad principal pongo:
Toolbar toolbarCard = (Toolbar) findViewById(R.id.toolbarCardFavoritos);
Y cuando añado:
setSupportActionBar(toolbarCard); o toolbarCard.inflateMenu(R.menu.card_menu);
me da error. En el LogCat me da: NullPointerException en los dos casos.
Gracias de antemano.
[…] que ya explicamos en el artículo anterior, incluiremos este código en un fichero independiente /res/layout/toolbar.xml, que añadiremos al […]