RippleRevealButton class

Botón animado con efecto ripple/circular reveal que alterna entre dos estados.

Genera un círculo que se expande desde el punto del toque hasta cubrir el área del botón, creando una transición suave entre dos contenidos y dos esquemas de color.

Ideal para: toggles, switches personalizados, botones de favorito/like, modo claro/oscuro, y cualquier control que requiera feedback visual claro.

Cómo funciona

  • El fondo alterna entre selectedBackgroundColor y unselectedBackgroundColor.
  • El ripple dibuja un círculo del color correspondiente al estado destino.
  • El contenido alterna entre selectedChild (seleccionado) y unselectedChild (no seleccionado).

Anatomía del efecto

Estado inicial: NO SELECCIONADO
┌─────────────────────┐
│  unselectedChild    │  ← Fondo: unselectedBackgroundColor
└─────────────────────┘

Al tocar:
┌─────────────────────┐
│    ●────────────     │  ← Círculo expandiéndose (selectedRippleColor)
└─────────────────────┘

Estado final: SELECCIONADO
┌─────────────────────┐
│   selectedChild     │  ← Fondo: selectedBackgroundColor
└─────────────────────┘

Buenas prácticas

  • Usa colores contrastantes entre fondo y ripple para maximizar el impacto visual.
  • Asegúrate de que selectedChild y unselectedChild sean visualmente distintos.
  • Para esquinas suaves, incrementa borderRadius (mínimo 2.0).
  • Si controlas el estado externamente, usa isSelected y actualízalo con setState.

Ejemplos

Ejemplo básico (toggle ON/OFF)

RippleRevealButton(
  selectedChild: const Text(
    'ON',
    style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
  ),
  unselectedChild: const Text(
    'OFF',
    style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
  ),
  selectedBackgroundColor: Colors.black,
  unselectedBackgroundColor: Colors.white,
  selectedRippleColor: Colors.black,
  unselectedRippleColor: Colors.white,
  onPressed: () => debugPrint('¡Alternado!'),
)

Ejemplo con iconos (favorito)

RippleRevealButton(
  selectedChild: const Icon(Icons.favorite, color: Colors.white),
  unselectedChild: const Icon(Icons.favorite_border, color: Colors.red),
  selectedBackgroundColor: Colors.red,
  unselectedBackgroundColor: Colors.grey.shade100,
  selectedRippleColor: Colors.red,
  unselectedRippleColor: Colors.grey.shade300,
  height: 56,
  width: 56,
  borderRadius: 28,
  onPressed: () => print('Favorito alternado'),
)

Ejemplo con control externo

class MyToggle extends StatefulWidget {
  const MyToggle({super.key});

  @override
  State<MyToggle> createState() => _MyToggleState();
}

class _MyToggleState extends State<MyToggle> {
  bool _isActive = false;

  @override
  Widget build(BuildContext context) {
    return RippleRevealButton(
      isSelected: _isActive,
      selectedChild: const Text('Activo'),
      unselectedChild: const Text('Inactivo'),
      selectedBackgroundColor: Colors.blue,
      unselectedBackgroundColor: Colors.grey.shade200,
      selectedRippleColor: Colors.blue,
      unselectedRippleColor: Colors.grey,
      onPressed: () => setState(() => _isActive = !_isActive),
    );
  }
}

Ejemplo con borde decorativo

RippleRevealButton(
  selectedChild: const Text(
    'Premium',
    style: TextStyle(color: Colors.amber, fontWeight: FontWeight.bold),
  ),
  unselectedChild: const Text('Básico'),
  selectedBackgroundColor: Colors.black,
  unselectedBackgroundColor: Colors.white,
  selectedRippleColor: Colors.amber,
  unselectedRippleColor: Colors.black,
  border: Border.all(color: Colors.amber, width: 2),
  borderRadius: 16,
  height: 56,
  onPressed: () => print('Plan cambiado'),
)

Notas técnicas

  • El cursor se establece automáticamente a click en plataformas desktop.
  • El ripple se origina en las coordenadas exactas del tap.
  • animationDuration controla la velocidad de la expansión circular.
  • El widget es completamente responsivo y se adapta al tamaño disponible.
Inheritance

Constructors

RippleRevealButton({Key? key, required Widget selectedChild, Widget? unselectedChild, required VoidCallback onPressed, Color? selectedBackgroundColor, Color? unselectedBackgroundColor, Color? selectedRippleColor, Color? unselectedRippleColor, double width = double.infinity, double height = 48, double? borderRadius, Duration? animationDuration, bool? isSelected, BoxBorder? border, EdgeInsetsGeometry padding = const EdgeInsets.symmetric(horizontal: 16), AlignmentGeometry alignment = Alignment.center, @Deprecated('Use selectedChild instead. Will be removed in v2.0.0') Widget? widgetA, @Deprecated('Use unselectedChild instead. Will be removed in v2.0.0') Widget? widgetB, @Deprecated('Use selectedBackgroundColor instead. Will be removed in v2.0.0') Color? backgroundColorA, @Deprecated('Use unselectedBackgroundColor instead. Will be removed in v2.0.0') Color? backgroundColorB, @Deprecated('Use selectedRippleColor instead. Will be removed in v2.0.0') Color? rippleColorA, @Deprecated('Use unselectedRippleColor instead. Will be removed in v2.0.0') Color? rippleColorB, @Deprecated('Use borderRadius instead. Will be removed in v2.0.0') double? radius, @Deprecated('Use animationDuration instead. Will be removed in v2.0.0') Duration? duration, @Deprecated('Use isSelected instead. Will be removed in v2.0.0') bool? selected})
Crea un RippleRevealButton con efecto de ripple circular.
const

Properties

alignment AlignmentGeometry
Alineación del contenido dentro del botón.
final
animationDuration Duration
Duración de la animación del efecto ripple.
final
border BoxBorder?
Borde decorativo opcional aplicado al contorno del botón.
final
borderRadius double
Radio de redondeo de las esquinas del botón.
final
hashCode int
The hash code for this object.
no setterinherited
height double
Alto del botón en píxeles lógicos.
final
isSelected bool?
Controla el estado del botón externamente (opcional).
final
key Key?
Controls how one widget replaces another widget in the tree.
finalinherited
onPressed VoidCallback
Callback ejecutado al presionar el botón, antes de cambiar el estado.
final
padding EdgeInsetsGeometry
Espaciado interno entre el borde del botón y su contenido.
final
runtimeType Type
A representation of the runtime type of the object.
no setterinherited
selectedBackgroundColor Color
Color de fondo cuando el botón está seleccionado.
final
selectedChild Widget
Widget mostrado cuando el botón está en estado seleccionado.
final
selectedRippleColor Color
Color del círculo de ripple al transicionar hacia el estado seleccionado.
final
unselectedBackgroundColor Color
Color de fondo cuando el botón está no seleccionado.
final
unselectedChild Widget?
Widget mostrado cuando el botón está en estado no seleccionado.
final
unselectedRippleColor Color
Color del círculo de ripple al transicionar hacia el estado no seleccionado.
final
width double
Ancho del botón en píxeles lógicos.
final

Methods

createElement() StatefulElement
Creates a StatefulElement to manage this widget's location in the tree.
inherited
createState() State<RippleRevealButton>
Creates the mutable state for this widget at a given location in the tree.
override
debugDescribeChildren() List<DiagnosticsNode>
Returns a list of DiagnosticsNode objects describing this node's children.
inherited
debugFillProperties(DiagnosticPropertiesBuilder properties) → void
Add additional properties associated with the node.
inherited
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
toDiagnosticsNode({String? name, DiagnosticsTreeStyle? style}) DiagnosticsNode
Returns a debug representation of the object that is used by debugging tools and by DiagnosticsNode.toStringDeep.
inherited
toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) String
A string representation of this object.
inherited
toStringDeep({String prefixLineOne = '', String? prefixOtherLines, DiagnosticLevel minLevel = DiagnosticLevel.debug, int wrapWidth = 65}) String
Returns a string representation of this node and its descendants.
inherited
toStringShallow({String joiner = ', ', DiagnosticLevel minLevel = DiagnosticLevel.debug}) String
Returns a one-line detailed description of the object.
inherited
toStringShort() String
A short, textual description of this widget.
inherited

Operators

operator ==(Object other) bool
The equality operator.
inherited