Flutter - How to animate NavigationRail page transition [windows] - windows

I have implemented the NavigationRail as such:
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
int _selectedIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
NavigationRail(
destinations: const [
NavigationRailDestination(
icon: Icon(Icons.home), label: Text("Home")),
NavigationRailDestination(
icon: Icon(Icons.settings), label: Text("Settings"))
],
selectedIndex: _selectedIndex,
),
Expanded(child: pageBuilder())
],
),
);
}
Widget pageBuilder() {
switch (_selectedIndex) {
case 0:
return const _HomePage();
case 1:
return const _SettingsPage();
default:
return const _HomePage();
}
}
}
With _HomePage :
class _HomePage extends StatefulWidget {
const _HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<_HomePage> {
#override
Widget build(BuildContext context) {
return Container(child: Text("HomePage"));
}
And SettingsPage is the same but says "SettingsPage" instead.
The question is, how can I animate the transition between these pages?
I cannot use Route and call Navigator.of(context).push(_pageRouter()) under the switch statement as it will throw errors about building or such (it's a long one which I can provide if needed).
Is there any way to achieve this without using Route? or some workaround?

There are multiple animation type. You can use switcher to change between animation
here is the link for widget. it will automatically change widget by animation.
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
child: pageBuilder(),
)

Related

How to shake a widget in Flutter on invalid input?

On my signup form, I have a checkbox which needs to shake a bit whenever the user tries to login before accepting the terms and conditions. How can I achieve something like this Flutter?
import 'package:flutter/material.dart';
#immutable
class ShakeWidget extends StatelessWidget {
final Duration duration;
final double deltaX;
final Widget child;
final Curve curve;
const ShakeWidget({
Key key,
this.duration = const Duration(milliseconds: 500),
this.deltaX = 20,
this.curve = Curves.bounceOut,
this.child,
}) : super(key: key);
/// convert 0-1 to 0-1-0
double shake(double animation) =>
2 * (0.5 - (0.5 - curve.transform(animation)).abs());
#override
Widget build(BuildContext context) {
return TweenAnimationBuilder<double>(
key: key,
tween: Tween(begin: 0.0, end: 1.0),
duration: duration,
builder: (context, animation, child) => Transform.translate(
offset: Offset(deltaX * shake(animation), 0),
child: child,
),
child: child,
);
}
}
If you need to re-enable shaking, just change ShakeWidget key to some random one.
I achieved this a different way because I wanted to be able to control the duration and get a bit more vigorous shaking. I also wanted to be able to add this easily as a wrapper for other child widgets so I looked up how to use keys to have a parent control actions in a child widget. Here is the class:
class ShakerState extends State<Shaker> with SingleTickerProviderStateMixin {
late AnimationController animationController;
late Animation<double> animation;
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 800), // how long the shake happens
)..addListener(() => setState(() {}));
animation = Tween<double>(
begin: 00.0,
end: 120.0,
).animate(animationController);
}
math.Vector3 _shake() {
double progress = animationController.value;
double offset = sin(progress * pi * 10.0); // change 10 to make it vibrate faster
return math.Vector3(offset * 25, 0.0, 0.0); // change 25 to make it vibrate wider
}
shake() {
animationController.forward(from:0);
}
#override
Widget build(BuildContext context) {
return Transform(
transform: Matrix4.translation(_shake()),
child: widget.child,
);
}
}
And then to use this you need a key in your parent:
final GlobalKey<ShakerState> _shakeKey = GlobalKey<ShakerState>();
And then you can do something like this inside your parent body (see where "Shaker" is used around the child I want to shake):
...
Container(
height: 50,
width: 250,
decoration: BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(20)),
child: TextButton(
onPressed: () => _handleEmailSignIn(loginController.text, loginPasswordController.text),
child: Shaker(_shakeKey, Text('Login', // <<================
style: TextStyle(color: Colors.white, fontSize: 25),
)),
),
),
...
Then with the controller you can trigger the shake at a time you want programmatically like this (see the use of "_shakeKey"):
Future<void> _handleEmailSignIn(String user, password) async {
try {
await auth.signInWithEmailAndPassword(email: user, password: password);
FocusScope.of(context).unfocus();
await Navigator.pushNamedAndRemoveUntil(context, '/next_page', ModalRoute.withName('/'));
} on FirebaseAuthException catch (e) {
_shakeKey.currentState?.shake(); // <<=============
if (e.code == 'user-not-found') {
print('No user found for that email.');
} else if (e.code == 'wrong-password') {
print('Wrong password provided for that user.');
}
}
setState(() {});
}
Little improved of #Kent code (added controller).
import 'package:flutter/material.dart';
class ShakeX extends StatefulWidget {
final Widget child;
final double horizontalPadding;
final double animationRange;
final ShakeXController controller;
final Duration animationDuration;
const ShakeX(
{Key key,
#required this.child,
this.horizontalPadding = 30,
this.animationRange = 24,
this.controller,
this.animationDuration = const Duration(milliseconds: 500)})
: super(key: key);
#override
_ShakeXState createState() => _ShakeXState();
}
class _ShakeXState extends State<ShakeX> with SingleTickerProviderStateMixin {
AnimationController animationController;
#override
void initState() {
animationController =
AnimationController(duration: widget.animationDuration, vsync: this);
if (widget.controller != null) {
widget.controller.setState(this);
}
super.initState();
}
#override
Widget build(BuildContext context) {
final Animation<double> offsetAnimation =
Tween(begin: 0.0, end: widget.animationRange)
.chain(CurveTween(curve: Curves.elasticIn))
.animate(animationController)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController.reverse();
}
});
return AnimatedBuilder(
animation: offsetAnimation,
builder: (context, child) {
return Container(
margin: EdgeInsets.symmetric(horizontal: widget.animationRange),
padding: EdgeInsets.only(
left: offsetAnimation.value + widget.horizontalPadding,
right: widget.horizontalPadding - offsetAnimation.value),
child: widget.child,
);
},
);
}
}
class ShakeXController {
_ShakeXState _state;
void setState(_ShakeXState state) {
_state = state;
}
Future<void> shake() {
print('shake');
return _state.animationController.forward(from: 0.0);
}
}
import 'package:flutter/material.dart';
class ShakeError extends StatefulWidget {
const ShakeError({
Key? key,
required this.child,
this.controller,
this.duration = const Duration(milliseconds: 500),
this.deltaX = 20,
this.curve = Curves.bounceOut,
}) : super(key: key);
final Widget child;
final Duration duration;
final double deltaX;
final Curve curve;
final Function(AnimationController)? controller;
#override
State<ShakeError> createState() => _ShakeErrorState();
}
class _ShakeErrorState extends State<ShakeError>
with SingleTickerProviderStateMixin<ShakeError> {
late AnimationController controller;
late Animation<double> offsetAnimation;
#override
void initState() {
controller = AnimationController(duration: widget.duration, vsync: this);
offsetAnimation = Tween<double>(begin: 0.0, end: 1.0)
.chain(CurveTween(curve: widget.curve))
.animate(controller);
if (widget.controller is Function) {
widget.controller!(controller);
}
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
/// convert 0-1 to 0-1-0
double shake(double animation) =>
2 * (0.5 - (0.5 - widget.curve.transform(animation)).abs());
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: offsetAnimation,
builder: (BuildContext context, Widget? child) {
return Transform.translate(
offset: Offset(widget.deltaX * shake(offsetAnimation.value), 0),
child: child,
);
},
child: widget.child,
);
}
}
Here is some code from my app. It shakes a red x on the screen. redx.png. I'm sure you could adopt it to your use case. I'm using AnimatedBuilder.
Code in action:
https://giphy.com/gifs/Yo2u06oMu1ksPYRD3B
import 'package:flutter/material.dart';
class ShakeX extends StatefulWidget {
const ShakeX({
Key key,
}) : super(key: key);
#override
_ShakeXState createState() => _ShakeXState();
}
class _ShakeXState extends State<ShakeX> with SingleTickerProviderStateMixin{
AnimationController controller;
#override
void initState() {
controller = AnimationController(duration: const Duration(milliseconds: 500), vsync: this);
super.initState();
}
#override
Widget build(BuildContext context) {
final Animation<double> offsetAnimation =
Tween(begin: 0.0, end: 24.0).chain(CurveTween(curve: Curves.elasticIn)).animate(controller)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
}
});
controller.forward(from: 0.0);
return AnimatedBuilder(animation: offsetAnimation,
builder: (context, child){
if (offsetAnimation.value < 0.0) print('${offsetAnimation.value + 8.0}');
return Container(
margin: EdgeInsets.symmetric(horizontal: 24.0),
padding: EdgeInsets.only(left: offsetAnimation.value + 30.0, right: 30.0 - offsetAnimation.value),
child: Image.asset("assets/redx.png"),
);
},);
}
}
For those still looking... You can achieve something like that using Animated Widget
ShakeAnimatedWidget(
enabled: this._enabled,
duration: Duration(milliseconds: 1500),
shakeAngle: Rotation.deg(z: 40),
curve: Curves.linear,
child: FlutterLogo(
style: FlutterLogoStyle.stacked,
),
),
Check the link for even advance usage.
There are many animation based packages in flutter, you can check that site (https://fluttergems.dev/animation-transition/) to see them. As developer, you don't have to to create animation classes from scratch.
Regarding shaking animation, I would suggest flutter_animator package. There is a shake widget which exactly performs what you need.
My version on previous code. It prevents usage of paddings and margins
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class ShakeX extends StatefulWidget {
final Widget child;
final double horizontalPadding;
final double animationRange;
final ShakeXController shakeXController;
final Duration animationDuration;
const ShakeX({
Key? key,
required this.child,
this.horizontalPadding = 16,
this.animationRange = 16,
required this.shakeXController,
this.animationDuration = const Duration(milliseconds: 500),
}) : super(key: key);
#override
ShakeXState createState() => ShakeXState();
}
class ShakeXState extends State<ShakeX> with SingleTickerProviderStateMixin {
late AnimationController animationController;
var childSize = Size.zero;
#override
void initState() {
super.initState();
animationController = AnimationController(duration: widget.animationDuration, vsync: this);
widget.shakeXController.setState(this);
}
#override
Widget build(BuildContext context) {
final Animation<double> offsetAnimation = Tween(begin: 0.0, end: widget.animationRange)
.chain(CurveTween(curve: Curves.elasticIn))
.animate(animationController)
..addStatusListener((status) {
if (status == AnimationStatus.completed && status != AnimationStatus.reverse) {
animationController.reverse();
}
});
return AnimatedBuilder(
animation: offsetAnimation,
builder: (context, child) {
return SizedBox(
width: childSize.width,
height: childSize.height,
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned(
left: offsetAnimation.value,
child: MeasureSize(
onChange: (size) {
setState(() {
childSize = size;
});
},
child: widget.child),
),
],
),
);
},
);
}
}
class ShakeXController {
late ShakeXState _state;
void setState(ShakeXState state) {
_state = state;
}
Future<void> shake() {
return _state.animationController.forward(from: 0.0);
}
}
typedef void OnWidgetSizeChange(Size size);
class MeasureSizeRenderObject extends RenderProxyBox {
Size? oldSize;
OnWidgetSizeChange onChange;
MeasureSizeRenderObject(this.onChange);
#override
void performLayout() {
super.performLayout();
Size newSize = child!.size;
if (oldSize == newSize) return;
oldSize = newSize;
WidgetsBinding.instance.addPostFrameCallback((_) {
onChange(newSize);
});
}
}
class MeasureSize extends SingleChildRenderObjectWidget {
final OnWidgetSizeChange onChange;
const MeasureSize({
Key? key,
required this.onChange,
required Widget child,
}) : super(key: key, child: child);
#override
RenderObject createRenderObject(BuildContext context) {
return MeasureSizeRenderObject(onChange);
}
#override
void updateRenderObject(BuildContext context, covariant MeasureSizeRenderObject renderObject) {
renderObject.onChange = onChange;
}
}

parallax effect | scrollable background image in flutter

I'm trying to implement a scrollable background image (parallax).
Like in a home screen launcher.
An example:
In Evie launcher:
this video
I've tried using AnimatedBuilder mentioned here in the docs like this.
I'm using a ValueNotifier<double> as the listener for the animation of the AnimatedBuilder Widget.
The complete code is this
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PageView Scrolling',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
ValueNotifier<double> _notifier;
double _prevnotifier;
double getOffset(){
if (_notifier.value == 0 && _prevnotifier != null){
return _prevnotifier;
}
return _notifier.value;
}
#override
void dispose() {
_notifier?.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_notifier = ValueNotifier<double>(0);
_prevnotifier = _notifier.value;
_notifier.addListener(
(){
print('object ${_notifier.value}');
if (_notifier.value != 0)
_prevnotifier = _notifier.value;
}
);
}
#override
Widget build(BuildContext context) {
print("Size is ${MediaQuery.of(context).size}");
return Scaffold(
body: Stack(
children: <Widget>[
AnimatedBuilder(
animation: _notifier,
builder: (context, _) {
return Transform.translate(
offset: Offset(-getOffset() * 60, 0),
child: Image.network(
"https://w.wallhaven.cc/full/r2/wallhaven-r276qj.png",
height: MediaQuery.of(context).size.height,
fit: BoxFit.fitHeight
),
);
},
),
NotifyingPageView(
notifier: _notifier,
),
],
),
);
}
}
class NotifyingPageView extends StatefulWidget {
final ValueNotifier<double> notifier;
const NotifyingPageView({Key key, this.notifier}) : super(key: key);
#override
_NotifyingPageViewState createState() => _NotifyingPageViewState();
}
class _NotifyingPageViewState extends State<NotifyingPageView> {
int _previousPage;
PageController _pageController;
void _onScroll() {
// Consider the page changed when the end of the scroll is reached
// Using onPageChanged callback from PageView causes the page to change when
// the half of the next card hits the center of the viewport, which is not
// what I want
if (_pageController.page.toInt() == _pageController.page) {
_previousPage = _pageController.page.toInt();
}
widget.notifier?.value = _pageController.page - _previousPage;
}
#override
void initState() {
_pageController = PageController(
initialPage: 0,
viewportFraction: 0.9,
)..addListener(_onScroll);
_previousPage = _pageController.initialPage;
super.initState();
}
List<Widget> _pages = List.generate(
10,
(index) {
return Container(
height: 10,
alignment: Alignment.center,
color: Colors.transparent,
child: Text(
"Card number $index",
style: TextStyle(
color: Colors.teal,
fontWeight: FontWeight.bold,
fontSize: 25,
),
),
);
},
);
#override
Widget build(BuildContext context) {
return PageView(
children: _pages,
controller: _pageController,
);
}
}
The image can be found here
Now I have two issues:
The image when using fit: BoxFit.fitHeight is not overflowing fully. It's currently like this
Because the value will become zero when the animation is done it's snapping like this:
this video
I tried storing the value just before the _notifier.value becomes zero and use it when it returns zero but it resulted in that weird transition that I've shown you in that above video.
What do you suggest can be done to make something like a scrollable wallpaper in flutter?
Something like this
Design
This is not as trivial as I thought it would be.
TLDR; Github read the comments.
I used a ValueNotifier<double> like I mentioned to control the scroll.
Then instead of Transform.translate I used an OverflowBox with its alignment property. Which is computed based on the notifier.value before rendering.
And to display the image in fullscreen mode:
I used AspectRatio with a child DecoratedBox whose decoration is a BoxDecoration with its image as an ImageProvider.
All the code can be found here on github. (Read the comments)
And this issue on github has slightly detailed info and a less complicated alternate implementation by Antonello Galipò

How to implement event listener or delegate on flutter

I have a main dart class in which the app bar is located and the app bar contains a refresh button. I'm using a navigation drawer to populate two other views f1 and f2.
From my main.dart how can I pass the refresh button clicks to the sub fragment kind of f1.dart so that I can refresh my contents on f1.dart
// State of Main
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
child: new Column(
children: <Widget>[
////////////////////////////////////////////////////////////
new FirstFragment(),
new SecondFragment()
/////////////////////////////////////////////////////////////
],
),
),
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
print("refresh pressed");
/////////////////////////
How to send this refresh pressed event to my FirstFragment class??
/////////////////////////
},
color: Colors.white,
)
],
),
body: _getDrawerItemWidget(_selectedDrawerIndex),
);
}
}
In Android, I've been using event listeners and for iOS, I can use delegates for the purpose. How can I achieve this on flutter/dart. ?
You can pass a callback, use the VoidCallback and receive the event on your Main widget.
class MainPage extends StatelessWidget {
_onTapButton() {
print("your event here");
}
#override
Widget build(BuildContext context) {
return Container(
child: ChildPage(
onTap: _onTapButton,
),
);
}
}
class ChildPage extends StatelessWidget {
final VoidCallback onTap;
const ChildPage({Key key, this.onTap}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text("Click Me"),
onPressed: () {
//call to your callback here
onTap();
},
),
);
}
}
In case you want the opposite, you can just refresh the state of your parent widget and change the parameter that you pass to your fragments or also you can use GlobalKey, like the example below:
class MainPage extends StatelessWidget {
final GlobalKey<ChildPageState> _key = GlobalKey();
_onTapButton() {
_key.currentState.myMethod();
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
ChildPage(
key: _key,
),
RaisedButton(
child: Text("Click me"),
onPressed: _onTapButton,
)
],
)
);
}
}
class ChildPage extends StatefulWidget {
const ChildPage({Key key}) : super(key: key);
#override
ChildPageState createState() {
return new ChildPageState();
}
}
class ChildPageState extends State<ChildPage> {
myMethod(){
print("called from parent");
}
#override
Widget build(BuildContext context) {
return Container(
child: Text("Click Me"),
);
}
}

How to make a Text widget act like marquee when the text overflows in Flutter

I'm looking for a way to implement Marquee style on a Text widget so that it automatically start scrolling when the text is overflowed from the screen.
Is there a way to do it.
I've tried all the decoration modes but cant seem to find a Marquee option there.
This widget is what I came up with, and I think it serves your needs:
class MarqueeWidget extends StatefulWidget {
final Widget child;
final Axis direction;
final Duration animationDuration, backDuration, pauseDuration;
const MarqueeWidget({
Key? key,
required this.child,
this.direction = Axis.horizontal,
this.animationDuration = const Duration(milliseconds: 6000),
this.backDuration = const Duration(milliseconds: 800),
this.pauseDuration = const Duration(milliseconds: 800),
}) : super(key: key);
#override
_MarqueeWidgetState createState() => _MarqueeWidgetState();
}
class _MarqueeWidgetState extends State<MarqueeWidget> {
late ScrollController scrollController;
#override
void initState() {
scrollController = ScrollController(initialScrollOffset: 50.0);
WidgetsBinding.instance.addPostFrameCallback(scroll);
super.initState();
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
child: widget.child,
scrollDirection: widget.direction,
controller: scrollController,
);
}
void scroll(_) async {
while (scrollController.hasClients) {
await Future.delayed(widget.pauseDuration);
if (scrollController.hasClients) {
await scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: widget.animationDuration,
curve: Curves.ease,
);
}
await Future.delayed(widget.pauseDuration);
if (scrollController.hasClients) {
await scrollController.animateTo(
0.0,
duration: widget.backDuration,
curve: Curves.easeOut,
);
}
}
}
}
Its functionality should be pretty obvious. An example implementation would look like this:
class FlutterMarqueeText extends StatefulWidget {
const FlutterMarqueeText({Key? key}) : super(key: key);
#override
_FlutterMarqueeTextState createState() => _FlutterMarqueeTextState();
}
class _FlutterMarqueeTextState extends State<FlutterMarqueeText> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Flutter Marquee Text"),
),
body: const Center(
child: SizedBox(
width: 200.0,
child: MarqueeWidget(
direction: Axis.horizontal,
child: Text("This text is to long to be shown in just one line"),
),
),
),
);
}
}
Use The Marquee package.
and if you get any 'hasSize' Error or 'Incorrect uses of Parent Widget' Error..
Just wrap Marquee widget with container and give height and width of that container.

Hero Animation Not Working in Flutter

So, I'm trying to make a home delivery app for a restaurant in flutter and I can't seem to get my hero animation working. First I made a splash screen where the logo shows up and then it navigates to home page where the logo is supposed to do a hero transition. The splash screen and the home page are in two separate dart files. Here's the code for my splash screen:
import 'package:flutter/material.dart';
import 'home_page.dart';
import 'dart:async';
class Splash extends StatefulWidget {
#override
_SplashState createState() => new _SplashState();
}
class _SplashState extends State<Splash> with SingleTickerProviderStateMixin
{
Animation<double> _mainLogoAnimation;
AnimationController _mainLogoAnimationController;
#override
void initState() {
super.initState();
goToHomePage();
_mainLogoAnimationController = new AnimationController(duration: new Duration(milliseconds: 2500) ,vsync: this);
_mainLogoAnimation = new CurvedAnimation(parent:
_mainLogoAnimationController, curve: Curves.easeIn);
_mainLogoAnimation.addListener(() => (this.setState(() {})));
_mainLogoAnimationController.forward();
}
Future goToHomePage() async {
await new Future.delayed(const Duration(milliseconds: 4000));
Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new HomePage()));
}
#override
Widget build(BuildContext context) {
return new Material(
color: Colors.black,
child: new Center(
child: new Opacity(
opacity: 1.0 * _mainLogoAnimation.value,
child: new Hero(
tag: 'tbh_logo',
child: new Image(
image: new AssetImage('assets/images/tbh_main_logo.png'),
width: 300.0
)
)
)
)
);
}
}
And here's the code for the home page:
import 'package:flutter/material.dart';
import '../ui/drawer.dart';
import 'splash.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("The Barni House"),
backgroundColor: Colors.black,
),
drawer: new Drawer(child: MyDrawer()),
body: new Center(
child: Column(
children: <Widget>[
new Container(
child: new Hero(
tag: 'tbh_logo',
child: new Image(
image: new AssetImage('assets/images/tbh_main_logo.png'),
width: 300.0
)
)
)
],
)
),
);
}
}
Checked your code so the hero animation is working but its happening to fast because of the transition duration is only 300 milliseconds.
To achieve the below result you can make a Custom MaterialPageRoute.
Before
#override
Duration get transitionDuration => const Duration(milliseconds: 300);
After
#override
Duration get transitionDuration => const Duration(milliseconds: 1000);
Also, you can play with the CurvedAnimation
new CurvedAnimation(
parent: routeAnimation,
curve: Curves.elasticIn,
)
CustomRoute.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
final Tween<Offset> _kBottomUpTween = new Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
);
// Offset from offscreen to the right to fully on screen.
final Tween<Offset> _kRightMiddleTween = new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
);
// Offset from offscreen below to fully on screen.
class AppPageRoute extends MaterialPageRoute<String> {
#override
final bool maintainState;
#override
final WidgetBuilder builder;
CupertinoPageRoute<String> _internalCupertinoPageRoute;
AppPageRoute({
#required this.builder,
RouteSettings settings: const RouteSettings(),
this.maintainState: true,
bool fullscreenDialog: false,
}) : assert(builder != null),
assert(settings != null),
assert(maintainState != null),
assert(fullscreenDialog != null),
super(
settings: settings,
fullscreenDialog: fullscreenDialog,
builder: builder,
) {
assert(opaque); // PageRoute makes it return true.
}
#override
Color get barrierColor => null;
#override
Duration get transitionDuration => const Duration(milliseconds: 1000);
CupertinoPageRoute<String> get _cupertinoPageRoute {
assert(_useCupertinoTransitions);
_internalCupertinoPageRoute ??= new CupertinoPageRoute<String>(
builder: builder,
fullscreenDialog: fullscreenDialog,
hostRoute: this,
);
return _internalCupertinoPageRoute;
}
bool get _useCupertinoTransitions {
return _internalCupertinoPageRoute?.popGestureInProgress == true ||
Theme.of(navigator.context).platform == TargetPlatform.iOS;
}
#override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
final Widget result = builder(context);
assert(() {
if (result == null) {
throw new FlutterError('The builder for route "${settings.name}" returned null.\n'
'Route builders must never return null.');
}
return true;
}());
return result;
}
#override
Widget buildTransitions(
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
if (_useCupertinoTransitions) {
return _cupertinoPageRoute.buildTransitions(context, animation, secondaryAnimation, child);
}
return new _CustomPageTransition(routeAnimation: animation, child: child, fullscreenDialog: fullscreenDialog);
}
}
class _CustomPageTransition extends StatelessWidget {
final Animation<Offset> _positionAnimation;
final Widget child;
final bool fullscreenDialog;
_CustomPageTransition({
Key key,
#required Animation<double> routeAnimation,
#required this.child,
#required this.fullscreenDialog,
}) : _positionAnimation = !fullscreenDialog
? _kRightMiddleTween.animate(new CurvedAnimation(
parent: routeAnimation,
curve: Curves.elasticIn,
))
: _kBottomUpTween.animate(new CurvedAnimation(
parent: routeAnimation, // The route's linear 0.0 - 1.0 animation.
curve: Curves.elasticIn,
)),
super(key: key);
#override
Widget build(BuildContext context) {
return new SlideTransition(
position: _positionAnimation,
child: child,
);
}
}
Push new Route
Future goToHomePage() async {
await new Future.delayed(const Duration(milliseconds: 4000));
Navigator.of(context).push(new AppPageRoute(builder: (BuildContext context) => new HomePage()));
}
You can use Custom MaterialPageRoute for the splash screen and for other routes MaterialPageRoute.
Hope it helps
// main.dart class
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:flutter_test7/photo_hero.dart';
import 'package:flutter_test7/second_page.dart';
class HeroAnimation extends StatelessWidget {
Widget build(BuildContext context) {
timeDilation = 2.5; // 1.0 means normal animation speed.
return new Scaffold(
appBar: new AppBar(
title: const Text('Basic Hero Animation'),
),
body: new Center(
child: new PhotoHero(
photo: 'images/flippers-alpha.png',
width: 300.0,
onTap: () {
Navigator.of(context).pushNamed('/second_page');
},
),
),
);
}
}
void main() {
runApp(
new MaterialApp(
home: new HeroAnimation(),
routes: <String, WidgetBuilder>{
'/second_page': (context) => new SecondPage()
},
),
);
}
// photo_hero.dart class
import 'package:flutter/material.dart';
class PhotoHero extends StatelessWidget {
const PhotoHero({Key key, this.photo, this.onTap, this.width})
: super(key: key);
final String photo;
final VoidCallback onTap;
final double width;
Widget build(BuildContext context) {
return new SizedBox(
width: width,
child: new Hero(
tag: photo,
child: new Material(
color: Colors.transparent,
child: new InkWell(
onTap: onTap,
child: new Image.asset(
photo,
fit: BoxFit.contain,
),
),
),
),
);
}
}
// second_page.dart class
import 'package:flutter/material.dart';
import 'package:flutter_test7/photo_hero.dart';
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => new _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text('Flippers Page'),
),
body: new Container(
color: Colors.lightBlueAccent,
padding: const EdgeInsets.all(16.0),
alignment: Alignment.topLeft,
child: new PhotoHero(
photo: 'images/flippers-alpha.png',
width: 100.0,
onTap: () {
Navigator.of(context).pop();
},
),
),
);
}
}
Hope this helps.
Hero animation is quite easy to implement in Flutter. You just need to import the package package:flutter/scheduler.dart in your landing page. See below code
import 'package:flutter/scheduler.dart' show timeDilation;
Remember to add timeDilation variable and assign it a value to speed up or slow down the animation in seconds. Add these just after your build method as in example below
Widget build(BuildContext context) {
timeDilation = 2;
For better animation, just use a custom animation

Resources