Flutter - Round out corners Tab Bars - flutter

I want to add this kind of tabBars in flutter.
Please suggest any way to achieve this using a library or code or any idea. That would be helpful.
Thanks for the help!

Not the perfect one also not even optimized. I will update in my free time. At the moment you can play with the code and make a change according to your requirement.
Live playground/demo: https://dartpad.dev/?id=610bea5fb086bf495550f99e9a9db839
Gist link: https://gist.github.com/omishah/610bea5fb086bf495550f99e9a9db839
Complete code:
import 'package:flutter/material.dart';
void main() {
runApp(const CodeCyanApp());
}
class CodeCyanApp extends StatelessWidget {
const CodeCyanApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int activeTabIndex = 0;
int totalTabs = 5;
static const bgColor = Colors.black;
static const tabBgColor = Colors.orange;
static const activeTabBgColor = Colors.white;
static const tabCornerRadiusColor = bgColor;
static const tabMinWidth = 90.0;
static const tabRadius = 17.0;
static const tabCornerRadius = 10.0;
static const tabContentPadding = 12.0;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: bgColor,
bottomNavigationBar: roundOutCornersTabBar(),
body: const Text("DEMO"),
// This trailing comma makes auto-formatting nicer for build methods.
);
}
Widget roundOutCornersTabBar() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children:
List<Widget>.generate(totalTabs, (index) => buildSingleTab(index)));
}
Widget buildSingleTab(int index) {
return Wrap(crossAxisAlignment: WrapCrossAlignment.end, children: [
(index == 0
? Stack(children: [
Container(
decoration: BoxDecoration(
color: index == activeTabIndex
? activeTabBgColor
: tabBgColor),
child: Container(
decoration: const BoxDecoration(
color: tabCornerRadiusColor,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(tabCornerRadius),
))),
width: 10,
height: 10,
)
])
: Container()),
InkWell(
focusColor: Colors.transparent,
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
onTap: () => setState(() {
activeTabIndex = index;
}),
child: Stack(
alignment: (index == activeTabIndex - 1
? Alignment.bottomRight
: index == activeTabIndex + 1
? Alignment.bottomLeft
: Alignment.bottomCenter),
children: [
(index == activeTabIndex - 1 || index == activeTabIndex + 1
? Container(width: 15, height: 15, color: activeTabBgColor)
: Container()),
Container(
constraints: const BoxConstraints(minWidth: tabMinWidth),
child: Text("Tab ${index + 1}", textAlign: TextAlign.center),
padding: const EdgeInsets.all(tabContentPadding),
decoration: BoxDecoration(
color:
index == activeTabIndex ? Colors.white : tabBgColor,
borderRadius: BorderRadius.only(
bottomLeft: (index == activeTabIndex + 1
? const Radius.circular(tabRadius)
: const Radius.circular(0)),
bottomRight: (index != totalTabs - 1 &&
index == activeTabIndex - 1
? const Radius.circular(tabRadius)
: const Radius.circular(0)),
topLeft: const Radius.circular(tabRadius),
topRight: const Radius.circular(tabRadius))),
),
])),
(index == totalTabs - 1
? Stack(children: [
Container(
decoration: BoxDecoration(
color: index == activeTabIndex
? activeTabBgColor
: tabBgColor),
child: Container(
decoration: const BoxDecoration(
color: tabCornerRadiusColor,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(tabCornerRadius),
))),
width: 10,
height: 10,
)
])
: Container())
]);
}
}
Ps: Will optimise the code and fix any bugs in my free time. Thank you

Related

Consumer doesn't update the UI when using notifyListeners()

I'm using animated containers and I want them to change color when I use NotifyListeners();
Thing is, the color only updates when you click on the container, otherwise it remains the same.
I tried switching to normal containers, I tried changing the home screen to stateful, nothing seems to work, any help is appreciated.
Github link: https://github.com/amrogad/beat_maker
void main() {
runApp(MultiProvider(providers: [
ChangeNotifierProvider<SoundProvider>(create: (_) => SoundProvider(),
),
ChangeNotifierProvider<IconProvider>(create: (_) => IconProvider(),
),
], child: MyApp()));
}
class IconProvider extends ChangeNotifier {
Color primary1 = Colors.deepPurpleAccent;
Color secondary1 = Colors.deepPurple;
Color primary2 = Colors.yellowAccent;
Color secondary2 = Colors.orangeAccent;
Color primary3 = Colors.cyanAccent;
Color secondary3 = Colors.blue;
Color primary4 = Colors.lime;
Color secondary4 = Colors.lightGreen;
void changeIcons(
Color primaryPath1,
Color secondaryPath1,
Color primaryPath2,
Color secondaryPath2,
Color primaryPath3,
Color secondaryPath3,
Color primaryPath4,
Color secondaryPath4,
) {
primary1 = primaryPath1;
secondary1 = secondaryPath1;
primary2 = primaryPath2;
secondary2 = secondaryPath2;
primary3 = primaryPath3;
secondary3 = secondaryPath3;
primary4 = primaryPath4;
secondary4 = secondaryPath4;
notifyListeners();
}
}
class HomeScreen extends StatelessWidget {
static const String routeName = 'Home Screen';
#override
Widget build(BuildContext context) {
var soundProvider = Provider.of<SoundProvider>(context);
return SafeArea(
child: Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.black,
title: Text(
'Beat Maker',
style: GoogleFonts.anton(color: Colors.lime, fontSize: 24),
),
centerTitle: true,
actions: [
GestureDetector(
onTap: () {
Navigator.of(context).pushNamed(SettingsScreen.routeName);
},
child: Icon(
Icons.settings,
color: Colors.lime,
size: 24,
)),
SizedBox(
width: 10,
),
],
),
body: Consumer<IconProvider>(
builder: (context, icon, child) {
return GridView(
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4
),
children: [
SoundButton(
icon.primary1, icon.secondary1, soundProvider.note1),
SoundButton(
icon.primary2, icon.secondary2, soundProvider.note2),
SoundButton(
icon.primary3, icon.secondary3, soundProvider.note3),
SoundButton(
icon.primary4, icon.secondary4, soundProvider.note4)
],
);
}
),
),
);
}
class SoundButton extends StatefulWidget {
final Color mainColor;
final Color sideColor;
final note;
const SoundButton(this.mainColor, this.sideColor, this.note, {Key? key})
: super(key: key);
#override
State<SoundButton> createState() => _SoundButtonState();
}
class _SoundButtonState extends State<SoundButton> {
late Color _mainColor;
late Color _sideColor;
final player = AudioPlayer();
#override
void initState() {
_mainColor = widget.mainColor;
_sideColor = widget.sideColor;
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_mainColor = Colors.redAccent;
_sideColor = Colors.red;
player.play(AssetSource(widget.note));
});
},
child: AnimatedContainer(
decoration: BoxDecoration(
border: Border.all(width: 1),
gradient: RadialGradient(colors: [_mainColor, _sideColor]),
boxShadow: const [
BoxShadow(
blurRadius: 1.0,
spreadRadius: 1.0,
offset: Offset(
1.0,
1.0,
),
),
]),
curve: Curves.easeOutCubic,
onEnd: () => Reset(),
duration: const Duration(milliseconds: 100),
),
);
}
void Reset() {
setState(() {
_mainColor = widget.mainColor;
_sideColor = widget.sideColor;
});
}
}
class OpenIconBottomSheet extends StatelessWidget {
const OpenIconBottomSheet({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var iconProvider = Provider.of<IconProvider>(context);
return Container(
height: 106,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12), topRight: Radius.circular(12)),
color: Colors.black,
border: Border.all(width: 3, color: Colors.lime)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
InkWell(
onTap: () {
iconProvider.changeIcons(
Colors.deepPurpleAccent,
Colors.deepPurple,
Colors.yellowAccent,
Colors.orangeAccent,
Colors.cyanAccent,
Colors.blue,
Colors.lime,
Colors.lightGreen,
);
Navigator.pop(context);
},
child: isSelected(
'Main Theme',
iconProvider.primary1 == Colors.deepPurpleAccent
? true
: false)),
Container(
width: double.infinity,
height: 3,
color: Colors.lime,
),
SizedBox(
height: 7,
),
InkWell(
onTap: () {
iconProvider.changeIcons(
Colors.white70,
Colors.white,
Colors.pinkAccent,
Colors.pink,
Colors.tealAccent,
Colors.teal,
Colors.amberAccent,
Colors.redAccent,
);
Navigator.pop(context);
},
child: isSelected('Secondary Theme',
iconProvider.primary1 == Colors.white70 ? true : false)),
],
),
);
}
Widget isSelected(String text, bool selected) {
if (selected) {
return Padding(
padding: EdgeInsets.all(6),
child: Text(text,
style: GoogleFonts.anton(color: Colors.lime, fontSize: 24)),
);
} else {
return Padding(
padding: EdgeInsets.all(6),
child: Text(text,
style: GoogleFonts.anton(color: Colors.white, fontSize: 24)),
);
}
}
}

Whats widgets should be used for creating one TextField between two toggle buttons in Flutter?

I need to do the following:
By other words only one of two buttons can be selected and there must be a TextField between them. The only solution I found is to use two ToggleButtons with TextField between them. I mean:
Row(
children: [
ToggleButtons(...),
TextField(...),
ToggleButtons(...),
]
)
But I don't like this solution because two buttons must belong to one widget. As the same time time when I insert TextField in ToggleButton.children TextField becomes third button and this is not what I need.
Could anyone say how to solve such a problem in Flutter?
You can create custom widget to handle this
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(body: Center(child: CustomCounter())));
}
}
class CustomCounter extends StatefulWidget {
const CustomCounter({Key? key}) : super(key: key);
#override
State<CustomCounter> createState() => _CustomCounterState();
}
class _CustomCounterState extends State<CustomCounter> {
bool minusSelected = false;
TextEditingController _textEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () {
minusSelected = true;
setState(() {
int currentValue = int.tryParse(_textEditingController.text) ?? 0;
_textEditingController.text = (currentValue -= 1).toString();
_textEditingController.selection = TextSelection.fromPosition(
TextPosition(offset: _textEditingController.text.length));
});
},
child: Container(
decoration: BoxDecoration(
color: minusSelected ? Colors.green : Colors.white,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(4),
topLeft: Radius.circular(4)),
border: Border.all(color: Colors.grey, width: 1)),
child: Icon(Icons.remove),
),
),
SizedBox(
width: 100,
child: TextField(
controller: _textEditingController,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.all(5),
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey)),
),
)),
InkWell(
onTap: () {
minusSelected = false;
setState(() {
int currentValue = int.tryParse(_textEditingController.text) ?? 0;
_textEditingController.text = (currentValue += 1).toString();
_textEditingController.selection = TextSelection.fromPosition(
TextPosition(offset: _textEditingController.text.length));
});
},
child: Container(
decoration: BoxDecoration(
color: minusSelected == false ? Colors.green : Colors.white,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(4),
topRight: Radius.circular(4)),
border: Border.all(color: Colors.grey, width: 1)),
child: Icon(Icons.add),
),
)
],
);
}
}
You don't have to use TextField, you can use a simple container to set your value
Row(
children: [
ToggleButtons(...),
Container(child: Text(yourValue),
ToggleButtons(...),
]
)
Or you can use readonly Textfield,
TextField(readonly: true,...)

flutter reset only custom widget state after it's optional function execution

I have a page where we have some pickup session when you select a pickup session at the bottom SwipeActionButton widget activate
now user can swipe right side and after swipe complete an async function execute which most time hit an api so if api result is success app goes to next page no problem here but if api result gave an error it shows a dialog
Press Ok and dialog pop but SwipeActionButton widget still show complete swipe how I can reset it.
code
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: NormalAppBar(
title: Text("Assign Requests"),
),
body: Consumer<PickupSessionProvider>(
builder: (context, provider, child) => Stack(
children: [
widget.requestIds.length == 0
? _requestsLoaded
? provider.unassignedRequestCount == 0
? Center(
child: Text("No requests.",
style: Theme.of(context).textTheme.headline6),
)
: _buildRequestsList(provider.unassignedRequests!)
: Center(
child: CircularProgressIndicator(),
)
: provider.pickupSessions!.length == 0
? Center(
child: Text("No active pickup session.",
style: Theme.of(context).textTheme.headline6),
)
: ListView(
padding: const EdgeInsets.only(bottom: 80),
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text(
"Select pickup session",
style: Theme.of(context).textTheme.headline4,
),
),
for (var pickupSession in provider.pickupSessions!)
_buildPickupSessionTile(pickupSession)
],
),
Positioned(
bottom: 0,
child: SwipeActionButton(
margin: EdgeInsets.symmetric(horizontal: 15, vertical: 20),
onDone: (_selectRequestList && requestIds.length == 0) ||
(!_selectRequestList &&
_selectedPickupSessionId == null)
? null
: () async {
var result = await showDialog(
context: context,
builder: (context) => _ProgressDialog(
requestIds: requestIds,
pickupSessionId: _selectedPickupSessionId),
barrierDismissible: true);
if (result == true) Navigator.of(context).pop(true);
},
doneText: "Assign request",
disabledText: "Assign request",
infoText: "Swipe to assign request",
),
)
],
),
),
);
}
Custom SwipeActionButton widget
class SwipeActionButton extends StatefulWidget {
final double height;
final Color doneColor;
final Color swiperColor;
final Color textColor;
final String doneText;
final String disabledText;
final VoidCallback? onDone;
final String? infoText;
final double? width;
final Color? backgroundColor;
final EdgeInsetsGeometry margin;
SwipeActionButton({
Key? key,
this.height = 50,
this.doneColor = const Color(0xff44b31f),
this.swiperColor = const Color(0xff44b31f),
this.textColor = const Color(0xff44b31f),
required this.doneText,
required this.disabledText,
this.onDone,
this.infoText,
this.width,
this.backgroundColor,
this.margin = EdgeInsets.zero
}) : super(key: key);
#override
_SwipeActionButtonState createState() => _SwipeActionButtonState();
}
class _SwipeActionButtonState extends State<SwipeActionButton>
with SingleTickerProviderStateMixin {
double swipePercent = 0.0;
bool swipeDone = false;
bool isDisabled = false;
late Color backgroundColor;
late AnimationController _controller;
Animation<double>? _animation;
void initState() {
super.initState();
backgroundColor = widget.backgroundColor ?? Color(0xff3344b31f);
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 500))
..addListener(() {
setState(() {
swipePercent = _animation?.value ??0;
});
})
..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed && swipeDone) {
widget.onDone!();
}
});
}
void dispose() {
_controller.dispose();
super.dispose();
}
_onDragStart(DragStartDetails details) {
_controller.reset();
swipePercent = 0.0;
}
_onDragUpdate(DragUpdateDetails details) {
setState(() {
swipePercent =
details.globalPosition.dx / MediaQuery.of(context).size.width;
if (swipePercent > 0.90) swipeDone = true;
});
}
_onDragEnd(DragEndDetails details) {
if (swipePercent > 0.90 || swipeDone) {
_animation =
Tween<double>(begin: swipePercent, end: 1).animate(_controller);
} else {
_animation =
Tween<double>(end: 0, begin: swipePercent).animate(_controller);
}
_controller.forward();
}
#override
Widget build(BuildContext context) {
isDisabled = widget.onDone == null;
double screenWidth = MediaQuery.of(context).size.width;
return Container(
alignment: Alignment.center,
margin: widget.margin,
width: screenWidth - widget.margin.horizontal,
child: Stack(
clipBehavior: Clip.hardEdge,
children: <Widget>[
Container(
height: widget.height,
decoration: BoxDecoration(
color: isDisabled ? Colors.grey : Color(0xff3344b31f),
borderRadius: BorderRadius.all(Radius.circular(100.0)),
border: Border.all(
color: isDisabled ? Colors.grey : Color(0xff3344b31f),
width: 1.5,
),
),
child: Center(
child: Text(widget.infoText ?? "",
style: Theme.of(context)
.textTheme
.subtitle1!
.copyWith(color: widget.textColor))),
),
Container(
width: isDisabled
? screenWidth
: lerpDouble(widget.height, screenWidth, swipePercent),
height: widget.height,
child: Center(
child: Opacity(
opacity: isDisabled ? 1 : lerpDouble(0, 1, swipePercent)!,
child: Text(
isDisabled ? widget.disabledText : widget.doneText,
style: Theme.of(context).textTheme.subtitle1!.copyWith(
color: Colors.white,
),
textScaleFactor:
isDisabled ? 1 : lerpDouble(0, 1, swipePercent),
))),
decoration: BoxDecoration(
color: isDisabled ? Colors.grey : null,
borderRadius: BorderRadius.all(Radius.circular(100.0)),
/* border: Border.all(
color: Theme.of(context).primaryColor,
width: 1.5,
), */
gradient: isDisabled
? null
: LinearGradient(
begin: Alignment.center,
end: Alignment.centerRight,
colors: [
widget.doneColor,
swipeDone ? widget.doneColor : backgroundColor
])),
),
isDisabled
? Container()
: Positioned(
left: lerpDouble(
0, screenWidth -(15 +widget.margin.horizontal) - (widget.height * .9), swipePercent),
/* top: widget.height * .1,
bottom: widget.height * .1,
*/
child: AbsorbPointer(
absorbing: swipeDone,
child: GestureDetector(
onHorizontalDragStart: _onDragStart,
onHorizontalDragUpdate: _onDragUpdate,
onHorizontalDragEnd: _onDragEnd,
child: Opacity(
opacity: 1,
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
height: widget.height,
width: widget.height,
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(100.0)),
border: Border.all(
color: Theme.of(context).primaryColor,
width: 1.5,
),
boxShadow: swipeDone
? null
: [
BoxShadow(
color: Colors.black45,
blurRadius: 4)
],
color: swipeDone
? backgroundColor
: widget.swiperColor),
child: swipeDone
? Icon(
Icons.check,
size: 20,
color: Colors.white,
)
: Icon(
Icons.arrow_forward,
size: 20,
color: Colors.white,
),
))))),
],
),
);
}
}
You're asking how you can make the swipeButton reset in case the request doesn't return with a valid value.
The swipeButton's state is defined by its swipeDone and swipePercent variables. To achieve what you want you need to pass swipeDone as a parameter when constructing the widget.
class SwipeActionButton extends StatefulWidget {
// Make swipeDone a class variable for the widget
bool swipeDone;
final double height;
final Color doneColor;
final Color swiperColor;
final Color textColor;
final String doneText;
final String disabledText;
final VoidCallback? onDone;
final String? infoText;
final double? width;
final Color? backgroundColor;
final EdgeInsetsGeometry margin;
SwipeActionButton({
// Add it to the constructor
required this.swipeDone,
Key? key,
this.height = 50,
this.doneColor = const Color(0xff44b31f),
this.swiperColor = const Color(0xff44b31f),
this.textColor = const Color(0xff44b31f),
required this.doneText,
required this.disabledText,
this.onDone,
this.infoText,
this.width,
this.backgroundColor,
this.margin = EdgeInsets.zero,
}) : super(key: key);
#override
_SwipeActionButtonState createState() => _SwipeActionButtonState();
}
In _SwipeActionButtonState, delete bool swipeDone = false; and replace every swipeDone by widget.swipeDone.
You also need to reset the value of swipePercent.
You can do this by adding at the beginning of the swipeButton's build method :
if (widget.swipeDone == false && swipePercent > 0.9) swipePercent = 0;
Now you can declare the variable swipeDone in the parent widget state, pass it as a parameter and modify it whenever needed. For more clarity I give you an example with a simple widget that reset the swipe button when the floating action button is pressed.
Complete code :
import 'package:flutter/material.dart';
import 'dart:ui';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool swipeDone = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(),
body: SwipeActionButton(
disabledText: 'disabled',
doneText: 'doneText',
onDone: () {},
swipeDone: swipeDone,
),
floatingActionButton: FloatingActionButton(onPressed: () {
setState(() {
swipeDone = false;
});
}),
),
);
}
}
class SwipeActionButton extends StatefulWidget {
// Make swipeDone a class variable for the widget
bool swipeDone;
final double height;
final Color doneColor;
final Color swiperColor;
final Color textColor;
final String doneText;
final String disabledText;
final VoidCallback? onDone;
final String? infoText;
final double? width;
final Color? backgroundColor;
final EdgeInsetsGeometry margin;
SwipeActionButton({
// Add it to the constructor
required this.swipeDone,
Key? key,
this.height = 50,
this.doneColor = const Color(0xff44b31f),
this.swiperColor = const Color(0xff44b31f),
this.textColor = const Color(0xff44b31f),
required this.doneText,
required this.disabledText,
this.onDone,
this.infoText,
this.width,
this.backgroundColor,
this.margin = EdgeInsets.zero,
}) : super(key: key);
#override
_SwipeActionButtonState createState() => _SwipeActionButtonState();
}
class _SwipeActionButtonState extends State<SwipeActionButton> with SingleTickerProviderStateMixin {
double swipePercent = 0.0;
bool isDisabled = false;
late Color backgroundColor;
late AnimationController _controller;
Animation<double>? _animation;
#override
void initState() {
super.initState();
backgroundColor = widget.backgroundColor ?? Color(0xff3344b31f);
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 500))
..addListener(() {
setState(() {
swipePercent = _animation?.value ?? 0;
});
})
..addStatusListener((AnimationStatus status) {
if (status == AnimationStatus.completed && widget.swipeDone) {
widget.onDone!();
}
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
_onDragStart(DragStartDetails details) {
_controller.reset();
swipePercent = 0.0;
}
_onDragUpdate(DragUpdateDetails details) {
setState(() {
swipePercent = details.globalPosition.dx / MediaQuery.of(context).size.width;
if (swipePercent > 0.90) widget.swipeDone = true;
});
}
_onDragEnd(DragEndDetails details) {
if (swipePercent > 0.90 || widget.swipeDone) {
_animation = Tween<double>(begin: swipePercent, end: 1).animate(_controller);
} else {
_animation = Tween<double>(end: 0, begin: swipePercent).animate(_controller);
}
_controller.forward();
}
#override
Widget build(BuildContext context) {
if (widget.swipeDone == false && swipePercent > 0.9) swipePercent = 0;
isDisabled = widget.onDone == null;
double screenWidth = MediaQuery.of(context).size.width;
return Container(
alignment: Alignment.center,
margin: widget.margin,
width: screenWidth - widget.margin.horizontal,
child: Stack(
clipBehavior: Clip.hardEdge,
children: <Widget>[
Container(
height: widget.height,
decoration: BoxDecoration(
color: isDisabled ? Colors.grey : Color(0xff3344b31f),
borderRadius: BorderRadius.all(Radius.circular(100.0)),
border: Border.all(
color: isDisabled ? Colors.grey : Color(0xff3344b31f),
width: 1.5,
),
),
child: Center(
child: Text(widget.infoText ?? "",
style: Theme.of(context).textTheme.subtitle1!.copyWith(color: widget.textColor))),
),
Container(
width: isDisabled ? screenWidth : lerpDouble(widget.height, screenWidth, swipePercent),
height: widget.height,
child: Center(
child: Opacity(
opacity: isDisabled ? 1 : lerpDouble(0, 1, swipePercent)!,
child: Text(
isDisabled ? widget.disabledText : widget.doneText,
style: Theme.of(context).textTheme.subtitle1!.copyWith(
color: Colors.white,
),
textScaleFactor: isDisabled ? 1 : lerpDouble(0, 1, swipePercent),
))),
decoration: BoxDecoration(
color: isDisabled ? Colors.grey : null,
borderRadius: BorderRadius.all(Radius.circular(100.0)),
/* border: Border.all(
color: Theme.of(context).primaryColor,
width: 1.5,
), */
gradient: isDisabled
? null
: LinearGradient(
begin: Alignment.center,
end: Alignment.centerRight,
colors: [widget.doneColor, widget.swipeDone ? widget.doneColor : backgroundColor])),
),
isDisabled
? Container()
: Positioned(
left:
lerpDouble(0, screenWidth - (15 + widget.margin.horizontal) - (widget.height * .9), swipePercent),
/* top: widget.height * .1,
bottom: widget.height * .1,
*/
child: AbsorbPointer(
absorbing: widget.swipeDone,
child: GestureDetector(
onHorizontalDragStart: _onDragStart,
onHorizontalDragUpdate: _onDragUpdate,
onHorizontalDragEnd: _onDragEnd,
child: Opacity(
opacity: 1,
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
height: widget.height,
width: widget.height,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(100.0)),
border: Border.all(
color: Theme.of(context).primaryColor,
width: 1.5,
),
boxShadow:
widget.swipeDone ? null : [BoxShadow(color: Colors.black45, blurRadius: 4)],
color: widget.swipeDone ? backgroundColor : widget.swiperColor),
child: widget.swipeDone
? Icon(
Icons.check,
size: 20,
color: Colors.white,
)
: Icon(
Icons.arrow_forward,
size: 20,
color: Colors.white,
),
))))),
],
),
);
}
}

ToggleButtons, Flutter: How to change border color and border radius

I have 3 ToggleButtons and I'm trying to figure out how to change the color of the selected button's border. As you can see in my picture, the green button on the left has a very hard to see light blue border around it since it is the selected button. I would like to know how I can change this color and also how I can round the border's corners.
If it helps, 'CryptoCard' is made with the Card class.
Here is my code:
Center(
child: ToggleButtons(
borderWidth: 0,
splashColor: Colors.yellow,
renderBorder: false,
children: <Widget>[
CryptoCard(
selectedCurrency,
snapshot.connectionState ==
ConnectionState.waiting
? '---'
: coinData[0],
'Bitcoin'),
CryptoCard(
selectedCurrency,
snapshot.connectionState ==
ConnectionState.waiting
? '---'
: coinData[1],
'Ethereum'),
CryptoCard(
selectedCurrency,
snapshot.connectionState ==
ConnectionState.waiting
? '---'
: coinData[2],
'Litecoin'),
],
onPressed: (int index) {
setState(() {
for (int buttonIndex = 0;
buttonIndex < isSelectedCrypto.length;
buttonIndex++) {
if (buttonIndex == index) {
isSelectedCrypto[buttonIndex] = true;
selectedCrypto =
cryptoAbbreviation[buttonIndex];
print("selectedCrypto");
print(selectedCrypto);
} else {
isSelectedCrypto[buttonIndex] = false;
}
}
});
futureData = getData();
},
isSelected: isSelectedCrypto))
ToggleButton has a property selectedBorderColor which you can use to set the border color of your selected button. You can use a custom widget to give rounded border to each individual button.
Please see the code below :
import 'package:flutter/material.dart';
final Color darkBlue = const Color.fromARGB(255, 18, 32, 47);
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<bool> isSelected = List.generate(6, (index) => false);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ToggleButtons(
children: [
CustomIcon(
icon: const Icon(Icons.ac_unit),
isSelected: isSelected[0],
bgColor: const Color(0xfff44336),
),
CustomIcon(
icon: const Icon(Icons.call),
isSelected: isSelected[1],
bgColor: const Color(0xffE91E63),
),
CustomIcon(
icon: const Icon(Icons.cake),
isSelected: isSelected[2],
bgColor: const Color(0xff9C27B0),
),
CustomIcon(
icon: const Icon(Icons.add),
isSelected: isSelected[3],
bgColor: const Color(0xff673AB7),
),
CustomIcon(
icon: const Icon(Icons.accessibility),
isSelected: isSelected[4],
bgColor: const Color(0xff3F51B5),
),
CustomIcon(
icon: const Icon(Icons.analytics),
isSelected: isSelected[5],
bgColor: const Color(0xff2196F3),
),
],
onPressed: (int index) {
setState(() {
for (int buttonIndex = 0;
buttonIndex < isSelected.length;
buttonIndex++) {
if (buttonIndex == index) {
isSelected[buttonIndex] = !isSelected[buttonIndex];
} else {
isSelected[buttonIndex] = false;
}
}
});
},
isSelected: isSelected,
selectedColor: Colors.amber,
renderBorder: false,
fillColor: Colors.transparent,
),
),
);
}
}
class CustomIcon extends StatefulWidget {
final Icon icon;
final bool isSelected;
final Color bgColor;
const CustomIcon(
{Key key,
this.icon,
this.isSelected = false,
this.bgColor = Colors.green})
: super(key: key);
#override
_CustomIconState createState() => _CustomIconState();
}
class _CustomIconState extends State<CustomIcon> {
#override
Widget build(BuildContext context) {
return Container(
width: 47,
height: 47,
decoration: BoxDecoration(
border: widget.isSelected
? Border.all(
color: const Color(0xffC5CAE9),
)
: null,
borderRadius: const BorderRadius.all(
Radius.circular(10),
),
),
child: Center(
child: Container(
height: 32,
width: 32,
decoration: BoxDecoration(
color: widget.bgColor,
borderRadius: const BorderRadius.all(
Radius.circular(5),
),
),
child: widget.icon,
),
),
);
}
}
I'm sure it is not the best but here is my code for this maybe it will help someone out there.
if you need only need one selected button to be colorized in different colour like this
Color mColor = Color(0xFF6200EE),mColor0 = Color(0xFF6200EE),mColor1 = Color(0xFF6200EE);
final isSelected = <bool>[false, false, false];
then
ToggleButtons(
color: Colors.black.withOpacity(0.60),
selectedColor: mColor,
selectedBorderColor: mColor0,
fillColor: mColor1.withOpacity(0.08),
splashColor: Colors.grey.withOpacity(0.12),
hoverColor: Color(0xFF6200EE).withOpacity(0.04),
borderRadius: BorderRadius.circular(4.0),
constraints: BoxConstraints(minHeight: 36.0),
isSelected: isSelected,
onPressed: (index) {
// Respond to button selection
setState(() {
isSelected[0] = false;
isSelected[1] = false;
isSelected[2] = false;
if (index == 0) {
mColor = Colors.blue;
mColor0 = Colors.blue;
mColor1 = Colors.blue;
}
if (index == 1) {
mColor = Colors.green;
mColor0 = Colors.green;
mColor1 = Colors.green;
}
if (index == 2) {
mColor = Colors.red;
mColor0 = Colors.red;
mColor1 = Colors.red;
}
isSelected[index] = !isSelected[index];
});
},
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
'BUTTON 1',
style: TextStyle(fontSize: 20),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
'BUTTON 2',
style: TextStyle(fontSize: 20),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
'BUTTON 3',
style: TextStyle(fontSize: 20),
),
),
],
),

How to add a asset images using Image_stack package

I am trying to add a asset images using image_stack package. I can't add images to a list in image_stack. I can only add a network images. I can't add a asset image.
Yes, you can not add any asset image because image_stack doesn't support that.
But you create your own widget and use it. Something like below.
custom_image_stack.dart
import 'package:flutter/material.dart';
class CustomImageStack extends StatelessWidget {
final List<String> imageList;
final double imageRadius;
final int imageCount;
final int totalCount;
final double imageBorderWidth;
final Color imageBorderColor;
final TextStyle extraCountTextStyle;
final Color backgroundColor;
CustomImageStack({
Key key,
#required this.imageList,
this.imageRadius = 25,
this.imageCount = 3,
this.totalCount,
this.imageBorderWidth = 2,
this.imageBorderColor = Colors.grey,
this.extraCountTextStyle = const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
),
this.backgroundColor = Colors.white,
}) : assert(imageList != null),
assert(extraCountTextStyle != null),
assert(imageBorderColor != null),
assert(backgroundColor != null),
assert(totalCount != null),
super(key: key);
#override
Widget build(BuildContext context) {
var images = List<Widget>();
int _size = imageCount;
if (imageList.isNotEmpty) images.add(circularImage(imageList[0]));
if (imageList.length > 1) {
if (imageList.length < _size) {
_size = imageList.length;
}
images.addAll(imageList
.sublist(1, _size)
.asMap()
.map((index, image) => MapEntry(
index,
Positioned(
left: 0.8 * imageRadius * (index + 1.0),
child: circularImage(image),
),
))
.values
.toList());
}
return Container(
child: Row(
children: <Widget>[
images.isNotEmpty
? Stack(
overflow: Overflow.visible,
textDirection: TextDirection.rtl,
children: images.reversed.toList(),
)
: SizedBox(),
Container(
margin: EdgeInsets.only(left: imageRadius / 2 * imageCount + 5),
child: totalCount - images.length > 0
? Container(
constraints: BoxConstraints(minWidth: imageRadius),
padding: EdgeInsets.all(3),
height: imageRadius,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(imageRadius),
border: Border.all(color: imageBorderColor, width: imageBorderWidth),
color: backgroundColor),
child: Center(
child: Text(
'+${totalCount - images.length}',
textAlign: TextAlign.center,
style: extraCountTextStyle,
),
),
)
: SizedBox(),
),
],
),
);
}
Widget circularImage(String imageUrl) {
return Container(
height: imageRadius,
width: imageRadius,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: Colors.white,
width: imageBorderWidth,
),
),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
image: DecorationImage(
image: isLink(imageUrl) ? NetworkImage(imageUrl) : AssetImage(imageUrl),
fit: BoxFit.cover,
),
),
),
);
}
bool isLink(String str) {
var regex = RegExp('^(http|https):.*\.(co|org|in)');
return regex.hasMatch(str);
}
}
main.dart
import 'package:flutter/material.dart';
import 'custom_image_stack.dart';
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> {
List<String> images = [
"assets/ajay.png",
"https://i.stack.imgur.com/IJ8Ep.jpg?s=48&g=1",
"assets/ajay.png",
"https://i.stack.imgur.com/IJ8Ep.jpg?s=48&g=1",
"assets/ajay.png",
"https://i.stack.imgur.com/IJ8Ep.jpg?s=48&g=1",
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: CustomImageStack(
imageList: images,
imageCount: 3,
imageBorderWidth: 3,
totalCount: images.length,
)),
);
}
}
Hope it helps :)