En este artículo ampliaré el uso del Floating Action Button para la promoción de acciones. Recuerda que este elemento hace parte del estándar Material Design para el nuevo diseño de UI.
Lo primero que aprenderás (o recordarás) será la ubicación de este elemento en un layout. Luego probarás las diferentes posiciones que puede tomar para romper bordes entre elementos. También verás cómo adjuntarle una escucha de eventos, generarás algunas animaciones y transformar el FAB en una Toolbar.
Floating Action Button En Material Design
Como dice Google en la documentación del Material Design, un float action button es un botón para destacar una acción en tu app. Se caracteriza por tener una forma circular, un icono interno que representa la acción, una reacción de superficie y la capacidad de cambiar de forma, desplazarse e interactuar con otros elementos.
Este viene en dos tamaños: por defecto (56dp) y mini (24dp). El primero se usa para proporcionar acciones generales, el segundo se adapta mejor cuando se necesita un elemento que siga el mismo patrón de orientación de otros componentes.
En java el floating action button se define por la clase FloatingActionButton
. En Xml usa la etiqueta <android.support.design.widget.FloatingActionButton>
para representar el view dentro de un layout.
Por ejemplo…
<android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/margenes_fab" android:src="@drawable/icono_agregar" />
Ten en cuenta los siguientes atributos a la hora de usar el FAB:
android:layout_margin
: Establece una medida para todas las márgenes del floating action button. Usa 16dp si la ubicación es cerca a los márgenes y bordes de un componente.android:src
: Permite cambiar el icono del floating action button. Si quieres descargar iconos en varias densidades te recomiendo la herramienta Material Design Icons.app:fabSize
: Determina el tamaño del floating action button. Usa el valornormal
para el tamaño estándar de 56dp omini
para reducir a 24dp.app:backgroundTint
: Asigna un color para el fondo del botón. Como dice la documentación, por defecto su background tomará el atributocolorAccent
del tema. Para hacerlo programáticamente usa el métodosetBackgroundTintList()
.
Floating action button con OnClickListener
Antes de explicar esta parte, crea un nuevo proyecto en Android Studio con las siguientes características:
- Nombre:«Floating Action Button»
- Actividad inicial: «Blank Activity» con nombre
ActividadPrincipal.java
- Versión mínima SDK: 14
La plantilla de la actividad en blanco por defecto trae consigo un floating action button en su layout y además implementa una acción de click en el código.
Eventos FAB — Usa la interfaz View.OnClickListener
sobre el fab para procesar los eventos de click que se produzcan.
Para relacionar la escucha usa el método setOnClickListener()
sobre la instancia del floating action button y sobrescribe el método onClick()
con las acciones a ejecutar.
ActividadPrincipal.java
import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; public class ActividadPrincipal extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.actividad_principal); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Se presionó el FAB", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); } }
El anterior código ejecuta una Snackbar para mostrar el mensaje "Se presionó el FAB"
. Obviamente tu pondrás acciones más complejas cómo iniciar una nueva actividad ó agregar un pago.
Rompimiento de bordes con el floating action button
Romper los bordes con un floating action button es un principio del diseño de layouts en Material Design para la armonización de superficies.
Para lograr este efecto de superposición entre elementos puedes usar layouts especializados en promover capas cómo el FrameLayout
o RelativeLayout
. Solo debes calcular las márgenes necesarias para acomodarlos.
Otra alternativa es usar el CoordinatorLayout
para este fin. Lo importante es tener en consideración los siguientes atributos de elemento:
app:layout_anchor
: Representa el id del view al cuál se anclará el presente elemento.app:layout_anchorGravity
: Valor de las constantes de ‘gravedad’ relativas del presente elemento con respecto a un borde del view asignado enlayout_anchor
.
Por ejemplo…
En una app bar extendida con un tamaño de 200dp puedes romper su borde inferior con un FAB.
Esto se hace asignado el id del contenido de la actividad y denotando el anclaje hacia los bordes superior y derecho, con las constantes top|end
.
actividad_principal.xml
<android.support.design.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:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="200dp" android:fitsSystemWindows="true" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed" app:toolbarId="@+id/toolbar"> <android.support.v7.widget.Toolbar android:id="@+id/detail_toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:id="@+id/contenedor_detalle_item" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|start" android:layout_margin="@dimen/fab_margin" android:src="@drawable/icono_compartir" app:layout_anchor="@+id/contenedor_detalle_item" app:layout_anchorGravity="top|end" /> </android.support.design.widget.CoordinatorLayout>
El resultado de la previsualización sería el siguiente:
Haz de cuenta que el coordinator layout le entrega un poder especial al view que es marcado por app:layout_anchor
para que se comporte como referencia absoluta de las posiciones relativas del elemento anclado. En este caso el floating action button.
Si quieres hacer flotar el fab hacia el lado izquierdo, entonces usa start|top
para app:layout_anchorGravity
.
Animación de escala en el Floating Action Button
El FAB puede variar su comportamiento en las escenas debido a su versatilidad como material flexible. Uno de los casos más frecuente es la variación de su escala al entrar o salir de escena.
Para simular el cambio de escala, usa el método clase AnimationUtils.loadInterpolator()
para crear un interpolador que permita controlar los tiempos en animaciones de escala.
En este caso animaré el floating action button para que comience en una escala 0
y termine en su tamaño original al entrar a la escena.
ActividadPrincipal.xml
import android.animation.Animator; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; public class ActividadPrincipal extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.actividad_principal); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); final FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setScaleX(0); fab.setScaleY(0); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { final Interpolator interpolador = AnimationUtils.loadInterpolator(getBaseContext(), android.R.interpolator.fast_out_slow_in); fab.animate() .scaleX(1) .scaleY(1) .setInterpolator(interpolador) .setDuration(600) .setStartDelay(1000) .setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { fab.animate() .scaleY(0) .scaleX(0) .setInterpolator(interpolador) .setDuration(600) .start(); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); } fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Se presionó el FAB", Snackbar.LENGTH_LONG).show(); } }); } }
Android proporciona un interpolador prediseñado para el cálculo de aceleración de un movimiento curvo llamado fast_out_slow_in.xml
, el cual cargamos con AnimationUtils
.
El código anterior sirve en versiones LOLLIPOP en adelante. Si quieres ver animaciones con soporte de versiones anterior, puedes ver el tutorial Working with Android FAB Animations. De verdad es muy útil.
Rotar Floating Action Button Al Clickearlo
Otra animación básica es la rotación del floating action button al clickearlo. Esta se ve muy bien cuando el icono hace referencia a la obtención de más acciones al primer click, y luego rota en forma de x (gesto de cerrar) para revertir el resultado.
Esta vez se usa el método rotate()
para girar el componente en el plano XY con respecto a su centro. Especificaré 45
grados para la primera rotación al clickear, y luego se retorna a 0
según el valor de una bandera global.
ActividadPrincipal.xml
import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; public class ActividadPrincipal extends AppCompatActivity { boolean click = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.actividad_principal); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setImageResource(R.drawable.ic_agregar); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { click = !click; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { Interpolator interpolador = AnimationUtils.loadInterpolator(getBaseContext(), android.R.interpolator.fast_out_slow_in); view.animate() .rotation(click ? 45f : 0) .setInterpolator(interpolador) .start(); } } }); } }
Transformar Floating Action Button En Toolbar
Los principios de comportamiento del fab en Material Design, sugieren que es posible transformar este componente en otros elementos que extiendan su funcionalidad. Ya sean toolbars, menús, hojas de material, diálogos, etc.
Llevar a cabo este tipo de animaciones es complejo debido a la orquestación que se debe realizar entre las animaciones de desplazamiento y forma del floating action button junto al otro componente final.
Sin embargo me encontré una librería muy interesante que te permitirá realizar un «morphing» de fab a Toolbar. Su nombre FABToolbar y a continuación te dejo un pequeño ejemplo de su uso.
Nota: Esta librería soporta versiones del SDK de Android mayores o iguales a 15.
Ejemplo…
1. Abre tu archivo build.gradle
y añade la dependencia compile com.github.fafaldo:fab-toolbar:1.2.0
:
build.gradle
dependencies { ... compile 'com.github.fafaldo:fab-toolbar:1.2.0' }
2. Esta librería usa un componente personalizado llamado FABToolbarLayout
, el cual debe albergar una envoltura para el floating action button(un RelativeLayout
quedaría bien) y el segundo un contenedor para los ítems de la Toolbar (un componente que extienda de ViewGroup
).
El layout de la actividad quedaría así:
actividad_principal.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.design.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:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context="com.herprogramacion.floatingactionbutton.ActividadPrincipal"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <com.github.fafaldo.fabtoolbar.widget.FABToolbarLayout android:id="@+id/fabtoolbar" android:layout_width="match_parent" android:layout_height="match_parent" app:containerId="@+id/fabtoolbar_container" app:fabId="@+id/fab" app:fabToolbarId="@+id/fabtoolbar_toolbar" app:fadeInFraction="0.2" app:hideDuration="600" app:horizontalMargin="@dimen/margenes_fab" app:showDuration="600" app:verticalMargin="@dimen/margenes_fab"> <include layout="@layout/content_actividad_principal" /> <RelativeLayout android:id="@id/fabtoolbar_container" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.design.widget.FloatingActionButton android:id="@id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/abc_ic_menu_share_mtrl_alpha" app:backgroundTint="@color/azul400" app:borderWidth="0dp" app:fabSize="normal" /> </RelativeLayout> <LinearLayout android:id="@id/fabtoolbar_toolbar" android:layout_width="match_parent" android:layout_height="70dp" android:layout_alignParentBottom="true" android:orientation="horizontal"> <ImageView android:id="@+id/uno" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:scaleType="centerInside" android:src="@drawable/abc_ic_menu_copy_mtrl_am_alpha" /> <ImageView android:id="@+id/dos" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:scaleType="centerInside" android:src="@drawable/abc_ic_menu_paste_mtrl_am_alpha" /> <ImageView android:id="@+id/tres" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:scaleType="centerInside" android:src="@drawable/abc_ic_menu_selectall_mtrl_alpha" /> <ImageView android:id="@+id/cuatro" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:scaleType="centerInside" android:src="@drawable/abc_ic_menu_cut_mtrl_alpha" /> </LinearLayout> </com.github.fafaldo.fabtoolbar.widget.FABToolbarLayout> </android.support.design.widget.CoordinatorLayout>
Como ves, el fab se encierra en un relative layout y los ítems que tendrá la toolbar irán en un linear layout para distribuir por igual los elementos horizontalmente.
La siguiente es una tabla de los atributos más relevantes del FABToolbarLayout
:
Atributo | Descripción |
---|---|
app:containerId |
Referencia al id del contenedor del fab |
app:fabId |
Referencia al id del fab |
app:fabToolbarId |
Referencia al id del view que representa el contenido del toolbar |
app:fadeInFraction |
Porcentaje de desplazamiento del fab en la animación . |
app:hideDuration |
Tiempo en milisegundos que dura la animación de toolbar a fab |
app:showDuration |
Tiempo en milisegundos que dura la animación de fab a toolbar |
app:horizontalMargin|app:verticalMargin |
Márgenes horizontales y verticales del fab con respecto al FABToolbarLayout |
3. Para convertir el fab en toolbar usa el método show()
y en caso contrario usa hide()
. Para evidenciar este comportamiento abre tu actividad principal y consigue la instancia del FABToolbarLayout
, el FAB y los cuatro ítems de selección.
Luego implementa en la actividad la escucha View.OnClickListener()
. Asigna dicha interfaz a los views y procesa el comportamiento en onClick()
estableciendo la ejecución de hide()
para los ítems de la toolbar.
En caso de que sea el fab usa el método show()
.
ActividadPrincipal.java
import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; import com.github.fafaldo.fabtoolbar.widget.FABToolbarLayout; public class ActividadPrincipal extends AppCompatActivity implements View.OnClickListener { private FABToolbarLayout morph; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.actividad_principal); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); morph = (FABToolbarLayout) findViewById(R.id.fabtoolbar); View uno, dos, tres, cuatro; uno = findViewById(R.id.uno); dos = findViewById(R.id.dos); cuatro = findViewById(R.id.cuatro); tres = findViewById(R.id.tres); fab.setOnClickListener(this); uno.setOnClickListener(this); dos.setOnClickListener(this); tres.setOnClickListener(this); cuatro.setOnClickListener(this); } @Override public void onClick(View v) { if (v.getId() == R.id.fab) { morph.show(); } morph.hide(); } }
Floating Action Button con menú
Otro posible comportamiento del fab es la visualización de un menú de mini fabs que promuevan acciones derivadas de la principal. A esto también se le llama «Speed dial».
Para crear esta extensión usaré la librería FloatingActionButton de futuressimple, la cual permite crear un menú desde un fab de forma sencilla.
Veamos cómo hacerlo:
1. Agrega al archivo build.gradle
la dependencia compile com.getbase:floatingactionbutton:1.10.1
de la siguiente forma:
dependencies { ... compile 'com.getbase:floatingactionbutton:1.10.1' }
2. Para crear el menú debes usar el componente personalizado FloatingActionsMenu
, el cual lleva en su interior todos los fabs que serán opciones para el despliegue. El primer hijo aparecerá en la parte superior y le seguirán los demás en orden descendente.
Algunos de los atributos más relevantes para el menú son:
Atributo | Descripción |
---|---|
fab:fab_addButtonSize |
Determina el tamaño del fab de origen del menú. Al igual que la clase original, puedes usar mini y normal . |
fab:fab_addButtonColorNormal |
Determina el color del background del fab para menú |
fab:fab_labelStyle |
Estilo de fondo para las etiquetas que se proyectan en cada opción del menú. Puedes crear una propia en tu archivo values/styles.xml o utilizar la que viene por defecto. |
fab:fab_labelsPosition |
Posición relativa de las etiquetas. Usa left y right para determinar a la izquierda o derecha. |
En los fab subordinados los más elementales son:
Atributo | Descripción |
---|---|
fab:fab_colorNormal |
Color del floating action button |
fab:fab_icon |
Icono para el fab |
fab:fab_size |
Cambia el tamaño del fab |
fab:fab_title |
Texto que saldrá en la etiqueta del fab |
Teniendo esto en cuenta crearemos el siguiente diseño:
Tendremos un menú con tres mini fabs, donde el primero tendrá el color primario y los subordinados el color del acento.
actividad_principal.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:fab="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:fitsSystemWindows="true" tools:context="com.herprogramacion.floatingactionbutton.ActividadPrincipal"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_actividad_principal" /> <com.getbase.floatingactionbutton.FloatingActionsMenu android:id="@+id/menu_fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="end|bottom" android:layout_margin="@dimen/margenes_fab" app:fab_labelStyle="@style/Etiquetas" fab:fab_addButtonColorNormal="?attr/colorPrimary" fab:fab_addButtonSize="normal" fab:fab_labelStyle="@style/Etiquetas" fab:fab_labelsPosition="left"> <com.getbase.floatingactionbutton.FloatingActionButton android:id="@+id/accion_favorito" android:layout_width="wrap_content" android:layout_height="wrap_content" fab:fab_colorNormal="?attr/colorAccent" fab:fab_icon="@drawable/ic_favorito" fab:fab_size="mini" fab:fab_title="Favorito" /> <com.getbase.floatingactionbutton.FloatingActionButton android:id="@+id/accion_buscar" android:layout_width="wrap_content" android:layout_height="wrap_content" fab:fab_colorNormal="?attr/colorAccent" fab:fab_icon="@drawable/ic_buscar" fab:fab_size="mini" fab:fab_title="Buscar" /> <com.getbase.floatingactionbutton.FloatingActionButton android:id="@+id/accion_carrito" android:layout_width="wrap_content" android:layout_height="wrap_content" fab:fab_colorNormal="?attr/colorAccent" fab:fab_icon="@drawable/ic_carro_compra" fab:fab_size="mini" fab:fab_title="Añadir a la cesta" /> </com.getbase.floatingactionbutton.FloatingActionsMenu> </android.support.design.widget.CoordinatorLayout>
Como ves las etiquetas se encuentran a la izquierda y cada elemento tiene su propio icono personalizado.
3. El estilo de las etiquetas se basa en un fondo oscuro semitransparente con un texto de color blanco.
Dentro de styles.xml
<style name="Etiquetas"> <item name="android:background">@drawable/background_label_fab</item> <item name="android:textColor">@color/white</item> </style>
El background es un rectángulo con bordes curvos muy sencillo. Crea una nuevo archivo xml dentro de drawable
, llámalo background_label_fab.xml
y agrega la siguiente definición:
background_label_fab.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/negro_semitransparente"/> <padding android:left="16dp" android:top="4dp" android:right="16dp" android:bottom="4dp"/> <corners android:radius="2dp"/> </shape>
Los colores usados puedes definirlos en tu archivo colors.xml
de la siguiente forma:
colors.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#00796B</color> <color name="colorPrimaryDark">#26A69A</color> <color name="colorAccent">#ef5350</color> <color name="negro_semitransparente">#B2000000</color> <color name="blanco">#fafafa</color> </resources>
El comportamiento para cada acción puedes generarla con una escucha View.OnClickListener
como vimos al inicio. Para que el menú funcione no es necesario realizar la llamada de algún método.
Conclusión
El Floating Action Button es un componente estilizado y consistente para simplificar el acceso a una tarea principal. Su versatilidad permite crear nuevas acciones y verificaciones al cambiar de forma o posición.
Su uso no algo fuera de lo normal, ya que actúa como un botón corriente, sin embargo si deseas transformar su comportamiento es necesario que aprendas a crear animaciones, generar morphings y atar su comportamiento a otros elementos como la Toolbar, Menús contextuales, paneles simples, etc.