Flutter current screen out, new in Animation - flutter

I need to animate current screen A out, then screen B in. PageRouteBuilder, transition:
#override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
var animCurrentWidget = Tween<Offset>(begin: const Offset(0, 0), end: const Offset(0, 1))
.chain(CurveTween(curve: const Interval(0, 0.5, curve: Curves.linear)))
.animate(animation);
var animNewWidget = Tween<Offset>(begin: const Offset(0, 1), end: const Offset(0, 0))
.chain(CurveTween(curve: const Interval(0.5, 1, curve: Curves.linear)))
.animate(animation);
return Stack(
children: <Widget>[
SlideTransition(position: animCurrentWidget, child: parent, transformHitTests: false),
SlideTransition(position: animNewWidget, child: child),
],
);
}
Pretty simple, current screen leaves with up->down, new one goes in with sliding down->up after first one left.
But the tricky part is that you need to have access to current Widget on screen to do that, which removes use of
onGenerateRoute: MyCustomRouting.generateRoute
And forces me to work with routes in local scope of every possible screen app can have.
What are the best practices for tasks like that?
Is there a way to get current widget inside MyCustomRouting.generateRoute without going full static?
Is there a way to get current 'on top' widget inside other widget in the tree?

Related

Flutter Hero animate 90 degree rotation

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',
),
),
),
),

Why isn't my Flutter animated checkmark working?

I'm trying to replicate the following animation from dribble.com:
I've gotten the ScaleTransition to work but the SizeTransition does not. What am I doing wrong or what don't I understand?
When I only swap out the SizeTransition with a FadeTransition (and keep the same controllers & animations), the animations both run. When I move the Center widget from being a child of the SizeTransition to the parent the animation runs.
However it is not properly centered.
import 'package:flutter/material.dart';
class AnimatedCheck extends StatefulWidget {
#override
_AnimatedCheckState createState() => _AnimatedCheckState();
}
class _AnimatedCheckState extends State<AnimatedCheck> with TickerProviderStateMixin {
late AnimationController scaleController = AnimationController(duration: const Duration(milliseconds: 800), vsync: this);
late Animation<double> scaleAnimation = CurvedAnimation(parent: scaleController, curve: Curves.elasticOut);
late AnimationController checkController = AnimationController(duration: const Duration(milliseconds: 400), vsync: this);
late Animation<double> checkAnimation = CurvedAnimation(parent: checkController, curve: Curves.linear);
#override
void initState() {
super.initState();
scaleController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
checkController.forward();
}
});
scaleController.forward();
}
#override
void dispose() {
scaleController.dispose();
checkController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
double circleSize = 140;
double iconSize = 108;
return ScaleTransition(
scale: scaleAnimation,
child: Container(
height: circleSize,
width: circleSize,
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
child: SizeTransition(
sizeFactor: checkAnimation,
axis: Axis.horizontal,
axisAlignment: -1,
child: Center(
child: Icon(Icons.check, color: Colors.white, size: iconSize)
)
),
),
);
}
}
When you move the Center to the outside, it doesn't center the children of it's children, just puts it's child in the center of the area it occupies. Try setting the Container alignment using alignment: Alignment.center, inside of the Container. Please let me know if it doesn't work and I'll replicate the code to figure out the problem is.
Edit: This is because there is space around the visual check you see, the SizeTransition doesn't know this so it animates from the edge of the Icon which causes the visual bug you see. I'd recommend you use a tool like Rive (rive.app) to accomplish what you want, alternatively, you can use another animation or use some clunky workaround like animating the position while the size transition occurs so that it appears to be centered.
Hi if this is an issue for you, I figured out how both animations will be triggerd.
See code below.
Stack(
children: [
Center(
child: ScaleTransition(
scale: scaleAnimation,
child: Container(
height: circleSize,
width: circleSize,
decoration: BoxDecoration(
color: kApprovedColor,
shape: BoxShape.circle,
),
),
),
),
SizeTransition(
sizeFactor: checkAnimation,
axis: Axis.horizontal,
axisAlignment: -1,
child: Center(
child: Icon(Icons.check, color: Colors.white, size: iconSize),
),
),
],
),
Flutter's documentation has an explanation:
Like most widgets, SizeTransition will conform to the constraints it
is given, so be sure to put it in a context where it can change size.
For instance, if you place it into a Container with a fixed size, then
the SizeTransition will not be able to change size, and will appear to
do nothing.
Thank you. Now I know what I'm doing wrong.

Make a flutter custom transition like page view

I want to make a custom transition like PageView in flutter when navigate to another page like image below but I do not know how. I used SlideTransitiondid but it does not give me the desired result
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class SlideTransitions extends CustomTransition {
#override
Widget buildTransition(
BuildContext context,
Curve curve,
Alignment alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return Align(
alignment: Alignment.center,
child: SlideTransition(
position: Tween(begin: Offset(1.0, 0.0), end: Offset(0.0, 0.0))
.animate(animation),
child: child,
),
);
}
}
pageview transition image
you can use page_transition PageTransitionType.rightToLeft . It's kinda similar to pageview swipe

Flutter PageView, can i animate removing items from list?

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:

Why can't I center a SizeTransition horizontally in a Column in Flutter?

Please check the following code. Regardless what I tried, the SizeTransition is not centered horizontally in the Column. I tried to wrap Column in a Container and then provide infinite width. I tried to wrap SizeTransition in a Center. I tried to wrap SizeTransition in a Container which has center alignment property. I tried to wrap it in a Stack. I tried to give the container child with alignment center property etc... But none of them works...
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: AnimatedBox(),
);
}
}
class AnimatedBox extends StatefulWidget {
#override
createState() => _AnimatedBoxState();
}
class _AnimatedBoxState extends State<AnimatedBox> with SingleTickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 400),
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
RaisedButton(
child: Text('animate forward'),
onPressed: () {_controller.forward();},
),
RaisedButton(
child: Text('animate reverse'),
onPressed: () {_controller.reverse();},
),
const SizedBox(height: 100.0,),
SizeTransition(
child: Container(
width: 200.0,
height: 200.0,
color: Colors.blue,
),
sizeFactor: CurvedAnimation(
curve: Curves.fastOutSlowIn,
parent: _controller,
),
),
],
);
}
}
For instance, the following code does not work for SizeTransition, but works for ScaleTransition. I have no idea what's wrong with SizeTransition.
return Container(
width: double.infinity,
child: Column(
Despite the fact that my previous answer solves the problem to some extent, I also wanted to address how limited SizeTransition widget is and how to solve this.
SizeTransition provides the effect of "unfolding" its content, running the animation either in horizontal or in vertical axis by rewriting alignment settings.
To achieve the same effect without breaking alignment rules, but also avoid using ScaleTransition widget as we need the "unfold/reveal" animation and not "scale up" - here is what I propose:
#override
Widget build(BuildContext context) {
final _animation = CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn);
return Column(
children: <Widget>[
// ...,
AnimatedBuilder(
animation: _animation,
builder: (_, child) => ClipRect(
child: Align(
alignment: Alignment.center,
heightFactor: _animation.value,
widthFactor: null,
child: child,
),
),
child: Container(
width: 200.0,
height: 200.0,
color: Colors.blue,
child: Text("test"),
),
)
]
);
}
This is basically an AnimatedBuilder widget with the same ClipRect & Align used as in SizeTransition, except that it does limit alignment to one axis only.
If you'd like the animation to run in both horizontal & vertical axes - assign the same _animation.value to widthFactor property:
Align(
alignment: Alignment.center,
heightFactor: _animation.value,
widthFactor: _animation.value,
child: child,
),
This will help you achieve "reveal from center" effect without scaling up & down the content of your widget.
I can see that you tried many things already, here is some ideas I have:
#1
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: // ...
)
or
#2
Column(
crossAxisAlignment: CrossAxisAlignment.stretch, // critical
children: <Widget>[
// ...,
Container(
alignment: Alignment.center, // critical
child: SizeTransition(
child: Container(
width: 200.0,
height: 200.0,
color: Colors.blue,
),
sizeFactor: CurvedAnimation(
curve: Curves.fastOutSlowIn,
parent: _controller,
),
),
),
]
)
Update
There is indeed a peculiar aspect of SizeTransition widget.
It has axis property that is set to Axis.vetical by default, which overrides the widget's horizontal alignment to -1.0 (start) and vertical alignment to 0.0 (center).
Changing that property to Axis.horizontal makes things work the other way around - aligning the widget horizontally to 0.0 (center) and vertically to -1.0 (start).
Solution:
SizeTransition(
axis: Axis.horizontal,
// ...,
)
Please let me know if this helped.
If you are using animated list view, you can use slide transition instead, just wrap your column around a slide transition widget and you should be good to go
SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, -1), //The animated item will move upwards, use +1 to move downwards
end: Offset.zero,
).animate(
CurvedAnimation(parent: widget.animation, curve: Curves.fastOutSlowIn),
),
child: Column()
)
widget.animation is your Animation<double> variable (if inside stateful widget)