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.
Related
I am using IconButtons in my application for opening popups, deleting ListTiles from ListViews, navigating to other views etc.
To my surprise the onPressed callback always gets triggered before the animation starts to play resulting in that the animation will not play at all. This is confusing to the user because there is no feedback that he pushed the button.
The setup looks something like this.
This is the code for the add button in the top right corner:
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AddDevicePopup(
...
).build(context);
});
},
icon: const Icon(
Icons.add_circle_outline_rounded,
size: 30.0,
)
)
When the user taps the button the popup immediately appears but the animation won't get played.
This is the same for the IconButtons on the tiles:
the edit button opens a popup immediately and the ripple or splash animation won't play.
the delete button removes the element from the list. In this case the ListTile element gets removed and so the IconButton disappears without the animation getting played.
In every case there is a Material widget that is an ancestor of the IconButton so wrapping it in a Material widget does not solve the problem. I have double checked it by removing the showDialog part from the callback, in these cases all of the IconButtons play the splash / ripple animation as expected.
How do I make the onPressed to wait for the animation to at least be started. I did not find any solution. Is is possible to trigger this animation via code? In that case I can add that to the beginning of the onPressed callback.
To add animations, you must use showGeneralDialog instead of showDialog.
The implementation looks like this:
showGeneralDialog(
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: const Duration(milliseconds: 200),
context: context,
pageBuilder: (_, __, ___) {
return AddDevicePopup(
...
).build(context);
},
//Customize your animation here
transitionBuilder: (_, a1, __, child) {
return Transform.scale(
scale: a1.value,
child: Opacity(
opacity: a1.value,
child: child,
),
);
},
);
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 am trying to achieve the look displayed on the Animations package's site on pub.dev.
I included a gif on what I am trying to achieve:
I tried to implement this but am having a hard time as each Step is not a widget. I first implemented like below, but it makes the entire screen (including the vertical line) rebuild.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: const FliAppBar(),
body: PageTransitionSwitcher(
duration: const Duration(seconds: 1),
transitionBuilder: (child, anim, sec) => SharedAxisTransition(
transitionType: SharedAxisTransitionType.vertical,
animation: anim,
secondaryAnimation: sec,
child: child,
),
child: Stepper(
key: ValueKey(stepperIndex),
currentStep: stepperIndex,
steps: steps,
),
),
);
}
Any ideas on how to achieve the look that is displayed on the gif using a SharedAxisTransition from the Animations package?
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?
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: