Flutter: Bouncing button animation scroll issue - flutter

I want to add some smooth & fluid animations in my Flutter app, especially on buttons like these on the Reflectly app (made on Flutter too).
So I followed this tutorial to add Bouncing Animations on my buttons. Everything is working fine but I noticed a "bug" related to the scroll:
When I touch a button AND keep pressing it, then drag to scroll down or up (always by keeping down), the button keep the down state and do not return to it's original position (see the GIF for more details).
Note: This issue doesn't appear on the Reflecty app.
To reproduce, this "bug", you can download the Bouncing button project here and then create a scroll with some bouncing buttons inside it like this:
return Scaffold(
body: Container(
width: double.infinity,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
AnimatedButton(),
// [...] Many others animated button here
],
),
),
),
);
I tried to add a onVerticalDragEnd callback to restore the state after but it's worse because it is no longer possible to scroll (scroll seems to be catches only on buttons) !
Any help would be greatly appreciated :)
Thanks in advance !

You can copy paste run full code below
When onTapDown and scroll will trigger onTapCancel
You can put _controller.reverse(); in _onTapCancel()
code snippet
void _onTapCancel() {
print("on tap cancel");
_controller.reverse();
}
working demo
full code
import 'package:flutter/material.dart';
class AnimatedButton extends StatefulWidget {
#override
_AnimatedButtonState createState() => _AnimatedButtonState();
}
class _AnimatedButtonState extends State<AnimatedButton>
with SingleTickerProviderStateMixin {
double _scale;
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 200),
lowerBound: 0.0,
upperBound: 0.1,
)..addListener(() {
setState(() {});
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onTapDown(TapDownDetails details) {
_controller.forward();
}
void _onTapUp(TapUpDetails details) {
print("onTapUp");
_controller.reverse();
}
void _onTapCancel() {
print("on tap cancel");
_controller.reverse();
}
#override
Widget build(BuildContext context) {
_scale = 1 - _controller.value;
return GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
child: Transform.scale(
scale: _scale,
child: _animatedButtonUI,
),
);
}
Widget get _animatedButtonUI => Container(
height: 100,
width: 250,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
boxShadow: [
BoxShadow(
color: Color(0x80000000),
blurRadius: 30.0,
offset: Offset(0.0, 30.0),
),
],
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFFA7BFE8),
Color(0xFF6190E8),
],
),
),
child: Center(
child: Text(
'tap!',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
);
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
AnimatedButton(),
],
),
),
),
);
}
}

Related

ScaleTransition looks like as it was sliding from right to left

I'm trying to make a widget that scale down and reappears at the different side, I was expecting it to scale down and scale up regardless of its alignment. But when I try, it looks like as it was sliding from the right to the left.
Tried removing the ListTile from the _buildRightAlignedListTile and uses the text directly and the ValueKey assigned to it but it still looks the same.
Is there any way to prevent this?
H̶e̶r̶e̶ ̶i̶s̶ ̶t̶h̶e̶ ̶s̶c̶r̶i̶p̶t̶ ̶y̶o̶u̶ ̶c̶a̶n̶ ̶t̶r̶y̶ ̶t̶o̶ ̶r̶u̶n̶ ̶i̶n̶ ̶[̶D̶a̶r̶t̶P̶a̶d̶]̶(̶h̶t̶t̶p̶s̶:̶/̶/̶d̶a̶r̶t̶p̶a̶d̶.̶d̶e̶v̶)̶,̶ ̶p̶l̶e̶a̶s̶e̶ ̶h̶a̶v̶e̶ ̶a̶ ̶l̶o̶o̶k̶.̶
EDIT
Finally found a way to share it, please use this DartPad link to reproduce the issue.
You can also copy and paste the script below just in case the link is dead. It was made directly from the dartpad.
EDIT 2
I apologize if my explanation is confusing, I'm having trouble trying to find the right words to explain it. I'm not a native speaker.
I want the right aligned widget to stay on the right and scale down in place until it disappear completely. And then the left aligned widget to scale up on the left, instead of scaling up from the middle to the left.
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _active = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: SwitchingWidget(active: _active),
),
floatingActionButton: FloatingActionButton(
onPressed: _toggle,
child: const Icon(Icons.check)
),
),
);
}
void _toggle() {
setState(() {
_active = !_active;
});
}
}
class SwitchingWidget extends StatefulWidget {
const SwitchingWidget({super.key, this.active = false});
final bool active;
#override
State<SwitchingWidget> createState() => _SwitchingWidgetState();
}
class _SwitchingWidgetState extends State<SwitchingWidget> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
transitionBuilder: (child, animation) {
return ScaleTransition(scale: animation, child: child);
},
child: widget.active
? _buildLeftAlignedListTile()
: _buildRightAlignedListTile(),
),
);
}
Widget _buildLeftAlignedListTile() {
return const ListTile(
key: ValueKey(1),
leading: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
),
title: Text(
'Left aligned',
style: TextStyle(color: Colors.white),
),
);
}
Widget _buildRightAlignedListTile() {
return const ListTile(
key: ValueKey(2),
title: Text(
'Right aligned',
textAlign: TextAlign.right,
style: TextStyle(color: Colors.white),
),
);
}
}
Found a solution to this while tinkering around with another stuff. The solution is to use separate AnimatedSwitcher for each widget I want to animate instead of using one and placing it directly with a conditional statement.
The script above has slightly modified and it works perfectly just like what I wanted. You can also copy and paste to run it on the DartPad since it was made from there to try it out.
Please let me know if there is a better approach!
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _active = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: SwitchingWidget(active: _active),
),
floatingActionButton: FloatingActionButton(
onPressed: _toggle,
child: const Icon(Icons.check)
),
),
);
}
void _toggle() {
setState(() {
_active = !_active;
});
}
}
class SwitchingWidget extends StatefulWidget {
const SwitchingWidget({super.key, this.active = false});
final bool active;
#override
State<SwitchingWidget> createState() => _SwitchingWidgetState();
}
class _SwitchingWidgetState extends State<SwitchingWidget> {
final scaleDuration = const Duration(milliseconds: 200);
Widget transitionBuilder(child, animation) {
return ScaleTransition(child: child, scale: animation);
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AnimatedSwitcher(
duration: scaleDuration,
transitionBuilder: transitionBuilder,
child: widget.active
? const SizedBox()
: _buildLeftAlignedWidget(),
),
AnimatedSwitcher(
duration: scaleDuration,
transitionBuilder: transitionBuilder,
child: widget.active
? _buildRightAlignedWidget()
: const SizedBox(),
),
],
),
);
}
Widget _buildLeftAlignedWidget() {
return Row(
key: const ValueKey(1),
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
margin: const EdgeInsets.only(right: 16),
width: 20,
height: 20,
child: const CircularProgressIndicator(color: Colors.white, strokeWidth: 2),
),
const Text(
'Left aligned',
style: TextStyle(color: Colors.white),
),
],
);
}
Widget _buildRightAlignedWidget() {
return const Text(
key: ValueKey(2),
'Right aligned',
textAlign: TextAlign.right,
style: TextStyle(color: Colors.white),
);
}
}

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

How to shrink and grow icon animation in flutter?

I am using the below code to perform animation-grow and shrink animation of the icon but for it, I have to click on the icon, I want the animation repetitive once we on the screen.
return new Container(
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new IconButton(
onPressed: () {
setState(() {
if (_resized) {
_resized = false;
_height = 20.0;
} else {
_resized = true;
_height = 40.0;
}
});
},
icon: Icon(Icons.calendar_today, size: _height),
color: _color,
),
],
),
);
I want the output like below where the outer portion continuously grows and shrink.
You can use several approaches to this problem. My preferable would be to use AnimationController that repeats itself.
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
)
..forward()
..repeat(reverse: true);
You can for instance animate the size of the padding around the button. I would use circular containers around the IconButton.
In order to do that you need to initialize AnimationController in your state. Remember about disposing it when the lifecycle of widget ends.
Here is a sample on codepen and dartpad:
https://codepen.io/orestesgaolin/pen/MWajRGV
https://dartpad.dartlang.org/ca4838f17ea6061cf0212a4b689eaf2a
And full source code can be found in this gist
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Animated Icon',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
AnimationController animationController;
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
)
..forward()
..repeat(reverse: true);
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.blue,
child: Center(
child: AnimatedBuilder(
animation: animationController,
builder: (context, child) {
return Container(
decoration: ShapeDecoration(
color: Colors.white.withOpacity(0.5),
shape: CircleBorder(),
),
child: Padding(
padding: EdgeInsets.all(8.0 * animationController.value),
child: child,
),
);
},
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: CircleBorder(),
),
child: IconButton(
onPressed: () {},
color: Colors.blue,
icon: Icon(Icons.calendar_today, size: 24),
),
),
),
),
),
);
}
}

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

How to move a widget in the screen in Flutter

I am using flutter and i have a container with the shape of a circle using this code
new Container(
width: 50.0,
height: 50.0,
decoration: new BoxDecoration(
shape: BoxShape.circle)
I want to make this circle move on the screen like this
how can I do this?
Here it is:
import 'package:flutter/material.dart';
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Drag app"),
),
body: HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomePageState();
}
}
class _HomePageState extends State<HomePage> {
double width = 100.0, height = 100.0;
Offset position ;
#override
void initState() {
super.initState();
position = Offset(0.0, height - 20);
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
left: position.dx,
//top: position.dy - height + 20,
child: Draggable(
child: Container(
width: width,
height: height,
color: Colors.blue,
child: Center(child: Text("Drag", style: Theme.of(context).textTheme.headline,),),
),
feedback: Container(
child: Center(
child: Text("Drag", style: Theme.of(context).textTheme.headline,),),
color: Colors.red[800],
width: width,
height: height,
),
onDraggableCanceled: (Velocity velocity, Offset offset){
setState(() => position = offset);
},
),
),
],
);
}
}
What you are looking for is Draggable widget. You can then handle the translation using onDraggableCanceled which is passed and offset that you can be used to update the placement
onDraggableCanceled :(velocity,offset){
//update the position here
}
Update
After checking the image you will need "Drop me here" part to be a DragTarget that has a method onAccept which will handles the logic when you drag and drop your Draggable
First, wrap your Container inside the Stack with Positioned.
Then, use Pan Gesture to implement a Pan in your Container and use onPan... methods to handle Pan Gesture
Here is code:
Offset position;
#override
void initState() {
super.initState();
position = Offset(10, 10);
}
#override
Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width;
double _height = _width * 9 / 16;
return GestureDetector(
onPanStart: (details) => _onPanStart(context, details),
onPanUpdate: (details) => _onPanUpdate(context, details, position),
onPanEnd: (details) => _onPanEnd(context, details),
onPanCancel: () => _onPanCancel(context),
child: SafeArea(
child: Stack(
children: <Widget>[
Positioned(
top: position.dy,
child: Container(
color: Colors.red,
width: _width,
height: _height,
),
),
],
),
),
);
}
void _onPanStart(BuildContext context, DragStartDetails details) {
print(details.globalPosition.dy);
}
void _onPanUpdate(BuildContext context, DragUpdateDetails details, Offset offset) {
setState(() {
position = details.globalPosition;
});
}
void _onPanEnd(BuildContext context, DragEndDetails details) {
print(details.velocity);
}
void _onPanCancel(BuildContext context) {
print("Pan canceled !!");
}
Hope this helps!
You can use Draggable class for dragging the item which you want to drag and for placing it or sticking it to somewhere on the screen you have to wrap that item with DragTarget class. In DragTarget class onAccept method is there where you can write the logic. You can also take a reference to my code here it is
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.indigo,
),
home: new MyHomePage(title: 'Flutter Demo Drag Box'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body:
new DragGame(), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class DragGame extends StatefulWidget {
#override
_DragGameState createState() => new _DragGameState();
}
class _DragGameState extends State<DragGame> {
int boxNumberIsDragged;
#override
void initState() {
boxNumberIsDragged = null;
super.initState();
}
#override
Widget build(BuildContext context) {
return new Container(
constraints: BoxConstraints.expand(),
color: Colors.grey,
child: new Stack(
children: <Widget>[
buildDraggableBox(1, Colors.red, new Offset(30.0, 100.0)),
buildDraggableBox(2, Colors.yellow, new Offset(30.0, 200.0)),
buildDraggableBox(3, Colors.green, new Offset(30.0, 300.0)),
],
));
}
Widget buildDraggableBox(int boxNumber, Color color, Offset offset) {
return new Draggable(
maxSimultaneousDrags: boxNumberIsDragged == null || boxNumber == boxNumberIsDragged ? 1 : 0,
child: _buildBox(color, offset),
feedback: _buildBox(color, offset),
childWhenDragging: _buildBox(color, offset, onlyBorder: true),
onDragStarted: () {
setState((){
boxNumberIsDragged = boxNumber;
});
},
onDragCompleted: () {
setState((){
boxNumberIsDragged = null;
});
},
onDraggableCanceled: (_,__) {
setState((){
boxNumberIsDragged = null;
});
},
);
}
Widget _buildBox(Color color, Offset offset, {bool onlyBorder: false}) {
return new Container(
height: 50.0,
width: 50.0,
margin: EdgeInsets.only(left: offset.dx, top: offset.dy),
decoration: BoxDecoration(
color: !onlyBorder ? color : Colors.grey,
border: Border.all(color: color)),
);
}
}