Pular para o conteúdo
charmotion

Splash screen animada no Flutter (e FlutterFlow)

Por equipe Charmotion · Atualizado em

Ilustração de capa do guia: splash screen animada no Flutter (e no FlutterFlow), com personagens de demonstração do Charmotion

A splash nativa do Flutter, configurada à mão ou com flutter_native_splash, é uma imagem estática sobre uma cor de fundo. Para animá-la, mantenha essa splash, faça a sua primeira rota desenhar a mesma cor e a mesma posição do ícone, toque ali uma intro em Rive ou vídeo e depois navegue adiante. FlutterNativeSplash.preserve() e remove() permitem esconder a splash nativa exatamente quando a intro estiver pronta.

Principais pontos

  • O flutter_native_splash gera as launch screens de cada plataforma, que são estáticas; o README dele diz que Lottie ainda não é suportado.
  • A animação fica na sua primeira rota, desenhada sobre a mesma cor de fundo da splash nativa.
  • preserve() mantém a splash nativa; chame remove() quando o arquivo da intro tiver carregado.
  • Uma LaunchStateMachine do Charmotion não dispara nenhum evento de fim, então espere 2,6 s antes de navegar.
  • A configuração de splash do FlutterFlow é uma imagem estática com duração mínima; coloque um widget RiveAnimation na página inicial.

Já tem um personagem ou logo? Veja seu personagem em telas reais de app: telas de splash, onboarding, carregamento e estado vazio. É grátis, roda no navegador e não envia nada.

O flutter_native_splash pode ser animado?

Não no mobile. O pacote gera as launch screens nativas para iOS e Android a partir das suas configurações (uma cor, uma imagem, opções do Android 12 como image e icon_background_color), e essas telas mostram uma imagem estática enquanto a engine do Flutter inicia. O README informa que GIFs agora são suportados na web e que Lotties ainda não são suportados (pub.dev).

Essa não é uma limitação do pacote que dê para contornar. No iOS, a launch screen só pode usar views padrão do UIKit, sem código. No Android, apenas um AnimatedVectorDrawable pode ser animado na splash do sistema, no Android 12 ou posterior (veja o guia do ícone de splash no Android). Em nenhum dos casos o Flutter já está rodando.

Então o padrão é o mesmo dos apps nativos: uma splash estática e, em seguida, uma primeira rota animada que fica idêntica a ela no primeiro frame.

Como criar uma primeira rota animada no Flutter?

Segure a splash nativa, carregue a intro, remova a splash assim que a intro puder ser desenhada, espere a duração da intro e então substitua a rota. Este exemplo usa a API atual do pacote 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 a sua primeira tela real. MediaQuery.disableAnimationsOf reflete a configuração de movimento reduzido da plataforma (Flutter API); a mesma flag é lida em _run por meio de accessibilityFeatures, porque ainda não existe context em initState.

Dá para usar uma intro em vídeo no Flutter?

Sim. Com video_player (pub.dev), crie o controller a partir de um asset, chame initialize(), remova a splash nativa no callback then e dê play. Escute o controller e navegue quando position chegar 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();

A intro em vídeo mais simples é um clipe opaco cujo fundo é exatamente a cor da sua splash, exibido em tela cheia ou centralizado sobre essa cor. Se o ícone precisar ficar sobre um gradiente ou uma foto, um .riv com fundo transparente é mais fácil de posicionar no Flutter do que um vídeo. Vídeo funciona bem para ícones 3D ou raster detalhados, mas não consegue reagir a estados depois.

Como fazer uma transição perfeita da splash para a intro?

  • Mesma cor. Use a color do flutter_native_splash (e color_dark, se você oferece modo escuro) como fundo da intro.
  • Mesmo ícone, mesmo lugar. Use o primeiro frame da intro, ou o ícone em repouso, como imagem da splash, no tamanho em que a intro o desenha. No Android 12+, o ícone da splash é recortado em círculo, então teste se o ícone recortado continua igual.
  • Remova a splash só quando estiver pronto. Chame FlutterNativeSplash.remove() depois que o arquivo da intro carregar, para que o usuário nunca veja um frame vazio.
  • Não faça o usuário esperar à toa. Cerca de um segundo é o limite para não interromper a linha de raciocínio do usuário (NN/g). Toque uma intro longa só na primeira abertura e, depois, uma curta ou nenhuma.
  • Respeite o movimento reduzido. Mostre o ícone estático e siga em frente na hora.

Como adicionar uma splash animada no FlutterFlow?

As configurações de splash do FlutterFlow, em App Assets, aceitam uma imagem enviada com opções de ajuste e tamanho, uma Min Duration em milissegundos, uma Background Color e uma Pre-loading Color para a tela em branco exibida enquanto a engine carrega (FlutterFlow Docs). A documentação não descreve nenhuma opção de animação ali, então trate essa tela como a parte estática.

  1. Defina a imagem da splash como o seu ícone em repouso e as duas cores como o fundo da sua marca.
  2. Mantenha a Min Duration curta; ela atrasa todas as aberturas.
  3. Crie uma página de intro como página inicial, com o mesmo fundo, e coloque um widget RiveAnimation com o seu arquivo de abertura (FlutterFlow Docs).
  4. No carregamento da página, espere 2,6 s (para um arquivo de abertura do Charmotion) e navegue para a sua página inicial sem transição.

O guia de Rive no FlutterFlow explica os nomes de state machine e o código personalizado, caso você precise de mais controle. O Charmotion exporta o .riv com LaunchStateMachine, além de MP4 e vídeo com fundo transparente, a partir de uma única imagem do seu ícone ou mascote.

Como adicionar uma splash screen animada no Flutter

  1. Configure uma splash estática. Use o flutter_native_splash com a cor da sua marca e o ícone em repouso.
  2. Mantenha a splash. Chame FlutterNativeSplash.preserve(widgetsBinding: binding) em main().
  3. Crie a rota da intro. Faça a primeira rota desenhar a mesma cor, com um RiveWidget ou vídeo no tamanho e na posição do ícone da splash.
  4. Remova e espere. Chame FlutterNativeSplash.remove() assim que o arquivo carregar, espere a duração da intro (2,6 s para um arquivo de abertura do Charmotion) e depois use pushReplacement para ir à home.
  5. Trate o movimento reduzido. Se disableAnimations estiver ativo, mostre o ícone estático e siga em frente imediatamente.

Perguntas frequentes

O flutter_native_splash consegue tocar uma animação Lottie?

Não. O README diz que Lotties ainda não são suportados, e o suporte a GIF vale só para a web. No iOS e no Android, a splash nativa é uma imagem estática, então toque a animação Lottie, Rive ou em vídeo na sua primeira rota, sobre a mesma cor de fundo.

No Flutter, a intro deve ser uma rota ou um overlay?

Uma rota é o mais simples: uma IntroPage substituída pela página inicial com pushReplacement. Um overlay sobre a página inicial funciona se você quiser que o app seja construído por baixo durante a intro, mas aí garanta que o overlay ignore o input só enquanto estiver visível.

Preciso de uma splash separada para o Android 12 no Flutter?

O flutter_native_splash tem opções para o Android 12 porque, nessa versão, a splash do sistema recorta o ícone em círculo e pode usar uma cor de fundo para o ícone. Forneça uma imagem que continue legível depois do recorte; caso contrário, o pacote usa o ícone do launcher.

Fontes

Guias relacionados