I'm trying to animate two square containers so that when they are tapped they are animated to scale. I see all these transform class examples online that show animation of a widget however when I use the transform class the scale just jumps from its initial value to its final value.
My end goal is to animate a container to 'bounce' every time it is tapped like what you can do with bounce.js in web development. To understand what I mean you can go to http://bouncejs.com, click 'select preset' in the upper left corner, select jelly from the drop down menu and click 'play animation'.
Can this be done with the transform class?
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
var squareScaleA = 0.5;
var squareScaleB = 0.5;
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Bounce Example"),
),
body: Stack(
children: <Widget>[
Container(
width: 300.0,
height: 150.0,
color: Colors.yellowAccent,
),
Column(
children: <Widget>[
Row(
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
squareScaleA = 1.0;
});
},
child: Transform.scale(
scale: squareScaleA,
child: Container(
width: 150.0,
height: 150.0,
color: Colors.green,
),
),
),
GestureDetector(
onTap: () {
setState(() {
squareScaleB = 1.0;
});
},
child: Transform.scale(
scale: squareScaleB,
child: Container(
width: 150.0,
height: 150.0,
color: Colors.blue,
),
),
),
],
),
],
),
],
),
);
}
}
Thanks in advance for any help!
You need to use Animations, you can start using AnimationController it's very simple , I fixed your sample :
class _MyHomePageState extends State<TestingNewWidget>
with TickerProviderStateMixin {
var squareScaleA = 0.5;
var squareScaleB = 0.5;
AnimationController _controllerA;
AnimationController _controllerB;
#override
void initState() {
_controllerA = AnimationController(
vsync: this,
lowerBound: 0.5,
upperBound: 1.0,
duration: Duration(seconds: 1));
_controllerA.addListener(() {
setState(() {
squareScaleA = _controllerA.value;
});
});
_controllerB = AnimationController(
vsync: this,
lowerBound: 0.5,
upperBound: 1.0,
duration: Duration(seconds: 1));
_controllerB.addListener(() {
setState(() {
squareScaleB = _controllerB.value;
});
});
super.initState();
}
#override
void dispose() {
_controllerA.dispose();
_controllerB.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Bounce Example"),
),
body: Stack(
children: <Widget>[
Container(
width: 300.0,
height: 150.0,
color: Colors.yellowAccent,
),
Column(
children: <Widget>[
Row(
children: <Widget>[
GestureDetector(
onTap: () {
if (_controllerA.isCompleted) {
_controllerA.reverse();
} else {
_controllerA.forward(from: 0.0);
}
},
child: Transform.scale(
scale: squareScaleA,
child: Container(
width: 150.0,
height: 150.0,
color: Colors.green,
),
),
),
GestureDetector(
onTap: () {
if (_controllerB.isCompleted) {
_controllerB.reverse();
} else {
_controllerB.forward(from: 0.0);
}
},
child: Transform.scale(
scale: squareScaleB,
child: Container(
width: 150.0,
height: 150.0,
color: Colors.blue,
),
),
),
],
),
],
),
],
),
);
}
}
Also you can read more about animation here: https://flutter.dev/docs/development/ui/animations
Related
I am looking to create a grid with 4 custom widgets that can either add or subtract from a given starting number. See image for reference.
For example, if you press player one, the number would increase or decrease to 100 or 99. But the other 3 players would remain the same.
I had originally used one stateful widget with a separate function for each player, but I am sure there's a way to do it in a more modular way.
class CommanderDamage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return CommanderDamageState();
}
}
class CommanderDamageState extends State<CommanderDamage> {
int damage = 0;
void update() {
setState(() {
damage++;
});
}
#override
Widget build(context) {
return MaterialApp(
home: Scaffold(
body: GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: 4,
itemBuilder: (BuildContext context, index) {
return Container(
child: Column(
children: <Widget>[
Text("Player " + index.toString()),
InkWell(
onTap: update,
child: Container(
width: 100.0,
height: 100.0,
child: Text(damage),
)
],
),
);
},
),
),
);
}
}
EDIT: I have edited my code to reflect my current. Currently, when the damage area is pressed, the damage increases for all 4 players instead of the one I am pressing.
Wrap your text widget inside InkWell(). Basically what InkWell does is creates a rectangular touch responsive area.
InkWell(
child: Text(
'Player One',
style: TextStyle(
fontSize: 20, color: Colors.white),
onTap: () {
// Your function
}
)
But this make the interactive tap area according to size of the text which is very small, so it's better to wrap it inside a container and provide height-width or some space with padding
InkWell(
child: Container(
width: 100,
height: 100,
child: Text(
'Player One',
style: TextStyle(
fontSize: 20, color: Colors.white), ),
onTap: () {
// Your function
}
)
An inside onTap you can your function and perform changes.
Read more about InkWell:
https://docs.flutter.io/flutter/material/InkWell-class.html
After lots of trial and error I managed to find an answer.
I had to set the state within the onTap instead of making a separate function and calling it in the onTap.
class CommanderDamage extends StatefulWidget {
int damage = 0;
CommanderDamage({this.damage, Key key});
#override
State<StatefulWidget> createState() {
return CommanderDamageState();
}
}
class CommanderDamageState extends State<CommanderDamage> {
var damage = [0, 0, 0, 0, 0, 0];
#override
Widget build(context) {
return MaterialApp(
home: Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft, end: Alignment.bottomRight,
colors: [Color(0xfff6921e), Color(0xffee4036)],
),
),
child: GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: damage.length,
itemBuilder: (BuildContext context, index) {
return Container(
child: Column(
children: <Widget>[
InkWell(
onTap: () {
setState(() {
damage[index]++;
});
},
onLongPress: () {
setState(() {
damage[index] = 0;
});
},
child: Container(
width: 100.0,
height: 100.0,
child: Text(damage[index].toString()),
),
),
],
),
);
},
),
),
],
),
),
);
}
}
I have a main widget called DashboardWidget. Inside it, I have a Scaffold with BottomNavigationBar and a FloatingActionButton:
Now, I want to make a widget that would be dragged from the bottom by:
Swiping up with the finger.
Pressing on FloatingActionButton.
In other words, I want to expand the BottomNavigationBar.
Here's a design concept in case I was unclear.
The problem is, I'm not sure where to start to implement that. I've thought about removing the BottomNavigationBar and create a custom widget that can be expanded, but I'm not sure if it's possible either.
Output:
I used a different approach and did it without AnimationController, GlobalKey etc, the logic code is very short (_handleClick).
I only used 4 variables, simple and short!
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static double _minHeight = 80, _maxHeight = 600;
Offset _offset = Offset(0, _minHeight);
bool _isOpen = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFF6F6F6),
appBar: AppBar(backgroundColor: Color(0xFFF6F6F6), elevation: 0),
body: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: FlatButton(
onPressed: _handleClick,
splashColor: Colors.transparent,
textColor: Colors.grey,
child: Text(_isOpen ? "Back" : ""),
),
),
Align(child: FlutterLogo(size: 300)),
GestureDetector(
onPanUpdate: (details) {
_offset = Offset(0, _offset.dy - details.delta.dy);
if (_offset.dy < _HomePageState._minHeight) {
_offset = Offset(0, _HomePageState._minHeight);
_isOpen = false;
} else if (_offset.dy > _HomePageState._maxHeight) {
_offset = Offset(0, _HomePageState._maxHeight);
_isOpen = true;
}
setState(() {});
},
child: AnimatedContainer(
duration: Duration.zero,
curve: Curves.easeOut,
height: _offset.dy,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.5), spreadRadius: 5, blurRadius: 10)]),
child: Text("This is my Bottom sheet"),
),
),
Positioned(
bottom: 2 * _HomePageState._minHeight - _offset.dy - 28, // 56 is the height of FAB so we use here half of it.
child: FloatingActionButton(
child: Icon(_isOpen ? Icons.keyboard_arrow_down : Icons.add),
onPressed: _handleClick,
),
),
],
),
);
}
// first it opens the sheet and when called again it closes.
void _handleClick() {
_isOpen = !_isOpen;
Timer.periodic(Duration(milliseconds: 5), (timer) {
if (_isOpen) {
double value = _offset.dy + 10; // we increment the height of the Container by 10 every 5ms
_offset = Offset(0, value);
if (_offset.dy > _maxHeight) {
_offset = Offset(0, _maxHeight); // makes sure it does't go above maxHeight
timer.cancel();
}
} else {
double value = _offset.dy - 10; // we decrement the height by 10 here
_offset = Offset(0, value);
if (_offset.dy < _minHeight) {
_offset = Offset(0, _minHeight); // makes sure it doesn't go beyond minHeight
timer.cancel();
}
}
setState(() {});
});
}
}
You can use the BottomSheet class.
Here is a Medium-tutorial for using that, here is a youtube-tutorial using it and here is the documentation for the class.
The only difference from the tutorials is that you have to add an extra call method for showBottomSheet from your FloatingActionButton when it is touched.
Bonus: here is the Material Design page on how to use it.
You can check this code, it is a complete example of how to start implementing this kind of UI, take it with a grain of salt.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:rxdart/rxdart.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Orination Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
bool _isOpen;
double _dragStart;
double _hieght;
double _maxHight;
double _currentPosition;
GlobalKey _cardKey;
AnimationController _controller;
Animation<double> _cardAnimation;
#override
void initState() {
_isOpen = false;
_hieght = 50.0;
_cardKey = GlobalKey();
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 700));
_cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.addListener(() {
setState(() {
_hieght = _cardAnimation.value;
});
});
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Colors.transparent,
titleSpacing: 0.0,
title: _isOpen
? MaterialButton(
child: Text(
"Back",
style: TextStyle(color: Colors.red),
),
onPressed: () {
_isOpen = false;
_cardAnimation = Tween(begin: _hieght, end: 50.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.forward(from: 0.0);
},
)
: Text(""),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.keyboard_arrow_up),
onPressed: () {
final RenderBox renderBoxCard = _cardKey.currentContext
.findRenderObject();
_maxHight = renderBoxCard.size.height;
_cardAnimation = Tween(begin: _hieght, end: _maxHight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut)
);
_controller.forward(from: 0.0);
_isOpen = true;
}),
body: Stack(
key: _cardKey,
alignment: Alignment.bottomCenter,
children: <Widget>[
Container(
width: double.infinity,
height: double.infinity,
color: Colors.black12,
),
GestureDetector(
onPanStart: _onPanStart,
onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd,
child:Material(
borderRadius: BorderRadius.only(
topRight: Radius.circular(16.0),
topLeft: Radius.circular(16.0),
),
elevation: 60.0,
color: Colors.white,
// shadowColor: Colors.,
child: Container(
height: _hieght,
child: Center(
child: Text("Hello, You can drag up"),
),
),
),
),
],
),
);
}
void _onPanStart(DragStartDetails details) {
_dragStart = details.globalPosition.dy;
_currentPosition = _hieght;
}
void _onPanUpdate(DragUpdateDetails details) {
final RenderBox renderBoxCard = _cardKey.currentContext.findRenderObject();
_maxHight = renderBoxCard.size.height;
final hieght = _currentPosition - details.globalPosition.dy + _dragStart;
print(
"_currentPosition = $_currentPosition _hieght = $_hieght hieght = $hieght");
if (hieght <= _maxHight && hieght >= 50.0) {
setState(() {
_hieght = _currentPosition - details.globalPosition.dy + _dragStart;
});
}
}
void _onPanEnd(DragEndDetails details) {
_currentPosition = _hieght;
if (_hieght <= 60.0) {
setState(() {
_isOpen = false;
});
} else {
setState(() {
_isOpen = true;
});
}
}
}
Edit: I modified the code by using Material Widget instead of A container with shadow for better performance,If you have any issue, please let me know .
For my flutter app I need a container that can be added when I press the add button. So I then looked at other Stack Overflow questions such as: Flutter - Render new Widgets on click and Flutter - Add new Widget on click. After, this is what I came up with.
class Body extends StatelessWidget {
#override
Widget build(BuildContext context) {
var tPadding= MediaQuery.of(context).size.width * 0.08;
var bPadding= MediaQuery.of(context).size.width * 0.15;
var vPadding = MediaQuery.of(context).size.height * 0.015;
return Expanded (
child: Container (
child: NotificationListener<OverscrollIndicatorNotification> (
onNotification: (OverscrollIndicatorNotification overscroll) {
overscroll.disallowGlow();
},
child: PageView.builder(
pageSnapping: false,
controller: PageController(viewportFraction: 0.85),
itemCount: container.length,
itemBuilder: (context, i) {
return Padding (
padding: EdgeInsets.only(
left: vPadding,
right: vPadding,
top: tPadding,
bottom: bPadding
),
child: container[i]
);
},
)
)
)
);
}
}
int _count = 1;
List container = [
List.generate(_count, (int i) => ContainerCard),
AddContainer()
];
class ContainerCard extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Material (
borderRadius: BorderRadius.circular(50.0),
color: Colors.white,
elevation: 8.0,
);
}
}
class AddContainer extends StatefulWidget {
#override
AddContainerState createState() => AddContainerState();
}
class AddContainerState extends State<AddContainer> {
#override
Widget build(BuildContext context) {
return Material(
borderRadius: BorderRadius.circular(50.0),
color: Colors.white,
elevation: 8.0,
child: InkWell (
onTap: _addContainer,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
child: Column (
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon (
Icons.add,
size: 50.0,
)
]
),
)
);
}
void _addContainer() {
setState(() {
_count = _count + 1;
});
}
}
But for some reason this is not working. What is wrong and how can I fix this?
Full Code:
import 'package:flutter/material.dart';
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp (
debugShowCheckedModeBanner: false,
home: Scaffold (
backgroundColor: Colors.white,
body: Column (
children: <Widget> [
AppBar(),
Body(),
]
)
)
);
}
}
class AppBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
var abHeight = MediaQuery.of(context).size.height * 0.2;
var vPadding = MediaQuery.of(context).size.height * 0.07;
return Container (
height: abHeight,
child: Column (
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget> [
Row (
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: vPadding),
child: PoppinsText (
text: "App",
fontSize: 40.0,
fontWeight: FontWeight.bold,
color: Colors.black,
),
)
]
)
]
)
);
}
}
class Body extends StatelessWidget {
#override
Widget build(BuildContext context) {
var tPadding= MediaQuery.of(context).size.width * 0.08;
var bPadding= MediaQuery.of(context).size.width * 0.15;
var vPadding = MediaQuery.of(context).size.height * 0.015;
return Expanded (
child: Container (
child: NotificationListener<OverscrollIndicatorNotification> (
onNotification: (OverscrollIndicatorNotification overscroll) {
overscroll.disallowGlow();
},
child: PageView.builder(
pageSnapping: false,
controller: PageController(viewportFraction: 0.85),
itemCount: container.length,
itemBuilder: (context, i) {
return Padding (
padding: EdgeInsets.only(
left: vPadding,
right: vPadding,
top: tPadding,
bottom: bPadding
),
child: container[i]
);
},
)
)
)
);
}
}
int _count = 1;
List container = [
List.generate(_count, (int i) => ContainerCard),
AddContainer()
];
class ContainerCard extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Material (
borderRadius: BorderRadius.circular(50.0),
color: Colors.white,
elevation: 8.0,
);
}
}
class AddContainer extends StatefulWidget {
#override
AddContainerState createState() => AddContainerState();
}
class AddContainerState extends State<AddContainer> {
#override
Widget build(BuildContext context) {
return Material(
borderRadius: BorderRadius.circular(50.0),
color: Colors.white,
elevation: 8.0,
child: InkWell (
onTap: _addContainer,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
child: Column (
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon (
Icons.add,
size: 50.0,
)
]
),
)
);
}
void _addContainer() {
setState(() {
_count = _count + 1;
});
}
}
class PoppinsText extends StatelessWidget {
PoppinsText ({Key key,
this.text,
this.fontSize,
this.fontWeight,
this.color}) : super(key: key);
final String text;
final double fontSize;
final FontWeight fontWeight;
final Color color;
#override
Widget build(BuildContext context) {
return Text (
text,
style: TextStyle (
fontFamily: 'Poppins',
fontWeight: fontWeight,
fontSize: fontSize,
color: color
),
);
}
}
You are using a Stateless widget. Switch to Stateful widget
Edit
Updated as per requirement.
The trick here is to use reverse property of pageview.
class Body extends StatefulWidget {
#override
_BodyState createState() => _BodyState();
}
class _BodyState extends State<Body> {
int count = 1;
#override
Widget build(BuildContext context) {
return Expanded(
child: Container(
child: NotificationListener<OverscrollIndicatorNotification>(
onNotification: (OverscrollIndicatorNotification overscroll) {
overscroll.disallowGlow();
},
child: PageView.builder(
reverse: true,
pageSnapping: false,
controller: PageController(viewportFraction: 0.85),
itemCount: count,
itemBuilder: (context, i) {
print(i);
if (i == 0) {
return Padding(
padding: EdgeInsets.only(
left:
MediaQuery.of(context).size.height * 0.015,
right:
MediaQuery.of(context).size.height * 0.015,
top: MediaQuery.of(context).size.width * 0.08,
bottom:
MediaQuery.of(context).size.width * 0.15),
child: Material(
borderRadius: BorderRadius.circular(50.0),
color: Colors.white,
elevation: 8.0,
child: InkWell(
onTap: () {
setState(() {
count++;
});
},
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.add,
size: 50.0,
)
]),
)));
} else {
return Padding(
padding: EdgeInsets.only(
left:
MediaQuery.of(context).size.height * 0.015,
right:
MediaQuery.of(context).size.height * 0.015,
top: MediaQuery.of(context).size.width * 0.08,
bottom:
MediaQuery.of(context).size.width * 0.15),
child: Material(
borderRadius: BorderRadius.circular(50.0),
color: Colors.white,
elevation: 8.0,
));
}
}))));
}
I don't know If you found your solution yet. This is my code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
const double paddingInset = 5;
Color tileColor = Colors.grey[350];
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Test App',
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
List<Widget> bodyElements = [];
int num = 0;
void addBodyElement() {
bodyElements.add(
Padding(
padding: const EdgeInsets.all(paddingInset),
child: Container(
height: 500,
width: double.infinity,
child: Center(child: Text('This is section $num')),
decoration: BoxDecoration(
color: tileColor,
borderRadius: BorderRadius.circular(10),
),
),
),
);
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Center(child: Text('Home')),
brightness: Brightness.dark,
leading: IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
setState(() {
bodyElements.clear();
num = 0;
});
},
),
),
body: ListView(
children: <Widget>[
Column(
children: bodyElements,
),
],
),
floatingActionButton: FloatingActionButton.extended(
icon: Icon(Icons.add),
label: Text('Add'),
onPressed: () {
num++;
setState(() {
addBodyElement();
});
},
),
);
}
}
Is there any way to create a background floating window using Flutter like IMO does.
Background Floating Window: This is a window which can be dragged using fingers and it is not only limited to my app. User can have my app window showing up on different apps too. Some apps that uses it include TrueCaller, IMO, etc.
Here is the screenshot, the boy window can be dragged and when you tap home button, the app will get minimised but this boy window will still be there on the home launcher and if user navigates to some other app, this window will still persist.
Screenshot Example
the below code gives you the result you want
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Unit Converter',
home: Scaffold(
body: SafeArea(
child: Center(
child: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
color: Colors.red
),
),
Container(
margin: EdgeInsets.all(20),
width: 150,
height: 200,
decoration: BoxDecoration(
color: Colors.blue
)
)
],
),
),
),
),
);
}
}
A minimal E.g of What you Want:
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: App(),
),
);
}
}
class App extends StatefulWidget {
#override
AppState createState() => AppState();
}
class AppState extends State<App> {
Color caughtColor = Colors.grey;
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(color: Colors.red),
),
DragBox(Offset(0.0, 0.0), 'Box One', Colors.blueAccent),
DragBox(Offset(200.0, 0.0), 'Box Two', Colors.orange),
DragBox(Offset(300.0, 0.0), 'Box Three', Colors.lightGreen),
],
);
}
}
class DragBox extends StatefulWidget {
final Offset initPos;
final String label;
final Color itemColor;
DragBox(this.initPos, this.label, this.itemColor);
#override
DragBoxState createState() => DragBoxState();
}
class DragBoxState extends State<DragBox> {
Offset position = Offset(0.0, 0.0);
#override
void initState() {
super.initState();
position = widget.initPos;
}
#override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: Draggable(
data: widget.itemColor,
child: Container(
width: 100.0,
height: 100.0,
color: widget.itemColor,
child: Center(
child: Text(
widget.label,
style: TextStyle(
color: Colors.white,
decoration: TextDecoration.none,
fontSize: 20.0,
),
),
),
),
onDraggableCanceled: (velocity, offset) {
setState(() {
position = offset;
});
},
feedback: Container(
width: 120.0,
height: 120.0,
color: widget.itemColor.withOpacity(0.5),
child: Center(
child: Text(
widget.label,
style: TextStyle(
color: Colors.white,
decoration: TextDecoration.none,
fontSize: 18.0,
),
),
),
),
));
}
}
A simple way to do this would be a stack.
https://docs.flutter.io/flutter/widgets/Stack-class.html
Is there any ready made widget or where to get started floating action button with speed dial actions in Flutter.
Here's a sketch of how to implement a Speed dial using FloatingActionButton.
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
State createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController _controller;
static const List<IconData> icons = const [ Icons.sms, Icons.mail, Icons.phone ];
#override
void initState() {
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
}
Widget build(BuildContext context) {
Color backgroundColor = Theme.of(context).cardColor;
Color foregroundColor = Theme.of(context).accentColor;
return new Scaffold(
appBar: new AppBar(title: new Text('Speed Dial Example')),
floatingActionButton: new Column(
mainAxisSize: MainAxisSize.min,
children: new List.generate(icons.length, (int index) {
Widget child = new Container(
height: 70.0,
width: 56.0,
alignment: FractionalOffset.topCenter,
child: new ScaleTransition(
scale: new CurvedAnimation(
parent: _controller,
curve: new Interval(
0.0,
1.0 - index / icons.length / 2.0,
curve: Curves.easeOut
),
),
child: new FloatingActionButton(
heroTag: null,
backgroundColor: backgroundColor,
mini: true,
child: new Icon(icons[index], color: foregroundColor),
onPressed: () {},
),
),
);
return child;
}).toList()..add(
new FloatingActionButton(
heroTag: null,
child: new AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return new Transform(
transform: new Matrix4.rotationZ(_controller.value * 0.5 * math.pi),
alignment: FractionalOffset.center,
child: new Icon(_controller.isDismissed ? Icons.share : Icons.close),
);
},
),
onPressed: () {
if (_controller.isDismissed) {
_controller.forward();
} else {
_controller.reverse();
}
},
),
),
),
);
}
}
This plugin could serve you:
https://pub.dartlang.org/packages/flutter_speed_dial
You need declare the dependency in the pubspect.yaml file
dependencies:
flutter:
sdk: flutter
flutter_speed_dial: ^1.0.9
Here is an example:
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: SpeedDial(
animatedIcon: AnimatedIcons.menu_close,
animatedIconTheme: IconThemeData(size: 22.0),
// this is ignored if animatedIcon is non null
// child: Icon(Icons.add),
visible: _dialVisible,
curve: Curves.bounceIn,
overlayColor: Colors.black,
overlayOpacity: 0.5,
onOpen: () => print('OPENING DIAL'),
onClose: () => print('DIAL CLOSED'),
tooltip: 'Speed Dial',
heroTag: 'speed-dial-hero-tag',
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 8.0,
shape: CircleBorder(),
children: [
SpeedDialChild(
child: Icon(Icons.accessibility),
backgroundColor: Colors.red,
label: 'First',
labelStyle: TextTheme(fontSize: 18.0),
onTap: () => print('FIRST CHILD')
),
SpeedDialChild(
child: Icon(Icons.brush),
backgroundColor: Colors.blue,
label: 'Second',
labelStyle: TextTheme(fontSize: 18.0),
onTap: () => print('SECOND CHILD'),
),
SpeedDialChild(
child: Icon(Icons.keyboard_voice),
backgroundColor: Colors.green,
label: 'Third',
labelStyle: TextTheme(fontSize: 18.0),
onTap: () => print('THIRD CHILD'),
),
],
),
);
}