La Snackbar en Android es un elemento parecido al Toast, ya que muestra un mensaje emergente al usuario para informar sobre una acción realizada. Pero adicionalmente puede mostrar un botón al usuario para interactuar con el resultado percibido.
En este tutorial veremos cómo usar a clase Snackbar
, uno de los componentes de la librería de Material Design de Google para comunicar al usuario acciones realizadas si tu app está en primer plano. Acciones como confirmar operaciones sobre registros, peticiones web terminadas, cambios de estados en servicios del sistema, etc.
Para interiorizar las funcionalidades generales de este widget, usaremos como referencia la siguiente App de ejemplo:
En ella usaremos una Snackbar para notificar en el momento en que añade un registro a una lista. Adicionalmente proveeremos la habilidad de deshacer la inserción del último registro. Puedes descargar el proyecto de Android estudio desde el siguiente enlace:
Crear Y Mostrar Una Snackbar En Android
En nuestro ejemplo mostramos la siguiente Snackbar
con el mensaje de que un registro de texto ha sido agregado a la lista:
Para lograr este resultado primero debes añadir la dependencia a material components:
dependencies {
implementation 'com.google.android.material:material:ultima-version'
}
Luego usa alguno de los métodos de fabricación make() para crear la Snackbar y el método show()
para mostrarla.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Snackbar.make(
findViewById(R.id.textview),
"Registro guardado",
BaseTransientBottomBar.LENGTH_SHORT
).show()
}
}
Donde los argumentos del método make()
cumplen el siguiente propósito:
context
: El contexto usado para crear el view de la Snackbarview
: Un view usado como referencia para buscar sobre el árbol de jerarquía hasta encontrar un padre adecuado en donde añadir la Snackbar. Por adecuado se entiende que sea de tipoCoordinatorLayout
o el view de primer nivel de la ventana.text
: El recurso string o secuencia de caracteres a mostrar como mensaje.duration
: La cantidad de milisegundos que se mostrará el widget. Puedes usar las constantesLENGTH_LONG
,LENGTH_SHORT
yLENGTH_INDEFINITE
; o también un tiempo personalizado.
Añadir Acción A Una Snackbar
Con nuestra app de registros permitimos al usuario deshacer la agregación de registros a través del botón de acción «DESHACER».
Para agregar esta acción a la Snackbar usa el método setAction()
. El primer parámetro será el nombre de la acción y el segundo un ejemplar que implemente el observador View.OnClickListener
:
private fun setUpSnackbar() {
snackbar = Snackbar.make(
findViewById(R.id.constraint_layout),
"Registro guardado",
BaseTransientBottomBar.LENGTH_LONG
)
snackbar.setAction("Deshacer") {
saveCommand.undo()
updateLogsList()
}
}
private fun updateLogsList() {
val logView: TextView = findViewById(R.id.log_view)
logView.text = logs.joinToString(separator = "\n")
}
Al interior de la escucha de la acción en la Snackbar, invocamos al método undo()
de un pequeño comando que hemos creado (LogCommand
) para encapsular el guardado de cada registro:
interface LogCommand {
fun saveLog(log: String)
fun undo()
}
class SaveLogCommand(private val logs: MutableList<String>) : LogCommand {
private var lastLog: String? = null
override fun saveLog(log: String) {
val sanitizeLog = if (log.isBlank()) "[vacío]" else log
lastLog = sanitizeLog
logs.add(sanitizeLog)
}
override fun undo() {
logs.remove(lastLog)
}
}
Este tiene como propiedad a una lista mutable de strings, la cual actúa como el recibidor del comando. Con saveLog()
guardados un registro y lo retenemos como el último disponible.
private fun saveLog() {
val log = getLogText()
saveCommand.saveLog(log)
updateLogsList()
hideKeyboard()
buildSnackbar()
}
private fun updateLogsList() {
val logView: TextView = findViewById(R.id.log_view)
logView.text = logs.joinToString(separator = "\n")
}
Y con undo()
removemos al último registro.
Incrementar Funcionalidad Con El CoordinatorLayout
Si despliegas una Snackbar sobre un CoordinatorLayout
esta ganará comportamientos adicionales debido a la interacción con él. Por ello en el ejemplo usamos esta jerarquía en el layout de la actividad principal.
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:id="@+id/constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<include layout="@layout/main_content" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/save_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
app:srcCompat="@drawable/ic_save"
android:contentDescription="Guardar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Deslizar Para Descartar Snackbar
Una de las posibilidades otorgadas, es la capacidad de desvanecer a la Snackbar con un swipe horizontal:
Floating Action Button Y Snackbar
Si en el CoordinatorLayout
existe un FloatingActionButton
y se muestra una Snackbar, el FAB se desplazará hacia arriba para dar paso momentáneamente a la aparición de mensaje. Una vez desvanecida, el FAB recobrará su posición original.
Cabe destacar que en la sección de lineamientos para el desplazamiento del componente, nos recomiendan dejar al FAB intacto y mostrar la Snackbar por encima.
Esto puedes lograrlo asignando el ID del FAB para asignarlo como «ancla» con el método setAnchorView()
.
private fun setUpSnackbar() {
Snackbar.make(/**/,/**/,/**/)
.setAction("Deshacer") {
// ...
}.setAnchorView(R.id.save_fab)
}
Personalizar La Snackbar
Nuestra app tiene un control Switch
para establecer el estilo aplicado a las Snackbars en la parte superior. Cuando lo activamos, estas comienzan a mostrarse con la siguiente apariencia:
Y es que la clase Snackbar
posee varias propiedades mutables y métodos set*()
para cambiar atributos como: color del texto en el mensaje, color de background, texto de la acción, etc.:
private fun buildSnackbar() {
// ...
if (useStyle) {
snackbar.setBackgroundTint(color(R.color.purple_700))
snackbar.setActionTextColor(color(R.color.purple_200))
} else {
snackbar.setBackgroundTint(color(R.color.teal_700))
snackbar.setActionTextColor(color(R.color.teal_200))
}
snackbar.show()
}
private fun setUpSwitch() {
val switch: SwitchMaterial = findViewById(R.id.style_switch)
switch.setOnCheckedChangeListener { _, isChecked ->
useStyle = isChecked
}
}
private fun color(color: Int) = ContextCompat.getColor(this, color)
En el código anterior usamos los métodos setBackgrountTint()
para asignar el color del background del view de la snackbar. También invocamos a setActionTextColor()
para modificar el color del botón de acción.
Aplicar Un Tema
Si deseas aplicar un mismo tema a todas las snackbars que aparezcan a lo largo de tu aplicativo, entonces puedes usar los atributos snackbarStyle
, snackbarButtonStyle
y snackbarTextViewStyle
para modificar la apariencia general, la del botón de acción y el texto:
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.SnackbarEnAndroid" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="snackbarStyle">@style/Widget.App.Snackbar</item>
<item name="snackbarButtonStyle">@style/Widget.App.SnackbarButton</item>
</style>
<style name="Widget.App.Snackbar" parent="Widget.MaterialComponents.Snackbar">
<item name="backgroundTint">@color/teal_700</item>
<item name="actionTextColorAlpha">1</item>
</style>
<style name="Widget.App.SnackbarButton" parent="Widget.MaterialComponents.Button.TextButton.Snackbar">
<item name="android:textColor">@color/teal_200</item>
</style>
</resources>
El resultado producido por la modificación anterior a los colores del background y la apariencia del texto, sería el siguiente:
Observar Aparición Y Desaparición
Nuestra App de registro también cuenta la cantidad de veces que se ha deshecho una acción de inserción:
Esto lo logramos usando al observador Snackbar.Callback
, el cual provee dos métodos para procesar el momento en que aparece (onShow()
) y desparece (onDismissed()
) la Snackbar que emite las notificaciones:
snackbar.addCallback(object : Snackbar.Callback() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
if (event == DISMISS_EVENT_ACTION) {
counter++
updateTextCounter()
}
}
})
private fun updateTextCounter() {
counterText.text = "Cantidad de inserciones deshecha: $counter"
}
En el anterior código creamos una expresión de objeto anónimo para crear la instancia de Snackbar.Callback
. Al sobrescribir onDismissed()
usamos el parámetro event
para determinar el origen de la causa del descarte de la Snackbar.
Buscamos solo los eventos relacionados al click del usuario en DESHACER, por lo que usamos la constante DISMISS_EVENT_ACTION
y luego actualizamos el texto del contador.
Claramente existen otros valores para diferentes causas:
DISMISS_EVENT_CONSECUTIVE
: Cuando la Snackbar se descarta por la aparición de otraDISMISS_EVENT_MANUAL
: Cuando llamaste manualmente al métododismiss()
DISMISS_EVENT_SWIPE
: Cuando deslizaste la Snackbar para desvanecerlaDISMISS_EVENT_TIMEOUT
: Cuando la Snackbar se desvaneció sola
Al momento de juntar todas estas características la aplicación permite guardar registros en una lista y deshacer el último comando inserción.
Únete Al Discord De Develou
Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!