Flutter is a powerful UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase. A key aspect of creating a polished and engaging user experience is the effective use of screen transitions. While Flutter provides default transitions, implementing custom transitions allows you to create unique and branded experiences. This blog post explores how to implement custom transitions between screens in Flutter.
What are Screen Transitions?
Screen transitions are visual animations that occur when navigating from one screen to another. These transitions help to guide the user’s eye and provide a sense of continuity within the application. Instead of abruptly switching between screens, a smooth transition can enhance the user experience significantly.
Why Use Custom Transitions?
- Brand Identity: Reinforce your brand by using unique transition styles.
- User Experience: Create a more engaging and intuitive navigation experience.
- Creative Expression: Implement complex and creative animations tailored to your app’s design.
Implementing Custom Transitions in Flutter
Flutter offers several ways to implement custom transitions between screens. We’ll explore using PageRouteBuilder and the Hero widget.
Method 1: Using PageRouteBuilder
The PageRouteBuilder class allows you to define custom page routes, including specifying custom transition animations. This method provides fine-grained control over the transition animation.
Step 1: Define Your Custom Transition
Create a custom route that extends PageRouteBuilder:
import 'package:flutter/material.dart';
class CustomPageRoute extends PageRouteBuilder {
final Widget child;
final AxisDirection direction;
CustomPageRoute({
required this.child,
this.direction = AxisDirection.left,
}) : super(
transitionDuration: const Duration(milliseconds: 500),
reverseTransitionDuration: const Duration(milliseconds: 500),
pageBuilder: (context, animation, secondaryAnimation) => child,
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
SlideTransition(
position: Tween(
begin: getBeginOffset(direction),
end: Offset.zero,
).animate(animation),
child: child,
),
);
Offset getBeginOffset(AxisDirection direction) {
switch (direction) {
case AxisDirection.up:
return const Offset(0, 1);
case AxisDirection.down:
return const Offset(0, -1);
case AxisDirection.left:
return const Offset(1, 0);
case AxisDirection.right:
return const Offset(-1, 0);
}
}
}
Key components:
transitionDuration: The duration of the transition animation.pageBuilder: Builds the new page being transitioned to.transitionsBuilder: Defines the transition animation usingSlideTransition.
Step 2: Navigate Using the Custom Route
Use Navigator.push with your custom route:
Navigator.push(
context,
CustomPageRoute(child: const SecondScreen(), direction: AxisDirection.left),
);
Example screen:
import 'package:flutter/material.dart';
class FirstScreen extends StatelessWidget {
const FirstScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First Screen'),
),
body: Center(
child: ElevatedButton(
child: const Text('Go to Second Screen'),
onPressed: () {
Navigator.push(
context,
CustomPageRoute(child: const SecondScreen(), direction: AxisDirection.left),
);
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: const Center(
child: Text('This is the second screen.'),
),
);
}
}
void main() {
runApp(
const MaterialApp(
home: FirstScreen(),
),
);
}
In this setup, when you navigate to SecondScreen, a slide transition from the left will occur.
Method 2: Using the Hero Widget
The Hero widget provides a “hero animation” effect, which animates a widget smoothly from one screen to another. This is great for transitioning images or other key UI elements.
Step 1: Wrap Widgets with Hero
Wrap the widget you want to transition in both the origin and destination screens with a Hero widget, ensuring they have the same tag.
import 'package:flutter/material.dart';
class HeroFirstScreen extends StatelessWidget {
const HeroFirstScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Hero First Screen'),
),
body: Center(
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const HeroSecondScreen()),
);
},
child: Hero(
tag: 'hero-image',
child: Image.network(
'https://via.placeholder.com/150',
width: 150,
height: 150,
),
),
),
),
);
}
}
class HeroSecondScreen extends StatelessWidget {
const HeroSecondScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Hero Second Screen'),
),
body: Center(
child: Hero(
tag: 'hero-image',
child: Image.network(
'https://via.placeholder.com/300',
width: 300,
height: 300,
),
),
),
);
}
}
void main() {
runApp(
const MaterialApp(
home: HeroFirstScreen(),
),
);
}
In this example:
- The image is wrapped with
Herowidgets in bothFirstScreenandSecondScreen. - Both
Herowidgets share the sametag(‘hero-image’). - When navigating to
SecondScreen, the image smoothly animates from its position inFirstScreento its position inSecondScreen.
Method 3: Using AnimatedWidget and AnimatedBuilder
For even more fine-grained control over animations, Flutter provides the `AnimatedWidget` and `AnimatedBuilder` classes.
Step 1: Create an Animated Transition Widget
import 'package:flutter/material.dart';
class FadeInRoute extends PageRouteBuilder {
final Widget page;
FadeInRoute({required this.page})
: super(
pageBuilder: (
BuildContext context,
Animation animation,
Animation secondaryAnimation,
) =>
page,
transitionsBuilder: (
BuildContext context,
Animation animation,
Animation secondaryAnimation,
Widget child,
) =>
FadeTransition(
opacity: animation,
child: child,
),
);
}
class AnimatedFirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('First Screen')),
body: Center(
child: ElevatedButton(
child: Text('Go to Second Screen with Fade Transition'),
onPressed: () {
Navigator.of(context).push(FadeInRoute(page: AnimatedSecondScreen()));
},
),
),
);
}
}
class AnimatedSecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Second Screen')),
body: Center(
child: Text('This is the second screen with a fade-in transition.'),
),
);
}
}
void main() {
runApp(
MaterialApp(
home: AnimatedFirstScreen(),
),
);
}
This example demonstrates a fade-in transition between two screens.
Best Practices
- Keep it Subtle: Transitions should enhance, not distract from, the user experience.
- Performance: Optimize animations to ensure smooth performance, especially on lower-end devices.
- Consistency: Maintain consistent transition styles throughout the application for a cohesive feel.
- Accessibility: Be mindful of users with motion sensitivities. Provide options to reduce or disable animations.
Conclusion
Implementing custom transitions between screens in Flutter allows you to create a more engaging, branded, and intuitive user experience. By using PageRouteBuilder, the Hero widget, and custom animation controllers, you can craft unique and polished transitions that elevate your app’s design. Experiment with different animation styles to find what best fits your application’s personality and user expectations.