OnPressed not working inside Stack widget after Transforming a widget - flutter

In the given code,onPressed on the raised button works and translate FlatButton to the top. But onPressed on FlatButton is not working
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Transform(
transform: Matrix4.translationValues(
0.0,
_translateButton.value,
0.0,
),
child: FlatButton(
onPressed: () {
print('tapped Flat button');
},
child: Text('upper'),
),
),
RaisedButton(
onPressed: () {
animate();
print('tapped Raised button');
},
child: Text('lower'))
],
);
}
Here _translatebutton value changes from 0 to -60 when animate() is called
_animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 500))
..addListener(() {
setState(() {});
});
_translateButton = Tween<double>(
begin: 0,
end: -60,
).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(
0.0,
0.75,
curve: _curve,
),
));

Wrap the Transform widget in a SizedBox (expanded or from size, depending on your requirement.

I came across this problem last week and in my case, the composition was like this:
Stack(
children: [
Widget0,
Widget1,
Opacity(
opacity: sth,
child: Transform.translate(
offset: Offset(sth),
child: Transform.rotate(
angle:sth,
child: FlatButton(
onPressed: (){sth},
child: Text("sth"),
),
),
),
),
],
)
Based on the suggestion of rahulthakur319 on the issue number
27587
I wrapped my Transform.translate composition inside a new Stack and I wrapped the stack inside a Container. Remember that the new Container should have enough width and height to show its child. I personally used MediaQuery.of(context).size.
it's working even during the complex series of animations.
The final code:
Stack(
children: [
Widget0,
Widget1,
Opacity(
opacity: sth,
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Stack(
children: [
Transform.translate(
offset: Offset(sth),
child: Transform.rotate(
angle: sth,
child: FlatButton(
onPressed: (){sth},
child: Text("sth"),
),
),
),
],
),
),
),
],
)

If your translation moves the button outside the area of the stack, the button no longer reacts to clicks. A simple way to test this is to wrap your Stack widget in a container and color it blue (or anything obvious), and if your button is moved outside the blue area, you know it's losing its clickability because it's outside of the Stack.
If this indeed is the issue, the solution is to keep the Stack inside the container, and then either set the container dimensions such that the button still stays within the border after translation, or reposition widgets relative to the container such that the translation stays within the border.

If someone is still trying to solve this issue, I solved it by wrapping the widget with IgnorePointer widget on which I don't want the pointer to reach.
reference from here

The answer I got was that in a View if an element is translated then the animation works correct but the click property is altered in someway that we can't use it after translating the element

I had this same issue my Switch widget was not working in the Stack.
The solution i found was to include it in SizeBox or Container with fixed width and height.
if your switch widget is in Row try to add Constraints on Row with SizeBox rather than adding it in every widget.

Related

Flutter - can't scroll to the bottom of listView

I have code like this:
Column(children: [
Container(
alignment: Alignment.centerLeft,
child: IconButton(
color: theme.iconTheme.color,
icon: const Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
},
),
),
Expanded(
child: ListView(
children: allOperations,
controller: listViewController,
),
),
]);
and I want to scroll to the bottom after widget is built.
WidgetsBinding.instance?.addPostFrameCallback((_) {
listViewController.jumpTo(listViewController.position.maxScrollExtent);
});
but it doesn't work. Always remain about 150px to scroll down.
Anyone had this problem?
UPDATE
It remains number of pixels to scroll down depending on number of element in ListView. For example if I have 20 elements there will remain about 50px to bottom if 30 then about 80px.
Problem example (let assume widget take all height of screen and if is higher than screen then it can be scrolled):
full height of ListView: 2000px
screen height: 600px
maxScrollExtent should be 1400px but is 1300px.
Have you tried to leave some white space below your list? This is typically a SizedBox and it would allow you to see the rest of your list and then leave some more depending on the size you choose.
adding Timer before scroll may solve your problem, I guess listview needs some time to add a new item.
Timer(Duration(milliseconds: 100), () {
WidgetsBinding.instance?.addPostFrameCallback((_) {
listViewController.jumpTo(listViewController.position.maxScrollExtent);
});
});
you can also try "animateTo" instead:
Timer(Duration(milliseconds: 100), () {
listViewController.animateTo(
listViewController.position.maxScrollExtent,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 750),
);
});

Flutter "Catch up" animation on drag

I'm currently building a flutter app where a user can drag a circle around the screen. I use a Listener widget to get the current pointer position, then use a transform widget to position it accordingly.
class _DraggingExample extends State<VelocityAnimations>{
var offset = Offset(0, 0);
#override
Widget build(BuildContext context) {
return Scaffold(
// I get pointer-events here.
body: Listener(
onPointerMove: (e) {
setState(() {
// - 50 to center the container
offset = e.localPosition - Offset(50, 50);
});
},
behavior: HitTestBehavior.opaque,
child: SizedBox.expand(
child: Stack(
children: [
Center(child: Text('Hello, World!')),
// I transform the container here.
Transform.translate(
offset: offset,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(50),
),
),
)
],
),
),
),
);
}
}
It works perfectly. But it's very bland. It would be much nicer if when I move my pointer/finger, the container gradually catches up to the finger. The effect I'm looking for can be seen in this codepen. Here, the black circle gradually catches up with the mouse as you move it along.
I've tried using a Physics Simulation and plain animations, but I can't figure out how to do it.
You can try to use AnimatedPositioned which animates its position implicitly.
The animation can be configured with duration and Curves as you want.
AnimatedPositioned(
left: offset.dx,
top: offset.dy,
curve: Curves.easeOutCirc,
duration: Duration(milliseconds: 400),
child: Container(
// ...
),
),

How to rotate a larger-than-screen image without the overflow being clipped off in Flutter?

I have this image that I would like to display full screen and rotate in the background:
Here it is filling the screen correctly:
The problem is, when it rotates, the sides have been clipped off:
I've tried every type of box fit. I've tried sizing the container width to double.infinity. I've tried wrapping the image in a SingleChildScrollView. I've tried putting overflow: Overflow.visible on the stack. All day trying things, but nothing seems to be working.
The image needs to continuously fill the screen while rotating. How can I code it so that the edges aren't clipped off?
Here's my code:
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Stack(
children: <Widget>[
SpinPerfect(
infinite: true,
duration: Duration(seconds: 10),
child: Image.asset(
'assets/images/star-burst.png',
fit: BoxFit.none,
),
),
Container(
child: Center(
child: Text('This is Screen 1'),
),
),
],
),
),
);
}
}
Note: I am currently rotating it using SpinPerfect from the animate_do package, but the same clipping problem happens when using Transform.rotate.
Thanks in advance for any direction!
Here is a solution that works very well:
Use SpinPerfect from the animate_do package (https://pub.dev/packages/animate_do) in combination with the photo_view package (https://pub.dev/packages/photo_view).
SpinPerfect(
infinite: true,
spins: 1,
duration: Duration(seconds: 60),
child: PhotoView(
disableGestures: true,
backgroundDecoration: BoxDecoration(color: Colors.transparent),
initialScale: PhotoViewComputedScale.covered * 2.7,
imageProvider: AssetImage('assets/images/background.jpg'),
),
)
Thanks to the creator of the animate_do package for the idea. (Very cool dude!)

Weird animation behaviour of AnimatedContainer when wrapped in IntrinsicHeight

I have created a container in which two AnimatedContainers are located. When i press the down button i would like the TextFieldContainer (The content with the white TextField in the middle) to disappear by decreasing it size to 0. Simountanesly i want to make the ToolbarContainer appear by increasing its size. So far this is what i get:
It looks like only one of the AnimatedContainers can animate its properties one at a time, which creates the werid behaviour as shown, where one of the AnimatedContainers animate quickly, then slowly and then quickly once again.
How do i make the animation smooth so the overall size of the main container does not change because the two AnimatedContainers animate their size equally quick and simountanesly?
Here is the code for the main container:
class _MessageToolbarContainerState extends State<MessageToolbarContainer> {
bool toggleToolbar = false;
#override
Widget build(BuildContext context) {
return Container(
width: widget.availableWidth - 75,
margin: EdgeInsets.only(bottom: 25.0),
constraints: BoxConstraints(minHeight: 50.0),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.all(Radius.circular(25.0)),
border: Border.all(
color: Colors.grey,
width: 2.0,
),
),
child: Material(
borderRadius: BorderRadius.all(Radius.circular(25.0)),
elevation: 5.0,
clipBehavior: Clip.antiAlias,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
AnimatedContainer(
duration: Duration(seconds: 1),
constraints: BoxConstraints(
minHeight: toggleToolbar ? 0.0 : 50.0,
maxHeight: toggleToolbar ? 0.0 : 100.0,
),
child: IntrinsicHeight(
child: TextFieldContent(
onDownPressed: () {
setState(() {
toggleToolbar = !toggleToolbar;
print(toggleToolbar);
});
},
),
),
),
AnimatedContainer(
duration: Duration(seconds: 1),
constraints: BoxConstraints(
minHeight: !toggleToolbar ? 0.0 : 50.0,
maxHeight: !toggleToolbar ? 0.0 : 100.0,
),
child: IntrinsicHeight(
child: ToolbarContent(
onUpPressed: () {
setState(() {
toggleToolbar = !toggleToolbar;
print(toggleToolbar);
});
},
),
),
),
],
),
),
);
}
}
EDIT:
I have boiled the problem down to one widget. I have wraped my AnimatedContainers in two IntrinsicHeight widgets to keep their content from expanding to much. If i remove the IntrinsicHeight widgets i get the following output:
As it is clearly shown the AnimatedContainers now animate smoothly. Sadly that does not fix my problem. I added the IntrinsicHeight widgets to make the content expand as the user typed in the TextField. If i remove the IntrinsicHeight widgets the content of the AnimatedContainers are not displayed properly.
The original question still stands. How do I make the 2 AnimatedContainers animate properly when they are wrapped in an IntrinsicHeight widget?

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)