I am trying to achieve something like that coupertino page transition on routing. (briefly how to target and animate both the previous and current routes).
I checked this package page_transition, but the logic used seems off as its rebuilding the previous context widget and tries to hack an animation in the new route.
even in the documentation it seems to only be about the route on top of the navigation stack.
and you end up with something like this:
Navigator.of(context).push(AnimatedRoute(
prevPage: widget, nextPage: Page2()));
class AnimatedRoute extends PageRouteBuilder {
final Widget nextPage;
final Widget prevPage;
AnimatedRoute({this.prevPage, this.nextPage}) : super(
transitionDuration: Duration(milliseconds: 700),
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return nextPage;
},
maintainState: true,
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return Material(
child: Stack(
overflow: Overflow.visible,
children: <Widget>[
SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 0.0),
end: const Offset(-0.3, 0.0),
).animate(animation),
child: prevPage,
),
SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: AnimatedBuilder(
animation: animation,
builder: (c, w) {
return Material(
shadowColor: Colors.black,
// elevation: 10.0 * animation.value,
child: nextPage
);
},
),
)
],
),
);
}
);
}
apart from the rebuild this also doesn't take in account the state of the older widget.
a better way to handle that is appreciated
You can use PageRouteBuilder to be able to handle transition both for the page you are transitioning from and the one you are transitioning to as I explain below:
Note: pageBuilder takes 3 parameters:
context, animation and secondaryAnimation. animation is used when the page is navigated to and secondaryAnimation is used for when the page is navigated from.
Suppose that we have two pages, A and B.
For page A:
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return AnimatedBuilder(
animation: secondaryAnimation,
builder: (context, innerChild) {
retrun Transform.scale(
scale: (animation.value - secondaryAnimation.value),
child: A(),
);
},
);
},
);
},
),
);
And for page B:
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return AnimatedBuilder(
animation: secondaryAnimation,
builder: (context, child) {
return Transform.translate(
offset: Offset(
1.0 - animation.value,
0.0,
),
child: B(),
);
},
);
},
);
},
),
);
This will lead to a transition like the gif below:
Transition sample
(gif explanation: first page moves out with scaling down and second page comes in like a slide transition)
There are other packages too like you can use getx although it is bloated with features, but still it also has a lot of transition to apply b/w both pages. You can check their source code too if you want.
Also why not you use Cupertino Transition it is great and supported by flutter?
Related
I want to use the SizeTransition animation to animate my page route, but there is no effect when I run it.
Here is my code
Navigator.push(
context,
PageRouteBuilder(
transitionDuration: Duration(seconds: 2),
transitionsBuilder:
(context, animation, animationTime, child) {
return SizeTransition(
sizeFactor: animation,
axis: Axis.vertical,
axisAlignment: 0,
child: child,
);
},
pageBuilder: (context, animation, animationTime) {
return CreateUpdateNoteView();
},
settings: RouteSettings(arguments: note),
),
);
This is the effect I am going for
First of all, we do not have the animation itself so it is kind of hard to say what is missing exactly but 2 things:
even though you put a transition it doesn't mean that the animation itself has a duration so:
building a controller
class YourClass extends State<XXX> with
SingleTickerProviderStateMixin {
late AnimationController controller;
#override
void initState() {
controller = AnimationController(
vsync: this,
duration: const Duration(second: 2),
); // automatically animation will be started
}
then add it in your sizeTransition:
Navigator.push(
context,
PageRouteBuilder(
transitionDuration: Duration(seconds: 2),
transitionsBuilder:
(context, animation, animationTime, child) {
return SizeTransition(
sizeFactor: CurvedAnimation(
curve: Curves.linear,
parent: controller
),
axis: Axis.vertical,
axisAlignment: 0,
child: child,
);
},
pageBuilder: (context, animation, animationTime) {
return CreateUpdateNoteView();
},
settings: RouteSettings(arguments: note),
),
);
A package exist for this exact purpose and is developped by flutter.dev, it is animations (can be found here: https://pub.dev/packages/animations#container-transform) and there is a codelab (can be found here: https://codelabs.developers.google.com/codelabs/material-motion-flutter#4), this will definitely help you.
Hope it could help
I am using Hero in my app for smooth transitions but I faced an issue: I would like the Hero-Widget rotate 90 degress on transition. I found out I can use flightShuttleBuilder for this and tried it like this:
flightShuttleBuilder: (
BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,
) {
final Widget toHero = toHeroContext.widget;
return RotationTransition(
turns: animation,
child: toHero,
);
},
As you can see I used the RotationTransition and it kind of works but not exactly how I want it. It rotates the widget during the flight by 360 instead of 90.
The widget should stay rotated on the 2nd screen. I tried that with RotatedBox. This is the full widget on the 2nd screen:
child: Hero(
tag: 'america',
flightShuttleBuilder: (
BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,
) {
final Widget toHero = toHeroContext.widget;
return RotationTransition(
turns: animation.drive(Tween(begin: 0, end: 0.25)),
child: toHero,
);
},
child: RotatedBox(
quarterTurns: 1,
child: SizedBox(
height: 100,
width: 100,
child: SvgPicture.asset(
'assets/icons/america.svg',
),
),
),
),
Is there a way to get this done and how would I do that? Couldn't find anything on this. Let me know if you need any more info!
With the help of #Michael Horn I got it working. As he suggested I changed the turns value. That fixed the rotation. However I wanted the widget to stay rotated on the 2nd screen. I did that with RotatedBox. But when simply giving it the value 1, Hero will take that Rotated Widget for flightShuttleBuilder and the sets it back when building is finished. I fixed this by changing the quarterTurns after all the widgets are build:
int rotation = 0;
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
setState(() {
rotation = 1;
});
});
}
and my widget:
child: Hero(
tag: 'america',
flightShuttleBuilder: (
BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,
) {
final Widget toHero = toHeroContext.widget;
return RotationTransition(
turns: animation.drive(Tween(begin: 0, end: 0.25)), // <- for the during-flight 90 degree rotation
child: toHero,
);
},
child: RotatedBox(
quarterTurns: rotation, // <- important to not change that dynamically
child: SizedBox(
height: 100,
width: 100,
child: SvgPicture.asset(
'assets/icons/america.svg',
),
),
),
),
Flutter. There are two screens. On the first screen, there is one widget in the center (a container with text). On the second screen (ListView), the same (container with text), but shifted to the side, and another one is added(TextField). How do I animate the transition between these screens? Move the first widget (container) I know how. But how do I add a TextField at the same time ? ......as an option, I thought to apply the opacity of the TextField , but this place on the first screen is already occupied. Probably a complete replacement of the screens is needed here.
Here you have example of quick animation. This code should be in onpressed
Navigator.push(
context,
PageRouteBuilder(
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secAnimation,
Widget child) {
animation = CurvedAnimation(
parent: animation,
curve: Curves.fastLinearToSlowEaseIn);
return ScaleTransition(
scale: animation,
child: child,
alignment: Alignment.center,
);
},
transitionDuration:
const Duration(milliseconds: 300),
pageBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secAnimation) {
return **urpage**();
}));
I have the following dialog. It has Slide Transition when it pops up on the screen and when it is being dismissed. Along with it, I also use Fade Transition for the background when dialog is showed or closed.
showGeneralDialog(
barrierDismissible: true,
barrierLabel: '',
barrierColor: kModalBackgroundColor,
transitionDuration: Duration(milliseconds: 150),
pageBuilder: (ctx, anim1, anim2) {
return DismissibleModalWidget();
},
transitionBuilder: (ctx, anim1, anim2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, 1), end: Offset(0, 0)).animate(anim1),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8 * anim1.value, sigmaY: 8 * anim1.value),
child: FadeTransition(
opacity: anim1,
child: child,
),
),
);
},
context: context,
);
Widget DismissibleModalWidget
class DismissibleModalWidget extends StatelessWidget {
const DismissibleModalWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Dismissible(
key: const Key('dismissible'),
direction: DismissDirection.vertical,
onDismissed: (_) => Navigator.of(context).pop(),
child: Material(
.....
What the code does now
SlideTransition works before FadeTransition, meaning that first the dialog will appear on the screen, then the fade transition will start to work. Same when I drag down the dialog to dismiss it, only after the dialog disappeared completely from the screen, fade will start animation from the blurred screen.
What I want to achieve
I would like to make both of these transitions to work together. Meaning as slide animation of the dialog is progress, so is fade transition of the background. When I want to drag down the dialog to dismiss it, the fade will start working and the more I drag down the dialog, the less blurred the background will be so that as soon as dialog completely disappeared, the background is back to normal.
I'm pretty new to flutter and i'm trying to do some animation on a PageView. to be precise, I want to animate removing an item.
I've tried serveral ways to animate it and apart from a solution, the way how you guys would solve such a problem would also be helpful for my flutter skils.
What I've tried so far:
Animating the padding and opacity
the problem with this is that when i set the padding in the setState in the onLongPress it rebuilds the widget and it overrides the padding again with the active or inactive CardPadding (i think)
Animating the width and height
I just can't seem to get both of these values to work
Animating the viewportFraction on the PageViewController
Would not know how to go about this and if it would be possible to do this only for a specific 'Page'
Below is the (stripped down) code I've written thus far.
class Main extends StatefulWidget {
#override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
int activeCard = 0;
EdgeInsets inActiveCardPadding = EdgeInsets.symmetric(vertical: 120.0, horizontal: 20.0);
EdgeInsets activeCardPadding = EdgeInsets.symmetric(vertical: 105.0, horizontal: 10.0);
PageController pageController = PageController(
initialPage: 0,
viewportFraction: 0.8,
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Stack(
children: <Widget>[
PageView.builder(
itemCount: PlantCareApp.plants.length,
controller: pageController,
onPageChanged: (activeCardIndex) {
setState(() {
this.activeCard = activeCardIndex;
});
},
itemBuilder: (context, cardIndex) {
return AnimatedContainer(
padding: (activeCard == cardIndex) ? activeCardPadding : inActiveCardPadding;,
duration: Duration(milliseconds: 250),
child: PlantCard(
PlantCareApp.plants[cardIndex],
onTap: () {
Navigator.pushNamed(context, PlantDetailScreen.route, arguments: PlantCareApp.plants[cardIndex]);
},
onLongPress: () {
setState(() {
//
// ANIMATE OR TRIGGER ANIMATION HERE
//
// do the actual removing
/*
PlantCareApp.plants[cardIndex].remove(); // remove from db
PlantCareApp.plants.removeAt(cardIndex); // remove from List
*/
});
//PlantCareApp.plants[cardIndex].remove();
},
),
);
},
),
],
),
),
);
}
}
Any help will be greatly appreciated! How would you guys tackle a problem like this, or how would you tackle this specific use case.
I guess actually animating viewportFraction would be the nicest because of the adjecent 'Pages' moving toward each other as well?
Thanks!
I'm not certain if this is what you are looking for, but here goes.
One way of doing this is simply using the provided Widgets within Flutter. Two of these will help you out: AnimatedList and Dismissible.
Now, you could do something like this:
// define somewhere
final _animatedListGK = GlobalKey<AnimatedListState>();
// put in a function somewhere
return AnimatedList(
key: _animatedListGK,
padding: const EdgeInsets.all(0),
initialItemCount: PlantCareApp.plants.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: _buildDismissibleRow(context, index, PlantCareApp.plants[index])
);
}
);
Note: you don't have to use the _animatedListGK global key per se, it depends on whether you can use AnimatedList.of(context) or not. Although it is the easier way.
The _animatedListGK is simply a Global Key that provides access to the AnimatedList so you can perform insertions/removals with animation.
Your dismissible row might look something like:
Widget _buildDismissibleRow(BuildContext context, int index, PlantModel plantModel) {
return Dismissible(
key: ValueKey<String>(plantModel.someKey),
direction: DismissDirection.startToEnd,
background: Container(color: Colors.red),
onDismissed: (direction) {
// You could use:
// AnimatedList.of(context)
_animatedListGK.currentState.removeItem(
index,
(context, animation) => Container(),
duration: Duration.zero
);
},
child: _buildContent(context, index, plantModel)
);
}
You could also do it without a dismissible row or even within the child of the dismissible row (_buildContent() for example). Something similar to:
// You could use:
// AnimatedList.of(context)
_animatedListGK.currentState.removeItem(
index,
(context, animation) {
return FadeTransition(
opacity: CurvedAnimation(parent: animation, curve: Interval(0.5, 1.0)),
child: SizeTransition(
sizeFactor: CurvedAnimation(parent: animation, curve: Interval(0.0, 1.0)),
child: _builContent(context, index, plantModel)
)
);
},
duration: const Duration(milliseconds: 300)
);
Notice how the SizeTransition simply "calls itself" by calling _builContent(context, index, plantModel)? That's how you can animate the row itself (out of existence).
Be sure to watch the videos in the aforementioned documentation pages! They will help understanding certain constructs.
A preview of what the dismissible might look like:
A preview of what the SizedTransition might look like: