Foundations · Motion

Motion

Cómo se mueve MiCapi. La app se construye en Flutter para Android (90% del tráfico) e iOS — toda la spec usa Duration y Curves nativos, no cubic-bezier de CSS. El motion en fintech acompaña — nunca bloquea ni adorna acciones que mueven dinero.

Android-first · Material 3 Flutter Curves + Duration Reduced motion compatible HapticFeedback nativo
Principios

Cinco reglas que mandan

Si una animación no cumple uno de estos cinco principios, no debe existir. La estética es consecuencia, no objetivo.

01

Propósito antes que estética

Si quitar la animación no pierde información ni orientación, no debe existir.

02

No retrasar acciones de dinero

Confirmar compra ejecuta inmediato. La animación acompaña — el usuario nunca espera a que termine.

03

Respetar reduced motion

Si MediaQuery.disableAnimationsOf(context), reemplazar por crossfade o corte.

04

Consistencia sobre sorpresa

La misma transición para el mismo tipo de acción. No inventamos motion por pantalla.

05

Android-first, iOS-compatible

Baseline Material 3. iOS debe sentirse nativo, pero no diseñamos dos sistemas paralelos.

Sección 1 · Tokens

Duration

Cuatro duraciones canónicas. La barra recorre su duración real, luego se mantiene, retorna y se mantiene — todas comparten el mismo ciclo de 1.8s para que veas la diferencia relativa.

duration-fast
Press feedback · ripple · toggle · checkbox
Duration(
  milliseconds: 100,
)
100ms
100 ms
duration-default
Fade · slide corto · color · crossfade skeleton→real
Duration(
  milliseconds: 200,
)
200ms
200 ms
duration-modal
Bottom sheet enter/exit · modal overlay · drawer
Duration(
  milliseconds: 300,
)
300ms
300 ms
duration-slow
Page transition · chart reveal · list stagger
Duration(
  milliseconds: 350,
)
350ms
350 ms

Las cuatro barras corren sincronizadas en un ciclo de 1800ms. Mismo punto de arranque · velocidades reales.

Sección 2 · Tokens

Easing — Flutter Curves

Tres curvas canónicas, mapeadas directamente a Curves de Flutter. La gráfica muestra tiempo (x) contra progreso (y). Si un patrón necesita una curva custom (spring, overshoot), se define inline — no se agrega al sistema.

ease-standard
Arranca rápido, aterriza suave.
Curva Material 3 estándar.
Curves.fastOutSlowIn
progreso tiempo →
Default para todo: fades, slides, scale, color, page transitions, segmented control.
ease-drawer
Desacelera progresivamente.
Aterrizaje firme.
Curves.easeOutCubic
progreso tiempo →
Bottom sheets subiendo · drawers · snapping a posición abierta.
ease-accelerate
Arranca lento, gana velocidad.
Sale del viewport.
Curves.easeIn
progreso tiempo →
Elementos que desaparecen: dismiss, exit, sheet cerrando.
No usamos CurveTween custom ni curvas con overshoot (spring, elastic, bounceOut). El motion fintech no rebota: en una pantalla de confirmación de compra, un overshoot lee como inestabilidad. Si un patrón necesita una curva no canónica, se justifica caso por caso — nunca como token global.
Sección 3 · Patterns

Motion patterns

Seis patrones canónicos. Cada mockup se renderiza a ancho de Android (360dp) — el formato real en el que vivirán. La barra de estado y la barra de navegación gestual son referencia visual de encuadre, no chrome obligatorio.

9:41
Confirmar compra
Vas a recibir
0,00109 BTC
≈ $500.000 COP
HapticFeedback.lightImpact()
Confirmar compra

aPress feedback + haptic

Al tocar un botón o card, el Material ripple se expande desde el punto de toque (InkWell). En CTAs primarios de acciones financieras, además, el elemento escala 0.97 → 1.0. El haptic acompaña solo si la acción mueve dinero o confirma algo irreversible.

Tokens
duration-fast + ease-standard
Widgets
InkWell / InkResponse para ripple (nativo Material). GestureDetector + AnimatedScale para el scale en CTAs primarios.
Haptic
HapticFeedback.lightImpact() en onTap de acciones confirmatorias (compra, retiro).
Regla. El ripple es suficiente para la mayoría. Solo agregar scale en botones primarios financieros. El haptic nunca en navegación normal.

bPage transition · push / pop

Push — la nueva página entra desde la derecha con fade-in simultáneo. Pop — la actual sale hacia la derecha con fade-out. La bottom nav permanece fija; las transiciones son dentro del body.

Tokens
duration-slow (350ms) + ease-standard
Widgets
PageRouteBuilder con SlideTransition (Tween Offset(1,0) → Offset.zero) + FadeTransition. Para detalle de activo, Hero con tag.
Anti-default
No usar CupertinoPageRoute — es patrón iOS. La misma transición en toda la app.
Regla. Una sola transición de navegación en toda la app. No se inventan transiciones distintas por pantalla. Demo: dashboard ↔ detalle BTC en loop.
9:41
Mis activos
Saldo total
$2.483.500
₿
Bitcoin
$458.320k
Ethereum
$15.420k
Tether
$4.080
Bitcoin
BTC · COP
$458.320.000
+2,4% · 24h
↗
Comprar BTC
→
↘
Vender BTC
→
9:41
Comprar BTC
Vas a pagar
$500.000 COP
Recibes aprox.
0,00109 BTC
Confirmar compra
0,00109 BTC
≈ $500.000 COP
Tasa$458.320.000 / BTC
Comisión1% · 5%
Confirmar
9:41
Comprar BTC
Vas a pagar
$500.000 COP
Recibes aprox.
0,00109 BTC
Confirmar compra
0,00109 BTC
≈ $500.000 COP
Tasa$458.320.000 / BTC
Comisión1% · 5%
Confirmar

cBottom sheet · enter / exit

El sheet sube desde el borde inferior (translateY 100% → 0) con backdrop que oscurece (opacity 0 → 0.5, scrim negro + blur 2px). En MiCapi es el container universal para confirmaciones, selectores de activo, selectores de método de pago y avisos de privacidad.

Enter
duration-modal (300ms) + ease-drawer (Curves.easeOutCubic)
Exit
duration-modal (300ms) + ease-accelerate (Curves.easeIn)
Widget
showModalBottomSheet con RoundedRectangleBorder (top: Radius 16). Para drag: DraggableScrollableSheet.
Regla. Sube con ease-drawer (aterriza), sale con ease-accelerate (se va). La asimetría de easing es intencional. Demo dual: light + dark mode.

dShimmer · skeleton loading

Gradiente lineal translúcido recorre de izquierda a derecha sobre el placeholder gris. Loop continuo de 1500ms con Curves.linear — el recorrido debe sentirse uniforme, sin aceleración. Es la única animación de la app que ignora los duration tokens y usa linear como easing.

Duración
1500ms loop infinito · Curves.linear
Widget
ShaderMask + LinearGradient animado vía AnimationController(1500ms)..repeat(). 3 stops: transparente → blanco 15% opacidad → transparente.
Reduced
Rectángulo estático con AnimatedOpacity pulsante (0.6 ↔ 1.0, 2000ms, ease-standard). Sin movimiento lateral.
Regla. Shimmer en todo placeholder que espera datos async (saldo, lista de transacciones, gráfica). Demo dual: light + dark.
9:41
Mis activos
9:41
Mis activos
9:41
Operar BTC
Comprar
Vender
Quiero pagar
$500.000
≈ 0,00109 BTC · tasa $458.320.000

eSegmented control · pill slide

El indicador (pill con fondo blanco surface) se desliza horizontalmente al segmento seleccionado. El pill viaja; los labels no se mueven. Es el control de elección de modo más usado en MiCapi (Comprar/Vender, BTC/ETH/USDT, COP/Cripto).

Tokens
duration-default (200ms) + ease-standard
Widget
AnimatedAlign con alignment del segmento activo, dentro de Container + DecoratedBox.
Haptic
HapticFeedback.selectionClick() al cambio.
Regla. Para 2–3 opciones cortas (≤10 caracteres por label). Más opciones o labels largos → dropdown o tabs. Demo: Comprar / Vender en loop.

fSheet drag + snap

El bottom sheet sigue el dedo 1:1 durante el drag — sin delay, sin animación, tracking directo. Al soltar: si quedó por encima del 40% del alto, snap a abierto; si por debajo, snap a cerrado. La animación solo aplica al snap posterior — durante el drag el sheet es el dedo.

Tokens (snap)
Snap a abrir: duration-default + ease-drawer. Snap a cerrar: duration-default + ease-accelerate.
Widget
DraggableScrollableSheet con controller, snap: true, snapSizes: [0.0, 0.5, 0.9].
Material 3
En Android se alinea con el patrón de gesture navigation. El usuario ya conoce el gesto.
Regla. El drag debe sentirse instantáneo. Si late, el sheet se siente "atascado" — peor que sin animación.
01 · idle 50%
Confirmar compra
Sheet a media altura
02 · drag ↑
Confirmar compra
Dedo arrastra · sheet sigue 1:1
03 · snap full
Confirmar compra
Soltar > 40% → snap drawer
Sección 4 · Mobile

Haptic feedback

Cuándo vibra el dispositivo. El haptic es feedback táctil — no es decoración, no es premio. Reservado para acciones financieras confirmatorias y para confirmar que algo se copió o seleccionó.

USAR SIEMPRE

Recomendación fuerte

Momento Tipo (Flutter) Por qué
Confirmar compra de cripto HapticFeedback.mediumImpact() Acción financiera irreversible confirmada
Confirmar retiro a banco HapticFeedback.mediumImpact() Acción financiera confirmada
Confirmar retiro a wallet externa HapticFeedback.heavyImpact() Acción irreversible on-chain — el impacto refuerza la finalidad
Copiar dirección · hash · referencia HapticFeedback.lightImpact() Feedback de acción completada
Toggle · switch · segmented control HapticFeedback.selectionClick() Feedback sutil de selección
OPCIONAL

Sujeto a prueba de UX

Momento Tipo (Flutter) Nota
Error de validación (shake del input) HapticFeedback.lightImpact() Puede reforzar el error visual, pero evaluar si se siente excesivo en uso repetido. Probar con usuarios reales antes de fijarlo como regla.

No usar haptic en scroll, navegación normal, aparición de modales, animaciones de carga, ni cambios de estado pasivos (precio actualizándose, balance refrescando). El haptic gratuito desgasta su valor — cuando llega el haptic importante, el usuario ya lo ignora.

Sección 5 · Accesibilidad

Reduced motion

Comparación lado a lado. Cuando MediaQuery.disableAnimationsOf(context) es true, el movimiento se reemplaza por crossfade o corte directo. Los haptics se mantienen — son feedback táctil, no visual.

Motion activo DEFAULT

Page transition
Slide horizontal (350ms) + fade simultáneo.
AB
Bottom sheet enter
Slide up desde el borde inferior (300ms · ease-drawer).
Shimmer
Gradiente recorriendo en loop continuo (1500ms · linear).
Press feedback
Ripple expandiéndose + scale 0.97 (Material InkWell).
Confirmar
Segmented control
Pill se desliza horizontal (200ms · ease-standard).
Comprar
Vender

Reduced motion ACCESIBILIDAD

Page transition
Crossfade de opacidad 200ms. Sin desplazamiento horizontal.
AB
Bottom sheet enter
Fade in 200ms. Sin desplazamiento vertical.
Shimmer
Pulso de opacidad 0.6 ↔ 1.0 cada 2000ms. Sin gradiente lateral.
Press feedback
Solo cambio de opacidad. Sin ripple, sin scale.
Confirmar
Segmented control
Corte directo, sin animación. El pill aparece en el segmento elegido.
Comprar
Vender
Sección 6 · Aplicación

Reglas de aplicación

Cheat sheet final. Si no estás seguro de qué hacer, esta es la regla.

Hacer

  • ✓Usar InkWell con ripple en todo elemento tappable (botones, cards, list tiles).
  • ✓Usar la misma page transition (slide + fade) en toda navegación push/pop.
  • ✓Usar shimmer en todo placeholder que espera datos async.
  • ✓Usar bottom sheet para confirmaciones, selectores y avisos de privacidad.
  • ✓Agregar haptic en acciones financieras confirmatorias y en copiar al portapapeles.
  • ✓Testear siempre con reduced motion activado (Settings → Accessibility → Remove animations en Android).

No hacer

  • ✗No animar el monto en un confirmation sheet — el usuario necesita leerlo, no verlo aparecer.
  • ✗No agregar delays artificiales antes de ejecutar una operación financiera.
  • ✗No usar bounce, spring, overshoot ni elastic — no son apropiados para fintech.
  • ✗No inventar transiciones distintas por pantalla.
  • ✗No animar cambios de precio en tiempo real con transiciones llamativas.
  • ✗No usar CupertinoPageRoute ni transiciones iOS-only como default.