Question #354HardNavigation & Routing

How do you implement a custom transition between screens in flutter?

#navigation#animation#transitions#page-route

Answer

Overview

Custom page transitions in Flutter allow you to create engaging navigation animations beyond the default platform transitions. Flutter provides

text
PageRouteBuilder
and various transition widgets to implement custom animations when navigating between screens.

Core Components

  1. PageRouteBuilder - Builds custom route transitions
  2. Animation - Controls animation progress (0.0 to 1.0)
  3. Tween - Defines animation start and end values
  4. Curve - Defines animation timing (easing)
  5. Transition Widgets - Apply animations (SlideTransition, FadeTransition, etc.)

Method 1: Using PageRouteBuilder

Basic Structure

dart
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      // Define your custom transition here
      return child; // Replace with animated widget
    },
  ),
);

Animation Parameters

  • animation - Primary animation (0.0 → 1.0 when pushing, 1.0 → 0.0 when popping)
  • secondaryAnimation - Animation of route below (for exit animations)
  • child - The destination screen (pageBuilder result)

Common Transition Types

1. Slide Transition

dart
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      const begin = Offset(1.0, 0.0); // Start from right
      const end = Offset.zero; // End at current position
      const curve = Curves.easeInOut;

      var tween = Tween(begin: begin, end: end).chain(
        CurveTween(curve: curve),
      );

      return SlideTransition(
        position: animation.drive(tween),
        child: child,
      );
    },
  ),
);

Slide Directions

dart
// Slide from right
const begin = Offset(1.0, 0.0);

// Slide from left
const begin = Offset(-1.0, 0.0);

// Slide from bottom
const begin = Offset(0.0, 1.0);

// Slide from top
const begin = Offset(0.0, -1.0);

// Diagonal slide (bottom-right)
const begin = Offset(1.0, 1.0);

2. Fade Transition

dart
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return FadeTransition(
        opacity: animation,
        child: child,
      );
    },
  ),
);

3. Scale Transition

dart
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return ScaleTransition(
        scale: animation,
        child: child,
      );
    },
  ),
);

4. Rotation Transition

dart
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return RotationTransition(
        turns: animation,
        child: child,
      );
    },
  ),
);

5. Combined Transitions

dart
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      const begin = Offset(0.0, 1.0);
      const end = Offset.zero;
      const curve = Curves.easeInOut;

      var slideTween = Tween(begin: begin, end: end).chain(
        CurveTween(curve: curve),
      );

      return SlideTransition(
        position: animation.drive(slideTween),
        child: FadeTransition(
          opacity: animation,
          child: child,
        ),
      );
    },
  ),
);

Advanced Transitions

1. Custom Duration

dart
Navigator.push(
  context,
  PageRouteBuilder(
    transitionDuration: Duration(milliseconds: 800),
    reverseTransitionDuration: Duration(milliseconds: 400),
    pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return FadeTransition(opacity: animation, child: child);
    },
  ),
);

2. Shared Axis Transition (Material Design)

dart
import 'package:animations/animations.dart';

Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return SharedAxisTransition(
        animation: animation,
        secondaryAnimation: secondaryAnimation,
        transitionType: SharedAxisTransitionType.horizontal,
        child: child,
      );
    },
  ),
);

3. Size Transition (Expandable)

dart
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return Align(
        child: SizeTransition(
          sizeFactor: animation,
          child: child,
        ),
      );
    },
  ),
);

4. Custom Curved Animation

dart
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      var curvedAnimation = CurvedAnimation(
        parent: animation,
        curve: Curves.elasticOut,
        reverseCurve: Curves.easeIn,
      );

      return ScaleTransition(
        scale: curvedAnimation,
        child: child,
      );
    },
  ),
);

Method 2: Custom PageRoute Class

Create reusable transition classes:

dart
class SlideRightRoute extends PageRouteBuilder {
  final Widget page;
  
  SlideRightRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            const begin = Offset(1.0, 0.0);
            const end = Offset.zero;
            const curve = Curves.easeInOut;

            var tween = Tween(begin: begin, end: end);
            var curvedAnimation = CurvedAnimation(
              parent: animation,
              curve: curve,
            );

            return SlideTransition(
              position: tween.animate(curvedAnimation),
              child: child,
            );
          },
        );
}

// Usage
Navigator.push(
  context,
  SlideRightRoute(page: DetailScreen()),
);

More Custom Route Classes

dart
// Fade Route
class FadeRoute extends PageRouteBuilder {
  final Widget page;
  
  FadeRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            return FadeTransition(opacity: animation, child: child);
          },
        );
}

// Scale Rotate Route
class ScaleRotateRoute extends PageRouteBuilder {
  final Widget page;
  
  ScaleRotateRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            return ScaleTransition(
              scale: Tween<double>(begin: 0.0, end: 1.0).animate(
                CurvedAnimation(
                  parent: animation,
                  curve: Curves.fastOutSlowIn,
                ),
              ),
              child: RotationTransition(
                turns: Tween<double>(begin: 0.0, end: 1.0).animate(
                  CurvedAnimation(
                    parent: animation,
                    curve: Curves.linear,
                  ),
                ),
                child: child,
              ),
            );
          },
        );
}

Method 3: Using go_router

dart
import 'package:go_router/go_router.dart';

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => HomeScreen(),
    ),
    GoRoute(
      path: '/details',
      pageBuilder: (context, state) => CustomTransitionPage(
        child: DetailScreen(),
        transitionsBuilder: (context, animation, secondaryAnimation, child) {
          const begin = Offset(1.0, 0.0);
          const end = Offset.zero;
          const curve = Curves.easeInOut;

          var tween = Tween(begin: begin, end: end).chain(
            CurveTween(curve: curve),
          );

          return SlideTransition(
            position: animation.drive(tween),
            child: child,
          );
        },
      ),
    ),
  ],
);

Complete Example with Multiple Transitions

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Custom Transitions Demo',
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Custom Transitions')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () => Navigator.push(
                context,
                SlideRightRoute(page: DetailScreen(title: 'Slide Right')),
              ),
              child: Text('Slide Right'),
            ),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () => Navigator.push(
                context,
                FadeRoute(page: DetailScreen(title: 'Fade')),
              ),
              child: Text('Fade'),
            ),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () => Navigator.push(
                context,
                ScaleRoute(page: DetailScreen(title: 'Scale')),
              ),
              child: Text('Scale'),
            ),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () => Navigator.push(
                context,
                CombinedRoute(page: DetailScreen(title: 'Combined')),
              ),
              child: Text('Slide + Fade'),
            ),
          ],
        ),
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  final String title;

  const DetailScreen({Key? key, required this.title}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(
        child: ElevatedButton(
          onPressed: () => Navigator.pop(context),
          child: Text('Go Back'),
        ),
      ),
    );
  }
}

// Custom Route Classes
class SlideRightRoute extends PageRouteBuilder {
  final Widget page;
  
  SlideRightRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            const begin = Offset(1.0, 0.0);
            const end = Offset.zero;
            const curve = Curves.easeInOut;

            var tween = Tween(begin: begin, end: end).chain(
              CurveTween(curve: curve),
            );

            return SlideTransition(
              position: animation.drive(tween),
              child: child,
            );
          },
        );
}

class FadeRoute extends PageRouteBuilder {
  final Widget page;
  
  FadeRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            return FadeTransition(opacity: animation, child: child);
          },
        );
}

class ScaleRoute extends PageRouteBuilder {
  final Widget page;
  
  ScaleRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            return ScaleTransition(
              scale: Tween<double>(begin: 0.0, end: 1.0).animate(
                CurvedAnimation(
                  parent: animation,
                  curve: Curves.fastOutSlowIn,
                ),
              ),
              child: child,
            );
          },
        );
}

class CombinedRoute extends PageRouteBuilder {
  final Widget page;
  
  CombinedRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            const begin = Offset(0.0, 1.0);
            const end = Offset.zero;
            const curve = Curves.easeInOut;

            var tween = Tween(begin: begin, end: end).chain(
              CurveTween(curve: curve),
            );

            return SlideTransition(
              position: animation.drive(tween),
              child: FadeTransition(
                opacity: animation,
                child: child,
              ),
            );
          },
        );
}

Available Curves

dart
// Common curves
Curves.linear          // Constant speed
Curves.easeIn          // Slow start
Curves.easeOut         // Slow end
Curves.easeInOut       // Slow start and end
Curves.fastOutSlowIn   // Material Design standard
Curves.bounceIn        // Bounce at start
Curves.bounceOut       // Bounce at end
Curves.elasticIn       // Elastic/spring start
Curves.elasticOut      // Elastic/spring end

Best Practices

  • Keep duration reasonable (200-500ms for most transitions)
  • Use platform-appropriate transitions (slide for iOS, fade for Android)
  • Don't overuse complex animations - they can slow UX
  • Match transition direction to user gesture (swipe right = slide left)
  • Test on real devices - animations can lag on slower phones
  • Reuse route classes instead of duplicating transition code
  • Consider accessibility - some users may prefer reduced motion

Common Use Cases

TransitionBest For
SlideModal sheets, detail views
FadeOverlays, dialogs
ScaleExpanding cards, zoom details
RotationPlayful interactions
CombinedPremium feel, onboarding

Official Documentation