Ir al contenido
charmotion

Splash screen animado en Flutter (y FlutterFlow)

Por el equipo de Charmotion · Actualizado el

Ilustración de portada de la guía: splash screen animada en Flutter (y FlutterFlow), con personajes de demostración de Charmotion

El splash nativo de Flutter, ya sea configurado a mano o con flutter_native_splash, es una imagen fija sobre un color de fondo. Para animarlo, conserva ese splash, haz que tu primera ruta dibuje el mismo color y la misma posición del ícono, reproduce ahí una intro de Rive o en video y luego navega a la siguiente pantalla. FlutterNativeSplash.preserve() y remove() te permiten ocultar el splash nativo justo cuando la intro está lista.

Puntos clave

  • flutter_native_splash genera las pantallas de inicio de cada plataforma, que son estáticas; su README indica que Lottie todavía no es compatible.
  • La animación va en tu primera ruta, dibujada sobre el mismo color de fondo que el splash nativo.
  • preserve() retiene el splash nativo; llama a remove() cuando el archivo de la intro se haya cargado.
  • Un LaunchStateMachine de Charmotion no dispara ningún evento de fin, así que espera 2.6 s antes de navegar.
  • La configuración de splash de FlutterFlow es una imagen fija con una duración mínima; coloca un widget RiveAnimation en la página inicial.

¿Ya tienes un personaje o logo? Mira tu personaje en pantallas reales de una app: pantallas de splash, onboarding, carga y estado vacío. Es gratis, funciona en tu navegador y no sube nada.

¿Se puede animar flutter_native_splash?

No en móviles. El paquete genera las pantallas de inicio nativas de iOS y Android a partir de tu configuración (un color, una imagen, opciones de Android 12 como image y icon_background_color), y esas pantallas muestran una imagen fija mientras arranca el motor de Flutter. Su README indica que los GIF ya son compatibles en web y que los Lottie todavía no lo son (pub.dev).

No es una limitación del paquete que puedas sortear. En iOS, la pantalla de inicio solo puede usar vistas estándar de UIKit sin código. En Android, solo un AnimatedVectorDrawable puede animarse en el splash del sistema, en Android 12 y posteriores (consulta la guía del ícono de splash en Android). En ambos casos, Flutter todavía no se está ejecutando.

Por eso el patrón es el mismo que en las apps nativas: un splash estático y luego una primera ruta animada que se ve idéntica en su primer fotograma.

¿Cómo se crea una primera ruta animada en Flutter?

Mantén el splash nativo, carga la intro, quita el splash cuando ya pueda dibujarse, espera lo que dura la intro y reemplaza la ruta. Esto usa la API actual del paquete rive (Rive):

import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:rive/rive.dart';

const bg = Color(0xFF0E1116); // same color as the native splash

Future<void> main() async {
  final binding = WidgetsFlutterBinding.ensureInitialized();
  FlutterNativeSplash.preserve(widgetsBinding: binding);
  await RiveNative.init();
  runApp(const MaterialApp(home: IntroPage()));
}

class IntroPage extends StatefulWidget {
  const IntroPage({super.key});
  @override
  State<IntroPage> createState() => _IntroPageState();
}

class _IntroPageState extends State<IntroPage> {
  File? file;
  RiveWidgetController? controller;

  @override
  void initState() {
    super.initState();
    _run();
  }

  Future<void> _run() async {
    final reduce = WidgetsBinding
        .instance.platformDispatcher.accessibilityFeatures.disableAnimations;
    if (!reduce) {
      file = await File.asset('assets/launch.riv', riveFactory: Factory.rive);
      if (file != null && mounted) {
        setState(() => controller = RiveWidgetController(
              file!,
              stateMachineSelector: StateMachineSelector.byName('LaunchStateMachine'),
            ));
      }
    }
    FlutterNativeSplash.remove();
    // LaunchStateMachine fires no end event: wait for the 2.6 s intro.
    if (controller != null) await Future.delayed(const Duration(milliseconds: 2600));
    if (mounted) {
      Navigator.of(context).pushReplacement(
        MaterialPageRoute(builder: (_) => const HomePage()),
      );
    }
  }

  @override
  void dispose() {
    controller?.dispose();
    file?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final still = MediaQuery.disableAnimationsOf(context) || controller == null;
    return ColoredBox(
      color: bg,
      child: Center(
        child: SizedBox(
          width: 160,
          height: 160,
          child: still
              ? Image.asset('assets/icon_rest.png')
              : RiveWidget(controller: controller!, fit: Fit.contain),
        ),
      ),
    );
  }
}

HomePage representa tu primera pantalla real. MediaQuery.disableAnimationsOf refleja la configuración de movimiento reducido de la plataforma (Flutter API); el mismo indicador se lee en _run mediante accessibilityFeatures porque todavía no hay contexto en initState.

¿Puedo usar una intro en video en Flutter?

Sí. Con video_player (pub.dev), crea el controlador a partir de un asset, llama a initialize(), quita el splash nativo en el callback then y reproduce. Escucha el controlador y navega cuando position llegue a duration.

final video = VideoPlayerController.asset('assets/launch.mp4');
await video.initialize();
FlutterNativeSplash.remove();
video.addListener(() {
  final v = video.value;
  if (v.isInitialized && v.position >= v.duration) _goHome();
});
await video.setVolume(0);
await video.play();

La intro en video más sencilla es un clip opaco cuyo fondo sea exactamente el color de tu splash, mostrado a pantalla completa o centrado sobre ese color. Si el ícono debe quedar sobre un degradado o una foto, en Flutter es más fácil colocar un .riv con fondo transparente que un video. El video sirve para íconos 3D o rasterizados con mucho detalle, pero después no puede reaccionar al estado.

¿Cómo se logra una transición imperceptible del splash a la intro?

  • Mismo color. Usa el color de flutter_native_splash (y color_dark si admites modo oscuro) como fondo de la intro.
  • Mismo ícono, mismo lugar. Usa el primer fotograma de la intro, o el ícono en reposo, como imagen del splash, al tamaño con que la intro lo dibuja. En Android 12 y posteriores, el ícono del splash se recorta en un círculo, así que comprueba que el ícono recortado siga coincidiendo.
  • Quita el splash solo cuando esté listo. Llama a FlutterNativeSplash.remove() después de que cargue el archivo de la intro, para que el usuario nunca vea un fotograma vacío.
  • Que la espera sea justa. Alrededor de un segundo es el límite para no interrumpir el hilo de pensamiento del usuario (NN/g). Reproduce una intro larga solo en el primer inicio, y después una corta o ninguna.
  • Respeta el movimiento reducido. Muestra el ícono fijo y continúa de inmediato.

¿Cómo se agrega un splash animado en FlutterFlow?

La configuración de splash de FlutterFlow, en App Assets, acepta una imagen subida con opciones de ajuste y tamaño, una Min Duration en milisegundos, un Background Color y un Pre-loading Color para la pantalla en blanco que se muestra mientras carga el motor (FlutterFlow Docs). La documentación no describe ninguna opción de animación ahí, así que tómala como la parte estática.

  1. Usa tu ícono en reposo como imagen del splash y el fondo de tu marca en ambos colores.
  2. Mantén corta la Min Duration: retrasa cada inicio.
  3. Crea una página de intro como página inicial, con el mismo fondo, y coloca un widget RiveAnimation con tu archivo de inicio (FlutterFlow Docs).
  4. Al cargar la página, espera 2.6 s (para un archivo de inicio de Charmotion) y navega a tu página principal sin transición.

La guía de Rive en FlutterFlow explica los nombres de las máquinas de estados y el código personalizado si necesitas más control. Charmotion exporta el .riv con LaunchStateMachine, además de MP4 y video con fondo transparente, a partir de una sola imagen de tu ícono o mascota de marca.

Cómo agregar una splash screen animada en Flutter

  1. Configura un splash estático. Usa flutter_native_splash con el color de tu marca y el ícono en reposo.
  2. Mantenlo visible. Llama a FlutterNativeSplash.preserve(widgetsBinding: binding) en main().
  3. Crea la ruta de la intro. Haz que la primera ruta dibuje el mismo color, con un RiveWidget o un video al tamaño y en la posición del ícono del splash.
  4. Quítalo y espera. Llama a FlutterNativeSplash.remove() cuando cargue el archivo, espera lo que dura la intro (2.6 s para un archivo de inicio de Charmotion) y luego usa pushReplacement para ir a home.
  5. Gestiona el movimiento reducido. Si disableAnimations está activado, muestra el ícono fijo y continúa de inmediato.

Preguntas frecuentes

¿flutter_native_splash puede reproducir una animación Lottie?

No. Su README dice que los Lottie todavía no son compatibles, y la compatibilidad con GIF solo aplica a web. En iOS y Android el splash nativo es una imagen fija, así que reproduce la animación Lottie, Rive o en video en tu primera ruta, sobre el mismo color de fondo.

¿La intro debe ser una ruta o un overlay en Flutter?

Una ruta es lo más sencillo: una IntroPage que se reemplaza por la página principal con pushReplacement. Un overlay sobre la página principal funciona si quieres que la app se construya debajo durante la intro, pero asegúrate de que el overlay ignore las entradas solo mientras está visible.

¿Necesito un splash aparte para Android 12 en Flutter?

flutter_native_splash tiene opciones para Android 12 porque allí el splash del sistema recorta el ícono en un círculo y puede usar un color de fondo para el ícono. Proporciona una imagen que se siga entendiendo al recortarse; si no, el paquete usa tu ícono de la app.

Fuentes

Guías relacionadas