Animating TransformationController - flutter

So I have a picture in an interactive viewer. I want to reset the Pan and Zoom every time the user is done changing it. I am successfully able to do this using the TransformationController and onInteractionEnd callback. However, of course, the reset is very abrupt, so I would like to animate it to provide a smooth transition. I have no experience with Flutter animations and this is the best I could come up with.
class _PhotoZoom extends State<PhotoZoom> with TickerProviderStateMixin {
TransformationController transformationController =
new TransformationController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: InteractiveViewer(
transformationController: transformationController,
child: Image.network(imageUrl),
onInteractionEnd: (ScaleEndDetails details) {
_resetPanZoom();
},
),
),
],
),
);
}
_resetPanZoom() {
AnimationController animationController =
AnimationController(duration: const Duration(seconds: 2), vsync: this);
Animation<Matrix4> animation = Matrix4Tween(
begin: transformationController.value, end: Matrix4.identity())
.animate(animationController);
transformationController.value = animation.value;
}
}
What I am trying to do is smoothly change the controller's changed Matrix4 variable to Matrix4.identity() which will reset the image to original Pan and Zoom. But yes this doesn't work as I am probably doing something wrong. What would be the right way to do this?

Related

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.

Flutter: Make a custom bottom bar sliding up when it appears

I am trying to design a music playlist page. So far I am able to create a listview with song cards. When I click on any song, a custom bottom bar appears and the audio starts playing. However, I just hold a state with boolean and show the bottom bar according to that. Instead, I want it to appear like sliding up and reach to the position. Let say in 0.5 seconds.
I have a custom NavBar class
class NavBar extends StatefulWidget
And I use this class in build similar to:
return Column(
children: [
SizedBox(
height: constraints.maxHeight * 0.5,
hild: SlidingBanners(),
),
Expanded(
child: Lists(),
),
NavBar()
],
);
How can I such animation?
Use a SizeTransition widget https://api.flutter.dev/flutter/widgets/SizeTransition-class.html
"SizeTransition acts as a ClipRect that animates either its width or
its height, depending upon the value of axis. The alignment of the
child along the axis is specified by the axisAlignment."
Widget build(BuildContext context) {
return SizeTransition(
sizeFactor: CurvedAnimation(
curve: Curves.fastOutSlowIn,
parent: _animationController,
),
child: Container(height: 100, color: Colors.blue)
);
}
init animation controller in stateful widgets initState()
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500));
}
Make sure your stateful widget uses SingleTickerProviderStateMixin
class _NavBarState extends State<NavBar>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
then open and close with
_animationController.forward()
_animationController.reverse()
You can pass the _animationController into the NavBar widget's constructor from its parent if you want the parent to control the animation.
Alternatively you can use an AnimatedContainer widget and setState with its height 0 or 100 depending on if Nav should be shown. This becomes a problem for some widgets that cannot be squished to height of 0 though and I would not recommend for any container that contains anything but text
One solution would be to use a SnackBar widget. Since it's automatically animated, you wouldn't want to worry about manually animating the bottom bar. You can insert your Audio Player (bottom bar) widget to the child of the SizedBox.
The bottom bar is made visible by,
ScaffoldMessenger.of(context).showSnackBar(snackBar);
This bottom bar is dismissed (hidden) by dragging down or by,
ScaffoldMessenger.of(context).hideCurrentSnackBar();
There maybe many other solutions as well to this, but reading your question, I hope this is what you wanted.
return Center(
child: ElevatedButton(
onPressed: () {
final snackBar = SnackBar(
duration: Duration(days: 365),
content: SizedBox(
height: 100,
//insert your audio player widget here
child: Column(
children: [
Text("YOUR AUDIOPLAYER WIDGET HERE"),
Text("Audio Controls Here"),
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
child: Text("Audio Player Minimize"),
),
],
),
),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
child: Text('Open Audio Player'),
),
);

How to play animation when user scrolls to a widget in flutter web

I am creating a simple flutter web page with an architecture like this.
final scrollController = new ScrollController();
return Scaffold(
body: SingleChildScrollView(
controller: scrollController,
child: Column(
children: [
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [SplashInfo(), Blobs()],
)),
Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Performance(
scrollController: scrollController,
),
PortfolioSplash(),
],
)),
],
)));
My goal is to play animations in some Widgets when the user scrolls down to them. Currently I have a solution that involves sending the scrollController in as an argument to the widgets I want to animate. See the Performance() widget.
In the widgets I add listeners to the controller that plays the animation at a hard-coded threshhold. My implementation looks like this:
void initState() {
super.initState();
animationController = AnimationController(vsync: this, duration: _duration)
..addListener(() {
setState(() {});
});
animation = Tween(begin: 0.0, end: 12.5).animate(animationController);
var listener;
removeListener() {
this.widget.scrollController.removeListener(listener);
}
listener = () {
if (this.widget.scrollController.offset >=
MediaQuery.of(context).size.height * 0.3 &&
animationController.status == AnimationStatus.dismissed) {
animationController.forward();
removeListener();
}
};
WidgetsBinding.instance.addPostFrameCallback(
(_) => this.widget.scrollController.addListener(listener));
}
This seems like a bad way to achieve this. Firstly hard-coding the scrolling thresholds. Also, there seems to be some performance issues with this implementation.

Flutter transform animation breaks "on pressed" function of floating action button in bottom nav bar. Workaround needed

I'm currently trying to implement a radial menu appearing when the floating action button in the bottom navigation menu is clicked (image1). The animation and rendering works fine but after the animation, the on pressed button function is no even triggered, when the buttons get clicked. I already read that this is due to the stack, wrapping the buttons and the animation. The area of the transformed buttons is not defined a priori. This causes gesture detection of the Buttons to stay behind the FAB. Wrapping the Stack with a fixed size container solves the problem, however, it totally breaks the layout. Also, the clickable navigations icons in the background are not reachable as the container is laying above the navbar (see image2). Is there a known workaround to fix this problem? I am really looking forward to getting some professional help. :)
Image of not working buttons and desired layout
Image of the broken UI after wrapping the stack with a container. But in this solution button pressed is working
The code is attached here:
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:vector_math/vector_math.dart' show radians;
class RadialFloatingActionButton extends StatefulWidget {
#override
_RadialFloatingActionButtonState createState() => _RadialFloatingActionButtonState();
}
class _RadialFloatingActionButtonState extends State<RadialFloatingActionButton> with
SingleTickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(milliseconds: 1100), vsync: this);
}
#override
Widget build(BuildContext context) {
return RadialAnimation(controller: controller);
}
}
class RadialAnimation extends StatelessWidget {
RadialAnimation({Key key, this.controller})
: scale = Tween<double>(
begin: 1.5,
end: 0.0,
).animate(
CurvedAnimation(parent: controller, curve: Curves.elasticInOut),
),
translation = Tween<double>(
begin: 0.0,
end: 90.0,
).animate(
CurvedAnimation(parent: controller, curve: Curves.easeInOutCirc),
),
super(key: key);
final AnimationController controller;
final Animation<double> scale;
final Animation<double> translation;
build(context) {
return AnimatedBuilder(
animation: controller,
builder: (context, builder) {
return Stack(alignment: Alignment.center, children: [
_buildButton(200, color: Colors.black, emoji: "A"),
_buildButton(245, color: Colors.black, emoji: "B"),
_buildButton(295, color: Colors.black, emoji: "C"),
_buildButtonMore(340, color: Colors.grey),
Transform.scale(
scale: scale.value -
1.5, // subtract the beginning value to run the opposite animation
child: FloatingActionButton(
child: Icon(
Icons.close,
),
onPressed: _close,
backgroundColor: Colors.black),
),
Transform.scale(
scale: scale.value,
child: FloatingActionButton(
child: Icon(
Icons.people,
color: Colors.white,
),
onPressed: _open,
backgroundColor: Colors.black),
)
]);
});
}
_buildButtonMore(double angle, {Color color}) {
final double rad = radians(angle);
return Transform(
transform: Matrix4.identity()
..translate(
(translation.value) * cos(rad), (translation.value) * sin(rad)),
child: FloatingActionButton(
child: Icon(Icons.more_horiz),
backgroundColor: color,
onPressed: _close,
elevation: 0));
}
_buildButton(double angle, {Color color, String emoji}) {
final double rad = radians(angle);
return Transform(
transform: Matrix4.identity()
..translate(
(translation.value) * cos(rad), (translation.value) * sin(rad)),
child: FloatingActionButton(
child: Center(
child: new Text(
emoji,
style: TextStyle(fontSize: 30),
textAlign: TextAlign.center,
),
),
backgroundColor: color,
onPressed: _close,
elevation: 0));
}
_open() {
controller.forward();
}
_close() {
controller.reverse();
}
}
I am not able to test right now, but wrapping in a container seems like the proper workaround, as Transforms seems to not apply to hit test behaviors in unbounding boxes (thats why Container makes it work). Maybe there’s a way (some sort of widget or property) that allows hitTestBehavior like GestureDetector where you can set it to “opaque areas” instead the entire bounding box of the Container capturing the touches.
Sorry cause i cant provide you an answer directly, will check it out later on the computer :)

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)