I am trying to use an AnimatedSwitcher within Stack. This leads to very strange behaviour of the animation. It animates the respective child widget (a red box in my case) in the center of my Stack and upon completion it snaps to the top left corner of my screen(which is where I would also like the animation to to take place). When I switch back, the same odd behaviour occurs.
My code looks as follows:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: Home()));
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool _showMenu = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Stack(
children: [
GestureDetector(
onTap: () => setState(() => _showMenu = !_showMenu),
child: SizedBox.expand(
child: Container(
color: Colors.yellow,
),
),
),
AnimatedSwitcher(
duration: Duration(milliseconds: 500),
child: _showMenu
? Container(
key: UniqueKey(),
height: 200,
width: 200,
color: Colors.red,
)
: Container())
],
),
),
);
}
}
Which produces the following behaviour on the tap-event somewhere on the screen:
Any ideas why the red box is not animated in the top left corner but only goes there once the animation has finished?
The problem lies, as #Marino Zorilla pointed out, in the unique key I specified for my animating widget. Once I removed this key and also changed the "empty" Container (for the false-condition of my ternary operation) to a SizedBox it works as desired.
Apparently, this has to do with how flutter works internally (when the element tree and the widget tree are compared to determine which widgets need to be rebuild). If the widget changes to a different type (like in my case from Container to SizedBox) no key is needed for flutter to know that this widget needs to be rebuild.
The correct code looks as follows:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: Home()));
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool _showBox = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Stack(
children: [
GestureDetector(
onTap: () => setState(() => _showBox = !_showBox),
child: SizedBox.expand(
child: Container(
color: Colors.yellow,
),
),
),
AnimatedSwitcher(
duration: Duration(milliseconds: 500),
child: _showBox
? Container(
height: 200.0,
width: 200.0,
color: Colors.red,
)
: SizedBox(),
)
],
)),
);
}
}
Related
I've spent a lot of time trying to implement one thing, which is
a button that pops up in the center of a picture when the user's mouse enters the picture block, and when the mouse exits, button should disappear.
I've tried using many different widgets, such as InkWell and MouseArea but sadly didn't get expected behavior
Please share your thoughts if you know how to implement such behavior in flutter
Any help appreciated!
Instead of using AnimatedContainer, you could use AnimatedSwitcher which switches between a Button and a non-visible SizedBox whenever the onEnter or onExit event is called.
Just change the container to the picture you want to show.
Code example with DartPad:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool showButton = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: Scaffold(
appBar: AppBar(
title: const Text('Material App Bar'),
),
body: Center(
child: MouseRegion(
onEnter: (_) => setState(() {
showButton = true;
}),
onExit: (_) => setState(() {
showButton = false;
}),
child: Stack(
alignment: Alignment.center,
children: [
Container(
color: Colors.red[200],
width: 300,
height: 300,
),
AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
child: showButton ? ElevatedButton(
onPressed: () => print("Hi!"),
child: const Text("Hello")
) : const SizedBox.shrink(),
)
],
),
),
),
),
);
}
}
How to clip Stack children within it's size.
In this image there are 3 grid-Items using orange color and every item using InkWell to use hover-Method to Align on Stack. While hover:false the Pop PoP Widget won't be visible to the UI. With align property it works, but as you can see the Right Top GridItem's item:2 pop POp widget is visible outside the Stack<Griditem> and I want to make it invisible outside the stack. I've tested using clipBehavior: with every Clip enums.
I want to hide the Pop POp widget while it is outside the Stack and yes I need this pop-up effect.
For Flutter web and I'm using Flutter V2.5.2
Current Layout with Issue
Full Code to reproduce the issue
import 'package:flutter/material.dart';
void main() => runApp(
const MaterialApp(
home: Appp(),
),
);
class Appp extends StatelessWidget {
const Appp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const BodyX();
}
}
class BodyX extends StatelessWidget {
const BodyX({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(body: LayoutBuilder(
builder: (context, constraints) {
return GridView.count(
crossAxisCount: 2,
children: [
...List.generate(
3,
(index) => GridItem(
key: UniqueKey(),
maxWidth: constraints.maxWidth / 2,
),
),
],
);
},
));
}
}
class GridItem extends StatefulWidget {
const GridItem({
Key? key,
required this.maxWidth,
}) : super(key: key);
final double maxWidth;
#override
State<GridItem> createState() => _AppXState();
}
class _AppXState extends State<GridItem> {
bool _isHovered = false;
#override
Widget build(BuildContext context) {
print("ItemWidth : ${widget.maxWidth}");
return SizedBox(
//though it wont effect here,
// just finding the size of Grid because it will 1x1
width: widget.maxWidth,
height: widget.maxWidth,
child: InkWell(
onTap: () {},
hoverColor: Colors.black,
onHover: (value) {
setState(() {
_isHovered = value;
});
},
child: Stack(
clipBehavior: Clip.antiAliasWithSaveLayer,
children: [
Container(
color: Colors.deepOrange.withOpacity(.2),
),
AnimatedAlign(
alignment: Alignment(0, _isHovered ? .7 : 2),
child: Container(
padding: const EdgeInsets.all(22),
color: Colors.greenAccent,
child: const Text(
"Pop POp",
),
),
duration: const Duration(
milliseconds: 200,
),
)
],
),
),
);
}
}
If you don't want a Widget to draw beyond its layout size, you can use ClipRect to clip it.
In your case, you can wrap ClipRect on your Stack, like so:
ClipRect(
child: Stack(
children: ...
),
)
Further more, you can use ClipRRect to clip a rounded rectangle shape (circular border) or ClipPath to clip a custom shape, like a triangle. You can read more about these widgets in the official docs.
This sounds a strange thing to ask! But I have two drag targets, and two draggables. I want them to start off one in each target. Then, when one is drag&dropped onto the second target, the second target's draggable needs to jump across to the first target.
I guess the problem resolves down to whether you can programmatically put a draggable inside a target. Does this look possible, or is a DragTarget not suitable?
Some example code - I can't start the draggables inside the dragtargets
import 'package:flutter/material.dart';
MyDraggable drag1 = new MyDraggable(Colors.red);
MyDraggable drag2 = new MyDraggable(Colors.green);
MyDragTarget target1 = new MyDragTarget();
MyDragTarget target2 = new MyDragTarget();
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(fontFamily: 'PressStart'),
home: MyHomeScreen(),
);
}
}
class MyHomeScreen extends StatefulWidget {
MyHomeScreen({Key key}) : super(key: key);
createState() => MyHomeScreenState();
}
class MyHomeScreenState extends State<MyHomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.red[100],
appBar: AppBar(
toolbarHeight: 70.0,
title: Center(child: Text('Swap the draggables')),
backgroundColor: Colors.pink,
),
body: Container(
color: Colors.yellow[200],
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [target1, drag1, drag2, target2],
// children: [target1, SizedBox(width: 100), target2],
),
),
Text('Q1: Can the Draggables be started in the DragTargets?'),
Text('Q2: If you drag from one target to the other, '
'can the second swap to the other target?'),
],
),
),
);
} // End build()
} // End class MyHomeScreenState
class MyDragTarget extends StatelessWidget {
bool dragAccepted = false;
Color acceptedColor;
#override
Widget build(BuildContext context) {
return Stack(children: [
Container(color: Colors.blue,height: 90.0,width: 90.0,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: DragTarget<Color>(
builder: (context, List<Color> acceptedData, rejectedData) {
if (dragAccepted) {
return MyDraggable(acceptedColor);
} else {
return Container(color: Colors.grey,height: 50,width: 50,);
}
},
onWillAccept: (aColor) {
acceptedColor = aColor;
return true;
},
onMove: (moveData) {},
onAccept: (aColor) { dragAccepted = true; },
onLeave: (data) {},
),
),
),
]);
} // Build
} // End Class MyDragTarget
class MyDraggable extends StatefulWidget {
MyDraggable(this.color);
final Color color;
#override
_MyDraggableState createState() => _MyDraggableState();
}
class _MyDraggableState extends State<MyDraggable> {
#override
Widget build(BuildContext context) {
return Draggable(
data: widget.color,
child: Container(color: widget.color, height: 50, width: 50),
feedback: Container(color: widget.color, height: 50, width: 50),
childWhenDragging:
Container(color: Colors.pink[100], height: 50, width: 50),
onDragStarted: () {},
onDragEnd: (dragDetails) {
setState(() {});
},
onDragCompleted: () {},
onDraggableCanceled: (velocity, offset) {},
);
}
}
For the default Draggable from Flutter SDK, there is no method provided that allowed us to modify the offset/ position of the Draggable displaying on the UI (all those infos are stored in DraggableDetails of a Draggable).
With that said, there are 2 options you can try out:
Create a custom Draggable class that allows for manipulating its position on UI programatically
Change the property of the children of Draggable widget based on the onWillAccept callback of the DragTarget class (the color of each Container in this case)
My Scaffold displays a bar at the bottom like this: that I did not put there (I know that it is the scaffold because when I remove it the bar is gone. but I cant do this without scaffold) this is my code:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
void initState() {
// TODO: implement initState
super.initState();
SystemChrome.setEnabledSystemUIOverlays([]);
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.red,
body: Column(
children: <Widget>[
Center(
child: Text('Scaffold Bar test'),
),
Container(
height: MediaQuery.of(context).size.height - 88,
child: ListView.builder(itemBuilder: (BuildContext context,int index) {
return Container(
height: 40.0,
width: MediaQuery.of(context).size.width,
color: index % 2 == 0 ? Colors.blue : Colors.orange,
);
}),
),
],
),
);
}
}
Set resizeToAvoidBottomPadding: false in the Scaffold.
And use MediaQuery.removePadding with removeTop: true to remove unnecessary padding at top of the ListView.builder.
Use Expanded instead of getting height from MediaQuery.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
void initState() {
// TODO: implement initState
super.initState();
SystemChrome.setEnabledSystemUIOverlays([]);
}
#override
Widget build(BuildContext context) {
return MediaQuery.removePadding(
context: context,
removeTop: true,
child: Scaffold(
backgroundColor: Colors.red,
resizeToAvoidBottomPadding: false,
body: Column(
children: <Widget>[
Center(
child: Text('Scaffold Bar test'),
),
Expanded(
child: ListView.builder(itemBuilder: (BuildContext context,int index) {
return Container(
height: 40.0,
width: MediaQuery.of(context).size.width,
color: index % 2 == 0 ? Colors.blue : Colors.orange,
);
}),
),
],
),
),
);
}
}
I would like to make something like this:
https://youtu.be/W3O0077GMlo
And I would like for the rotating circle (moon in this video) to act as a button.
What is the best way to do this performance wise?
You can use the RotationTransition inside a Stack widget to create the rotating animation. Inside the Stackset the alignment to center, and wrap your rotating widget inside an Align. Set the alignment attribute of the Align widget to Alignment.topCenter or any outer alignment.
Remember to deploy on release to your phone to make sure the animations are running smooth.
Quick standalone code example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(width: 300.0, height: 300.0, child: OrbitingButton()),
),
),
);
}
}
class OrbitingButton extends StatefulWidget {
#override
_OrbitingButtonState createState() => _OrbitingButtonState();
}
class _OrbitingButtonState extends State<OrbitingButton>
with SingleTickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(vsync: this);
controller.repeat(min: 0.0, max: 1.0, period: Duration(seconds: 1));
}
#override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: <Widget>[
RotationTransition(
turns: controller,
child: Align(
alignment: Alignment.topCenter,
child: Container(
color: Colors.green,
height: 30.0,
width: 30.0,
),
),
),
RaisedButton(
child: Text('Button'),
)
],
);
}
}