Animated Splash Screen in Flutter (and FlutterFlow)
By the Charmotion team · Updated

Flutter's native splash, whether set by hand or with flutter_native_splash, is a still image on a background color. To animate it, keep that splash, make your first route draw the same color and icon position, play a Rive or video intro there, then navigate on. FlutterNativeSplash.preserve() and remove() let you hide the native splash exactly when the intro is ready.
Key takeaways
- flutter_native_splash generates the platform launch screens, which are static; its README says Lottie is not yet supported.
- The animation belongs on your first route, drawn on the same background color as the native splash.
preserve()holds the native splash; callremove()when the intro file has loaded.- A Charmotion
LaunchStateMachinefires no end event, so wait 2.6 s before navigating. - FlutterFlow's splash setting is a still image with a minimum duration; put a RiveAnimation widget on the initial page.
Already have a character or logo? See your character in real app screens: splash, onboarding, loading and empty-state screens. It's free, runs in your browser and uploads nothing.
Can flutter_native_splash be animated?
Not on mobile. The package writes the native launch screens for iOS and Android from your settings (a color, an image, Android 12 options such as image and icon_background_color), and those screens show a still image while the Flutter engine starts. Its README notes that GIFs are now supported on web and that Lotties are not yet supported (pub.dev).
This is not a package limitation you can work around. On iOS, the launch screen can only use standard UIKit views with no code. On Android, only an AnimatedVectorDrawable can animate in the system splash, on Android 12 and later (see the Android splash icon guide). Flutter itself is not running yet in either case.
So the pattern is the same as in native apps: a static splash, then an animated first route that looks identical at its first frame.
How do you build an animated first route in Flutter?
Hold the native splash, load the intro, remove the splash once it can draw, wait for the intro's length, then replace the route. This uses the rive package's current API (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 stands for your real first screen. MediaQuery.disableAnimationsOf reflects the platform's reduce-motion setting (Flutter API); the same flag is read in _run through accessibilityFeatures because there is no context yet in initState.
Can you use a video intro in Flutter instead?
Yes. With video_player (pub.dev), create the controller from an asset, call initialize(), remove the native splash in the then callback, and play. Listen to the controller and navigate when position reaches 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();
The simplest video intro is an opaque clip whose background is exactly your splash color, shown full-bleed or centered on that color. If the icon must sit over a gradient or a photo, a .riv with a transparent background is easier to place than video in Flutter. Video suits detailed 3D or raster icons; it cannot react to state afterwards.
How do you make the splash-to-intro handoff seamless?
- Same color. Use the flutter_native_splash
color(andcolor_darkif you support dark mode) as the intro's background. - Same icon, same place. Use the intro's first frame, or the resting icon, as the splash image, at the size your intro draws it. On Android 12+, the splash icon is clipped to a circle, so test that the cropped icon still matches.
- Remove the splash only when ready. Call
FlutterNativeSplash.remove()after the intro file loads, so the user never sees an empty frame. - Keep the wait honest. About a second is the limit for keeping a user's flow of thought (NN/g). Play a long intro on first launch only, and a short one or none afterwards.
- Respect reduced motion. Show the still icon and continue at once.
How do you add an animated splash in FlutterFlow?
FlutterFlow's splash settings, under App Assets, take an uploaded image with fit and size options, a Min Duration in milliseconds, a Background Color and a Pre-loading Color for the blank screen shown while the engine loads (FlutterFlow Docs). The docs describe no animation option there, so treat it as the static part.
- Set the splash image to your resting icon and both colors to your brand background.
- Keep Min Duration short; it delays every launch.
- Create an intro page as the initial page, with the same background, and place a RiveAnimation widget with your launch file (FlutterFlow Docs).
- On page load, wait 2.6 s (for a Charmotion launch file) and navigate to your home page with no transition.
The Rive in FlutterFlow guide covers state machine names and custom code if you need more control. Charmotion exports the .riv with LaunchStateMachine, plus MP4 and transparent video, from one image of your icon or mascot.
How to add an animated splash screen in Flutter
- Configure a static splash. Use flutter_native_splash with your brand color and the resting icon.
- Preserve it. Call
FlutterNativeSplash.preserve(widgetsBinding: binding)inmain(). - Build the intro route. Make the first route draw the same color, with a
RiveWidgetor video at the splash icon's size and position. - Remove and wait. Call
FlutterNativeSplash.remove()once the file loads, wait the intro's length (2.6 s for a Charmotion launch file), thenpushReplacementto home. - Handle reduced motion. If
disableAnimationsis on, show the still icon and continue immediately.
Frequently asked questions
Can flutter_native_splash play a Lottie animation?
No. Its README says Lotties are not yet supported, and GIF support applies to web only. On iOS and Android the native splash is a still image, so play the Lottie, Rive or video animation in your first route on the same background color.
Should the intro be a route or an overlay in Flutter?
A route is simplest: an IntroPage replaced by the home page with pushReplacement. An overlay on top of the home page works if you want the app to build underneath during the intro, but then make sure the overlay ignores input only while it is visible.
Do I need a separate splash for Android 12 in Flutter?
flutter_native_splash has Android 12 options because the system splash there clips the icon to a circle and can use an icon background color. Provide an image that still reads when cropped, or the package falls back to your launcher icon.
Sources
- flutter_native_splash, pub.dev
- Flutter runtime, Rive
- MediaQuery.disableAnimationsOf, Flutter API
- video_player, pub.dev
- App Assets (splash screen settings), FlutterFlow Docs
- RiveAnimation widget, FlutterFlow Docs
- Response Times: The 3 Important Limits, Nielsen Norman Group