blob: 12314141b7222e784f25f206c72ccdbc2041996c [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
/// Determines which type of shared axis transition is used.
enum SharedAxisTransitionType {
/// Creates a shared axis vertical (y-axis) page transition.
vertical,
/// Creates a shared axis horizontal (x-axis) page transition.
horizontal,
/// Creates a shared axis scaled (z-axis) page transition.
scaled,
}
/// Used by [PageTransitionsTheme] to define a page route transition animation
/// in which outgoing and incoming elements share a fade transition.
///
/// The shared axis pattern provides the transition animation between UI elements
/// that have a spatial or navigational relationship. For example,
/// transitioning from one page of a sign up page to the next one.
///
/// The following example shows how the SharedAxisPageTransitionsBuilder can
/// be used in a [PageTransitionsTheme] to change the default transitions
/// of [MaterialPageRoute]s.
///
/// ```dart
/// MaterialApp(
/// theme: ThemeData(
/// pageTransitionsTheme: PageTransitionsTheme(
/// builders: {
/// TargetPlatform.android: SharedAxisPageTransitionsBuilder(
/// transitionType: SharedAxisTransitionType.horizontal,
/// ),
/// TargetPlatform.iOS: SharedAxisPageTransitionsBuilder(
/// transitionType: SharedAxisTransitionType.horizontal,
/// ),
/// },
/// ),
/// ),
/// routes: {
/// '/': (BuildContext context) {
/// return Container(
/// color: Colors.red,
/// child: Center(
/// child: ElevatedButton(
/// child: Text('Push route'),
/// onPressed: () {
/// Navigator.of(context).pushNamed('/a');
/// },
/// ),
/// ),
/// );
/// },
/// '/a' : (BuildContext context) {
/// return Container(
/// color: Colors.blue,
/// child: Center(
/// child: ElevatedButton(
/// child: Text('Pop route'),
/// onPressed: () {
/// Navigator.of(context).pop();
/// },
/// ),
/// ),
/// );
/// },
/// },
/// );
/// ```
class SharedAxisPageTransitionsBuilder extends PageTransitionsBuilder {
/// Construct a [SharedAxisPageTransitionsBuilder].
const SharedAxisPageTransitionsBuilder({
required this.transitionType,
this.fillColor,
});
/// Determines which [SharedAxisTransitionType] to build.
final SharedAxisTransitionType transitionType;
/// The color to use for the background color during the transition.
///
/// This defaults to the [Theme]'s [ThemeData.canvasColor].
final Color? fillColor;
@override
Widget buildTransitions<T>(
PageRoute<T>? route,
BuildContext? context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return SharedAxisTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
transitionType: transitionType,
fillColor: fillColor,
child: child,
);
}
}
/// Defines a transition in which outgoing and incoming elements share a fade
/// transition.
///
/// The shared axis pattern provides the transition animation between UI elements
/// that have a spatial or navigational relationship. For example,
/// transitioning from one page of a sign up page to the next one.
///
/// Consider using [SharedAxisTransition] within a
/// [PageTransitionsTheme] if you want to apply this kind of transition to
/// [MaterialPageRoute] transitions within a Navigator (see
/// [SharedAxisPageTransitionsBuilder] for example code).
///
/// This transition can also be used directly in a
/// [PageTransitionSwitcher.transitionBuilder] to transition
/// from one widget to another as seen in the following example:
///
/// ```dart
/// int _selectedIndex = 0;
///
/// final List<Color> _colors = [Colors.white, Colors.red, Colors.yellow];
///
/// @override
/// Widget build(BuildContext context) {
/// return Scaffold(
/// appBar: AppBar(
/// title: const Text('Page Transition Example'),
/// ),
/// body: PageTransitionSwitcher(
/// // reverse: true, // uncomment to see transition in reverse
/// transitionBuilder: (
/// Widget child,
/// Animation<double> primaryAnimation,
/// Animation<double> secondaryAnimation,
/// ) {
/// return SharedAxisTransition(
/// animation: primaryAnimation,
/// secondaryAnimation: secondaryAnimation,
/// transitionType: SharedAxisTransitionType.horizontal,
/// child: child,
/// );
/// },
/// child: Container(
/// key: ValueKey<int>(_selectedIndex),
/// color: _colors[_selectedIndex],
/// child: Center(
/// child: FlutterLogo(size: 300),
/// )
/// ),
/// ),
/// bottomNavigationBar: BottomNavigationBar(
/// items: const <BottomNavigationBarItem>[
/// BottomNavigationBarItem(
/// icon: Icon(Icons.home),
/// title: Text('White'),
/// ),
/// BottomNavigationBarItem(
/// icon: Icon(Icons.business),
/// title: Text('Red'),
/// ),
/// BottomNavigationBarItem(
/// icon: Icon(Icons.school),
/// title: Text('Yellow'),
/// ),
/// ],
/// currentIndex: _selectedIndex,
/// onTap: (int index) {
/// setState(() {
/// _selectedIndex = index;
/// });
/// },
/// ),
/// );
/// }
/// ```
class SharedAxisTransition extends StatelessWidget {
/// Creates a [SharedAxisTransition].
///
/// The [animation] and [secondaryAnimation] argument are required and must
/// not be null.
const SharedAxisTransition({
Key? key,
required this.animation,
required this.secondaryAnimation,
required this.transitionType,
this.fillColor,
this.child,
}) : super(key: key);
/// The animation that drives the [child]'s entrance and exit.
///
/// See also:
///
/// * [TransitionRoute.animate], which is the value given to this property
/// when it is used as a page transition.
final Animation<double> animation;
/// The animation that transitions [child] when new content is pushed on top
/// of it.
///
/// See also:
///
/// * [TransitionRoute.secondaryAnimation], which is the value given to this
/// property when the it is used as a page transition.
final Animation<double> secondaryAnimation;
/// Determines which type of shared axis transition is used.
///
/// See also:
///
/// * [SharedAxisTransitionType], which defines and describes all shared
/// axis transition types.
final SharedAxisTransitionType transitionType;
/// The color to use for the background color during the transition.
///
/// This defaults to the [Theme]'s [ThemeData.canvasColor].
final Color? fillColor;
/// The widget below this widget in the tree.
///
/// This widget will transition in and out as driven by [animation] and
/// [secondaryAnimation].
final Widget? child;
@override
Widget build(BuildContext context) {
final Color color = fillColor ?? Theme.of(context).canvasColor;
return DualTransitionBuilder(
animation: animation,
forwardBuilder: (
BuildContext context,
Animation<double> animation,
Widget? child,
) {
return _EnterTransition(
animation: animation,
transitionType: transitionType,
child: child,
);
},
reverseBuilder: (
BuildContext context,
Animation<double> animation,
Widget? child,
) {
return _ExitTransition(
animation: animation,
transitionType: transitionType,
reverse: true,
fillColor: color,
child: child,
);
},
child: DualTransitionBuilder(
animation: ReverseAnimation(secondaryAnimation),
forwardBuilder: (
BuildContext context,
Animation<double> animation,
Widget? child,
) {
return _EnterTransition(
animation: animation,
transitionType: transitionType,
reverse: true,
child: child,
);
},
reverseBuilder: (
BuildContext context,
Animation<double> animation,
Widget? child,
) {
return _ExitTransition(
animation: animation,
transitionType: transitionType,
fillColor: color,
child: child,
);
},
child: child,
),
);
}
}
class _EnterTransition extends StatelessWidget {
const _EnterTransition({
required this.animation,
required this.transitionType,
this.reverse = false,
this.child,
});
final Animation<double> animation;
final SharedAxisTransitionType transitionType;
final Widget? child;
final bool reverse;
static final Animatable<double> _fadeInTransition = CurveTween(
curve: decelerateEasing,
).chain(CurveTween(curve: const Interval(0.3, 1.0)));
static final Animatable<double> _scaleDownTransition = Tween<double>(
begin: 1.10,
end: 1.00,
).chain(CurveTween(curve: standardEasing));
static final Animatable<double> _scaleUpTransition = Tween<double>(
begin: 0.80,
end: 1.00,
).chain(CurveTween(curve: standardEasing));
@override
Widget build(BuildContext context) {
switch (transitionType) {
case SharedAxisTransitionType.horizontal:
final Animatable<Offset> slideInTransition = Tween<Offset>(
begin: Offset(!reverse ? 30.0 : -30.0, 0.0),
end: Offset.zero,
).chain(CurveTween(curve: standardEasing));
return FadeTransition(
opacity: _fadeInTransition.animate(animation),
child: AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget? child) {
return Transform.translate(
offset: slideInTransition.evaluate(animation),
child: child,
);
},
child: child,
),
);
case SharedAxisTransitionType.vertical:
final Animatable<Offset> slideInTransition = Tween<Offset>(
begin: Offset(0.0, !reverse ? 30.0 : -30.0),
end: Offset.zero,
).chain(CurveTween(curve: standardEasing));
return FadeTransition(
opacity: _fadeInTransition.animate(animation),
child: AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget? child) {
return Transform.translate(
offset: slideInTransition.evaluate(animation),
child: child,
);
},
child: child,
),
);
case SharedAxisTransitionType.scaled:
return FadeTransition(
opacity: _fadeInTransition.animate(animation),
child: ScaleTransition(
scale: (!reverse ? _scaleUpTransition : _scaleDownTransition)
.animate(animation),
child: child,
),
);
}
}
}
class _ExitTransition extends StatelessWidget {
const _ExitTransition({
required this.animation,
required this.transitionType,
this.reverse = false,
required this.fillColor,
this.child,
});
final Animation<double> animation;
final SharedAxisTransitionType transitionType;
final bool reverse;
final Color fillColor;
final Widget? child;
static final Animatable<double> _fadeOutTransition = _FlippedCurveTween(
curve: accelerateEasing,
).chain(CurveTween(curve: const Interval(0.0, 0.3)));
static final Animatable<double> _scaleUpTransition = Tween<double>(
begin: 1.00,
end: 1.10,
).chain(CurveTween(curve: standardEasing));
static final Animatable<double> _scaleDownTransition = Tween<double>(
begin: 1.00,
end: 0.80,
).chain(CurveTween(curve: standardEasing));
@override
Widget build(BuildContext context) {
switch (transitionType) {
case SharedAxisTransitionType.horizontal:
final Animatable<Offset> slideOutTransition = Tween<Offset>(
begin: Offset.zero,
end: Offset(!reverse ? -30.0 : 30.0, 0.0),
).chain(CurveTween(curve: standardEasing));
return FadeTransition(
opacity: _fadeOutTransition.animate(animation),
child: Container(
color: fillColor,
child: AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget? child) {
return Transform.translate(
offset: slideOutTransition.evaluate(animation),
child: child,
);
},
child: child,
),
),
);
case SharedAxisTransitionType.vertical:
final Animatable<Offset> slideOutTransition = Tween<Offset>(
begin: Offset.zero,
end: Offset(0.0, !reverse ? -30.0 : 30.0),
).chain(CurveTween(curve: standardEasing));
return FadeTransition(
opacity: _fadeOutTransition.animate(animation),
child: Container(
color: fillColor,
child: AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget? child) {
return Transform.translate(
offset: slideOutTransition.evaluate(animation),
child: child,
);
},
child: child,
),
),
);
case SharedAxisTransitionType.scaled:
return FadeTransition(
opacity: _fadeOutTransition.animate(animation),
child: Container(
color: fillColor,
child: ScaleTransition(
scale: (!reverse ? _scaleUpTransition : _scaleDownTransition)
.animate(animation),
child: child,
),
),
);
}
}
}
/// Enables creating a flipped [CurveTween].
///
/// This creates a [CurveTween] that evaluates to a result that flips the
/// tween vertically.
///
/// This tween sequence assumes that the evaluated result has to be a double
/// between 0.0 and 1.0.
class _FlippedCurveTween extends CurveTween {
/// Creates a vertically flipped [CurveTween].
_FlippedCurveTween({
required Curve curve,
}) : super(curve: curve);
@override
double transform(double t) => 1.0 - super.transform(t);
}