What is the best way to make an orbiting button - flutter

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'),
)
],
);
}
}

Related

Flutter: AnimatedSwitcher in Stack not animating in desired position

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(),
)
],
)),
);
}
}

How to animate the swap of 2 items in a Row?

I want to make something very simple. There's a Row with 2 widgets. When I press a button, they swap orders. I want this order swap to be animated.
I've loked at AnimatedPositioned but it requires a Stack. What would be the best way of doing such thing?
I thought Animating position across row cells in Flutter answered this but it's another different problem
You can easily animate widgets in a Row with SlideAnimation. Please see the code below or you may directly run the code on DartPad https://dartpad.dev/e5d9d2c9c6da54b3f76361eac449ce42 Just tap on the colored box to swap their positions with an slide animation.
SlideAnimation
Animates the position of a widget relative to its normal position.
The translation is expressed as an Offset scaled to the child's size.
For example, an Offset with a dx of 0.25 will result in a horizontal
translation of one quarter the width of the child.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
AnimationController _controller;
List<Animation<Offset>> _offsetAnimation;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_offsetAnimation = List.generate(
2,
(index) => Tween<Offset>(
begin: const Offset(0.0, 0.0),
end: Offset(index == 0 ? 1 : -1, 0.0),
).animate(_controller),
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
void _animate() {
_controller.status == AnimationStatus.completed
? _controller.reverse()
: _controller.forward();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Flutter Demo Row Animation")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
BoxWidget(
callBack: _animate,
text: "1",
color: Colors.red,
position: _offsetAnimation[0],
),
BoxWidget(
callBack: _animate,
text: "2",
color: Colors.blue,
position: _offsetAnimation[1],
)
],
),
RaisedButton(
onPressed: _animate,
child: const Text("Swap"),
)
],
),
),
);
}
}
class BoxWidget extends StatelessWidget {
final Animation<Offset> position;
final Function callBack;
final String text;
final Color color;
const BoxWidget(
{Key key, this.position, this.callBack, this.text, this.color})
: super(key: key);
#override
Widget build(BuildContext context) {
return SlideTransition(
position: position,
child: GestureDetector(
onTap: () => callBack(),
child: Container(
margin: const EdgeInsets.all(10),
height: 50,
width: 50,
color: color,
child: Center(
child: Container(
height: 20,
width: 20,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: Center(child: Text(text)),
),
),
),
),
);
}
}

Animating position across row cells in Flutter

I have a Row Widget that holds a child Widget in one of its cells (the others hold a Spacer()). Depending on the state, the cell which holds the child changes, resulting in a position change of the child Widget. I want to animate this motion to make it smooth. Is there a way of doing so with the standard animation Widgets (something like AnimatedPositioned, which won't work in this case)?
You could use AnimatedPositioned if you insert a Stack inside your row.
Otherwise, you can use an AnimatedBuilder with Transform.translate and animate the Offset on the axis X of your widget. Bellow there's a complete example, that you can run at DartPad to see the result. Hope it helps.
import 'package:flutter/material.dart';
void main(){
runApp(MaterialApp(home: MyAnimation()));
}
class MyAnimation extends StatefulWidget {
#override
_MyAnimationState createState() => _MyAnimationState();
}
class _MyAnimationState extends State<MyAnimation> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<double> animation;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 5000));
animation = Tween(begin: 0.0, end: 300.0).animate(_controller);
_controller.forward();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Title")),
body: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Row(
children: <Widget>[
Transform.translate(
child: Container(width: 100, height: 100, color: Colors.black),
offset: Offset(animation.value, 0),
),
Container(width: 100, height: 100, color: Colors.transparent),
Container(width: 100, height: 100, color: Colors.transparent),]
);
},
),
);
}
}

Flutter - How to add a label that follows the progress position in a LinearProgressIndicator

The title is pretty self explanatory I think.
Basically I need to have a LinearProgressIndicator with a label in the same position as the current progress. Like this:
I suppose I need to use a Stack to create the Text, but how can I position it based on the progress of the bar?
You can use Align widget to align the text in the stack. Use alignment property as Alignment.lerp(Alignment.topLeft, Alignment.topRight, _progressValue);
The progress value should be from 0 to 1
https://dartpad.dev/bbc452ca5e8370bf2fbf48d34d82eb93
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
debugShowCheckedModeBanner: false,
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Slider Demo'),
),
body: new Container(
color: Colors.blueAccent,
padding: new EdgeInsets.all(32.0),
child: new ProgressIndicatorDemo(),
),
);
}
}
class ProgressIndicatorDemo extends StatefulWidget {
#override
_ProgressIndicatorDemoState createState() =>
new _ProgressIndicatorDemoState();
}
class _ProgressIndicatorDemoState extends State<ProgressIndicatorDemo>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
animation = Tween(begin: 0.0, end: 1.0).animate(controller)
..addListener(() {
setState(() {
// the state that has changed here is the animation object’s value
});
});
controller.repeat();
}
#override
void dispose() {
controller.stop();
super.dispose();
}
#override
Widget build(BuildContext context) {
print(animation.value);
return new Center(
child: new Stack(children: <Widget>[
LinearProgressIndicator(
value: animation.value,
),
Align(
alignment :Alignment.lerp(Alignment.topLeft, Alignment.topRight, animation.value),
child: Text("xxxxxxxxxxxxxxxxa"),
),
]));
}
}
Column(children: [
LinearProgressIndicator(
value: value,
backgroundColor: Colors.grey,
color: Colors.blue,
minHeight: 20,
),
Align(
alignment:
AlignmentGeometry.lerp(const Alignment(-1.04, -1), const Alignment(1.04, -1), value)
as AlignmentGeometry,
child: Text(
'${minutes}:${seconds}',
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.blue, fontSize: 12),
)),
]);

Flutter. How can I make container wider than screen?

I'm trying to create a parallax background for page controller. For that purpuse I need to create a background image that is wider than the screen. I've put it inside a container like this:
#override
Widget build(BuildContext context) {
return Material(
child: Stack(
children: [
Container(
width: 4000,
height: 250,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/pizza_bg.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat
)
)
),
],
),
);
}
But the problem is that no matter what width I specify, the container (and the image, of course) never get wider than the screen. Is it possible at all?
p.s. I tried to use SizedBox and AspectRatio widgets, and they both give the same result
try this, as an option
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
width: 4000,
height: 250,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/pizza_bg.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
),
),
),
),
],
),
),
);
}
}
also you can disable scroll for user and manage scroll position via scroll controller
SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
controller: controller, // your ScrollController
child: Container(
width: 4000,
height: 250,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/pizza_bg.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
),
),
),
),
For images you can use Transform.scale(), as found in the documentation. Using your example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(
children: [
Align(
alignment: Alignment.center,
child: Transform.scale(
scale: 10.0,
child: Container(
width: 400,
height: 25,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/pizza_bg.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
),
),
),
),
),
],
),
),
);
}
}
If you want to animate the scale, you can use ScaleTransition(), explained in this page of the docs. For example:
/// Flutter code sample for ScaleTransition
// The following code implements the [ScaleTransition] as seen in the video
// above:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
/// AnimationControllers can be created with `vsync: this` because of TickerProviderStateMixin.
class _MyStatefulWidgetState extends State<MyStatefulWidget>
with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ScaleTransition(
scale: _animation,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: FlutterLogo(size: 150.0),
),
),
),
);
}
}
NOTE: To avoid quality loss in the image, use an image of the size after scaling or a vector graphic as a source.