Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
In my application, I want the switch is used to toggle a setting between on/off which is true/false respectively. When I went to build it, it turned out that Flutter provides a default switch, but it is not what I want. I want to customize it accordingly to my UI.
This is the Flutter switch button:
Here is what I want:
How can I make it possible for my UI?
Set
bool _switchValue = true;
in your screen.dart file:
CupertinoSwitch(
value: _switchValue,
onChanged: (value) {
setState(() {
_switchValue = value;
});
},
),
You can use package https://pub.dev/packages/custom_switch or fork it and modify it to your needs.
Full code
import 'package:custom_switch/custom_switch.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.deepOrange
),
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool status = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Custom Switch Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CustomSwitch(
activeColor: Colors.pinkAccent,
value: status,
onChanged: (value) {
print("VALUE : $value");
setState(() {
status = value;
});
},
),
SizedBox(height: 12.0,),
Text('Value : $status', style: TextStyle(
color: Colors.black,
fontSize: 20.0
),)
],
),
),
);
}
}
Create a custom switch class
class CustomSwitch extends StatefulWidget {
final bool value;
final ValueChanged<bool> onChanged;
CustomSwitch({Key? key, required this.value, required this.onChanged})
: super(key: key);
#override
_CustomSwitchState createState() => _CustomSwitchState();
}
class _CustomSwitchState extends State<CustomSwitch>
with SingleTickerProviderStateMixin {
Animation? _circleAnimation;
AnimationController? _animationController;
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 60));
_circleAnimation = AlignmentTween(
begin: widget.value ? Alignment.centerRight : Alignment.centerLeft,
end: widget.value ? Alignment.centerLeft : Alignment.centerRight)
.animate(CurvedAnimation(
parent: _animationController!, curve: Curves.linear));
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController!,
builder: (context, child) {
return GestureDetector(
onTap: () {
if (_animationController!.isCompleted) {
_animationController!.reverse();
} else {
_animationController!.forward();
}
widget.value == false
? widget.onChanged(true)
: widget.onChanged(false);
},
child: Container(
width: 45.0,
height: 28.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.0),
color: _circleAnimation!.value == Alignment.centerLeft
? Colors.grey
: Colors.blue,
),
child: Padding(
padding: const EdgeInsets.only(
top: 2.0, bottom: 2.0, right: 2.0, left: 2.0),
child: Container(
alignment:
widget.value ? ((Directionality.of(context) == TextDirection.rtl) ? Alignment.centerRight : Alignment.centerLeft ) : ((Directionality.of(context) == TextDirection.rtl) ? Alignment.centerLeft : Alignment.centerRight),
child: Container(
width: 20.0,
height: 20.0,
decoration: BoxDecoration(
shape: BoxShape.circle, color: Colors.white),
),
),
),
),
);
},
);
}
}
Call this class as widget and use the value parameter to set the state of the switch
bool _enable = false;
CustomSwitch(
value: _enable,
onChanged: (bool val){
setState(() {
_enable = val;
});
},
),
For a custom switch, I used this package:
https://pub.dev/packages/flutter_switch
You can customize the height and width of the switch, the border radius of the switch, the colors, the toggle size, etc.
Install:
dependencies:
flutter_switch: ^0.0.2
Import:
import 'package:flutter_switch/flutter_switch.dart';
Sample Usage:
FlutterSwitch(
height: 20.0,
width: 40.0,
padding: 4.0,
toggleSize: 15.0,
borderRadius: 10.0,
activeColor: lets_cyan,
value: isToggled,
onToggle: (value) {
setState(() {
isToggled = value;
});
},
),
You can achieve such a design using this package:
This usage is from their README file:
import 'package:custom_switch_button/custom_switch_button.dart';
bool isChecked = false;
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Custom Switch Button example app'),
),
body: GestureDetector(
onTap: () {
setState(() {
isChecked = !isChecked;
});
},
child: Center(
child: CustomSwitchButton(
backgroundColor: Colors.blueGrey,
unCheckedColor: Colors.white,
animationDuration: Duration(milliseconds: 400),
checkedColor: Colors.lightGreen,
checked: isChecked,
),
),
),
),
);
Final result:
Related
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)),
);
}
}
}
I can disable the tooltip statically.
But I want to disable tooltip dynamically when i click flatbutton.But Couldnt disable dynamically and i have no idea to do that.
This is my code:
import 'package:flutter/material.dart';
void main(){
runApp(MaterialApp(home: HelloWorld(),debugShowCheckedModeBanner: false,));
}
class HelloWorld extends StatefulWidget {
#override
_HelloWorldState createState() => _HelloWorldState();
}
class _HelloWorldState extends State<HelloWorld> {
bool check = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(children: <Widget>[
TopToolbar(),
FlatButton(
child: Text("Disable Tooltip"),
onPressed: () {
setState(() {
TopToolbar toolbar = new TopToolbar();
toolbar.showTooltip = false;
});
},
),
]),
),
));
}
}
class TopToolbar extends StatefulWidget {
bool showTooltip;
final Color backgroundColor;
final double height;
bool isVisible;
TopToolbar({
this.height = 55,
this.isVisible = true,
this.backgroundColor = const Color(0xFFEEEEEE),
Key key,this.showTooltip=true,
}) : super(key: key);
#override
_TopToolbarState createState() => _TopToolbarState();
}
class _TopToolbarState extends State<TopToolbar> {
#override
Widget build(BuildContext context) {
if (widget.isVisible) {
return Container(
foregroundDecoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey,
),
),
),
margin: EdgeInsets.only(bottom: 1),
color: widget.backgroundColor,
height: widget.height,
child: Stack(
children: <Widget>[
Positioned(
top: 7,
right: 60,
height: 40,
width: 40,
child: RawMaterialButton(
elevation: 0.0,
fillColor: widget.backgroundColor,
splashColor: Colors.grey[300],
child: IconButton(
icon: Icon(
Icons.bookmark,
color: Colors.grey[500],
size: 25,
),
onPressed: (){},
tooltip: widget.showTooltip ? "Bookmark" : null,
),
onPressed: (){},
),
),
],
),
);
} else {
return Container();
}
}
}
If I give statically false. it works fine.
For example : If add child like TopToolbar(showTooltip : false),it works fine,
But If i give toolbar.showTooltip = false in Flatbutton onPressed method,it doesnt work.
I want to disble it in dynamically. please help me to do that.
we can hide or deactivate tooltip programmatically like below,
Future.delayed(
Duration(seconds: 2),
() {
tooltip?.deactivate();
}
);
Here, we can set time according to your requirement.(Currently, we are set 2 sec.)
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: HelloWorld(),
debugShowCheckedModeBanner: false,
));
}
class HelloWorld extends StatefulWidget {
#override
_HelloWorldState createState() => _HelloWorldState();
}
class _HelloWorldState extends State<HelloWorld> {
bool check = false;
bool showTooltip = true;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(children: <Widget>[
TopToolbar(showTooltip: showTooltip),
FlatButton(
child: Text("Disable Tooltip"),
onPressed: () {
setState(() {
showTooltip = false;
});
},
),
]),
),
));
}
}
class TopToolbar extends StatefulWidget {
final bool showTooltip;
final Color backgroundColor;
final double height;
final bool isVisible;
TopToolbar({
this.height = 55,
this.isVisible = true,
this.backgroundColor = const Color(0xFFEEEEEE),
Key key,
this.showTooltip = true,
}) : super(key: key);
#override
_TopToolbarState createState() => _TopToolbarState();
}
class _TopToolbarState extends State<TopToolbar> {
#override
Widget build(BuildContext context) {
if (widget.isVisible) {
return Container(
foregroundDecoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey,
),
),
),
margin: EdgeInsets.only(bottom: 1),
color: widget.backgroundColor,
height: widget.height,
child: Stack(
children: <Widget>[
Positioned(
top: 7,
right: 60,
height: 40,
width: 40,
child: RawMaterialButton(
elevation: 0.0,
fillColor: widget.backgroundColor,
splashColor: Colors.grey[300],
child: IconButton(
icon: Icon(
Icons.bookmark,
color: Colors.grey[500],
size: 25,
),
onPressed: () {},
tooltip: widget.showTooltip ? 'Bookmark' : null,
),
onPressed: () {},
),
),
],
),
);
} else {
return Container();
}
}
}
I've used this method to hide tooltips:
Tooltip(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0),
),
textStyle: TextStyle(color: Colors.white.withOpacity(0)),
message: 'Certificates',
child: Container()
);
Make the property message='' // empty string
setState((){messageText=''});
Tooltip(
message: messageText,
...
)
I hope you are doing well today. I'm having an issue with this horizontal scroll in flutter. The images are supposed to scroll left and right and depending on the picture, you will press the button and have the ability to guess the type of pic. For some reason, images and tags don't match with images. The image names are linked to the vehicleNames list in _MyHomePageState. I have also included image_card.dart to show how ImageCard works. Thank you for the second set of eyes.
main.dart
import 'dart:ui';
import 'package:flutter/material.dart';
import 'image_card.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Guess the car!'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin{
String curVehName = "";
double scrollPercent = 0.0;
Offset startDrag;
double startDragPercentScroll;
double finishScrollStart;
double finishScrollEnd;
AnimationController finishScrollController;
List<String> vehicleNames = [
'bmw',
'ford',
'rover',
'toyota'
];
#override
initState(){
super.initState();
finishScrollController = AnimationController(
duration: Duration(milliseconds: 150),
vsync: this,
)..addListener(() {
setState(() {
scrollPercent = lerpDouble(finishScrollStart, finishScrollEnd,
finishScrollController.value);
});
});
#override
dispose(){
finishScrollController.dispose();
super.dispose();
}
}
List<Widget> buildCards(){
List<Widget> cardList = [];
for(int i = 0; i < vehicleNames.length;i++){
cardList.add(buildCard(i,scrollPercent));
print("index: ${i}");
}
return cardList;
}
Widget buildCard(int cardIndex, double scrollPercent){
final cardScrollPercent = scrollPercent / ( 1 / vehicleNames.length);
return FractionalTranslation(
translation: Offset(cardIndex-cardScrollPercent,0.0),
child: Padding(
padding: EdgeInsets.all(8.0),
child: ImageCard(imageName: vehicleNames[cardIndex],
),
),
);
}
onHorizontalDragStart(DragStartDetails details){
startDrag = details.globalPosition;
startDragPercentScroll = scrollPercent;
}
onHorizontalDragUpdate(DragUpdateDetails details){
final currentDrag = details.globalPosition;
final dragDistance = currentDrag.dx - startDrag.dx;
final singleCardDragPercent = dragDistance / context.size.width;
setState(() {
scrollPercent = ( startDragPercentScroll + ( -singleCardDragPercent
/ vehicleNames.length)).clamp(0.0, 1.0-(1/vehicleNames.length));
});
}
onHorizontalDragEnd(DragEndDetails details){
finishScrollStart = scrollPercent;
finishScrollEnd = (scrollPercent * vehicleNames.length).round()
/vehicleNames.length;
finishScrollController.forward(from: 0.0);
setState(() {
startDrag = null;
startDragPercentScroll = null;
curVehName = '';
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
GestureDetector(
onHorizontalDragStart: onHorizontalDragStart,
onHorizontalDragUpdate: onHorizontalDragUpdate,
onHorizontalDragEnd: onHorizontalDragEnd,
behavior: HitTestBehavior.translucent ,
child: Stack(
children: buildCards(),
),
),
OutlineButton(
padding: EdgeInsets.all(10.0),
onPressed: (){
setState((){
this.curVehName = vehicleNames[(scrollPercent*10).round()];
});
},
child: Text(
'Show Answer',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
borderSide: BorderSide(
color: Colors.black,
width: 4.0,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
highlightedBorderColor: Colors.black,
),
Text(
curVehName,
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
color: Colors.blue,
letterSpacing: 2,
),
),
],
),
),
);
}
}
image_card.dart
import 'package:flutter/material.dart';
class ImageCard extends StatelessWidget{
final String imageName;
ImageCard({this.imageName});
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0),
border: Border.all(
color: Colors.black,
width: 4.0,
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Image.asset(
'assets/images/$imageName.jpg',
height: 300,
fit: BoxFit.fitHeight,
),
),
);
}
}
I believe I found the issue. It seems that the
this.curVehName = vehicleNames[(scrollPercent*10).round()];
hard-coded the value of numbers needed in my vehicle names list. Once I added 10 pictures and added names to the list, it then worked as directed. The goal now is to see if I can make this a dynamic list.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
In my application, I want the switch is used to toggle a setting between on/off which is true/false respectively. When I went to build it, it turned out that Flutter provides a default switch, but it is not what I want. I want to customize it accordingly to my UI.
This is the Flutter switch button:
Here is what I want:
How can I make it possible for my UI?
Set
bool _switchValue = true;
in your screen.dart file:
CupertinoSwitch(
value: _switchValue,
onChanged: (value) {
setState(() {
_switchValue = value;
});
},
),
You can use package https://pub.dev/packages/custom_switch or fork it and modify it to your needs.
Full code
import 'package:custom_switch/custom_switch.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.deepOrange
),
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool status = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Custom Switch Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CustomSwitch(
activeColor: Colors.pinkAccent,
value: status,
onChanged: (value) {
print("VALUE : $value");
setState(() {
status = value;
});
},
),
SizedBox(height: 12.0,),
Text('Value : $status', style: TextStyle(
color: Colors.black,
fontSize: 20.0
),)
],
),
),
);
}
}
Create a custom switch class
class CustomSwitch extends StatefulWidget {
final bool value;
final ValueChanged<bool> onChanged;
CustomSwitch({Key? key, required this.value, required this.onChanged})
: super(key: key);
#override
_CustomSwitchState createState() => _CustomSwitchState();
}
class _CustomSwitchState extends State<CustomSwitch>
with SingleTickerProviderStateMixin {
Animation? _circleAnimation;
AnimationController? _animationController;
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 60));
_circleAnimation = AlignmentTween(
begin: widget.value ? Alignment.centerRight : Alignment.centerLeft,
end: widget.value ? Alignment.centerLeft : Alignment.centerRight)
.animate(CurvedAnimation(
parent: _animationController!, curve: Curves.linear));
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController!,
builder: (context, child) {
return GestureDetector(
onTap: () {
if (_animationController!.isCompleted) {
_animationController!.reverse();
} else {
_animationController!.forward();
}
widget.value == false
? widget.onChanged(true)
: widget.onChanged(false);
},
child: Container(
width: 45.0,
height: 28.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.0),
color: _circleAnimation!.value == Alignment.centerLeft
? Colors.grey
: Colors.blue,
),
child: Padding(
padding: const EdgeInsets.only(
top: 2.0, bottom: 2.0, right: 2.0, left: 2.0),
child: Container(
alignment:
widget.value ? ((Directionality.of(context) == TextDirection.rtl) ? Alignment.centerRight : Alignment.centerLeft ) : ((Directionality.of(context) == TextDirection.rtl) ? Alignment.centerLeft : Alignment.centerRight),
child: Container(
width: 20.0,
height: 20.0,
decoration: BoxDecoration(
shape: BoxShape.circle, color: Colors.white),
),
),
),
),
);
},
);
}
}
Call this class as widget and use the value parameter to set the state of the switch
bool _enable = false;
CustomSwitch(
value: _enable,
onChanged: (bool val){
setState(() {
_enable = val;
});
},
),
For a custom switch, I used this package:
https://pub.dev/packages/flutter_switch
You can customize the height and width of the switch, the border radius of the switch, the colors, the toggle size, etc.
Install:
dependencies:
flutter_switch: ^0.0.2
Import:
import 'package:flutter_switch/flutter_switch.dart';
Sample Usage:
FlutterSwitch(
height: 20.0,
width: 40.0,
padding: 4.0,
toggleSize: 15.0,
borderRadius: 10.0,
activeColor: lets_cyan,
value: isToggled,
onToggle: (value) {
setState(() {
isToggled = value;
});
},
),
You can achieve such a design using this package:
This usage is from their README file:
import 'package:custom_switch_button/custom_switch_button.dart';
bool isChecked = false;
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Custom Switch Button example app'),
),
body: GestureDetector(
onTap: () {
setState(() {
isChecked = !isChecked;
});
},
child: Center(
child: CustomSwitchButton(
backgroundColor: Colors.blueGrey,
unCheckedColor: Colors.white,
animationDuration: Duration(milliseconds: 400),
checkedColor: Colors.lightGreen,
checked: isChecked,
),
),
),
),
);
Final result:
I am trying to create a number input field with and up and down arrow button to increment and decrement its value. I am wondering if there is any inbuilt widget which already provides this functionality. I have to create couple of such fields in my UI and creating so many stateful widgets makes me wonder if there is any simpler approach.
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final title = 'Increment Decrement Demo';
return MaterialApp(
title: title,
home: NumberInputWithIncrementDecrement(),
);
}
}
class NumberInputWithIncrementDecrement extends StatefulWidget {
#override
_NumberInputWithIncrementDecrementState createState() =>
_NumberInputWithIncrementDecrementState();
}
class _NumberInputWithIncrementDecrementState
extends State<NumberInputWithIncrementDecrement> {
TextEditingController _controller = TextEditingController();
#override
void initState() {
super.initState();
_controller.text = "0"; // Setting the initial value for the field.
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Number Field increment decrement'),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: TextFormField(
controller: _controller,
keyboardType: TextInputType.numberWithOptions(
decimal: false, signed: false),
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
MaterialButton(
minWidth: 5.0,
child: Icon(Icons.arrow_drop_up),
onPressed: () {
int currentValue = int.parse(_controller.text);
setState(() {
currentValue++;
_controller.text =
(currentValue).toString(); // incrementing value
});
},
),
MaterialButton(
minWidth: 5.0,
child: Icon(Icons.arrow_drop_down),
onPressed: () {
int currentValue = int.parse(_controller.text);
setState(() {
print("Setting state");
currentValue--;
_controller.text =
(currentValue).toString(); // decrementing value
});
},
),
],
),
Spacer(
flex: 2,
)
],
),
),
);
}
}
current output looks some thing like this.
I am looking for something like the following in a simpler manner like in HTML number input field.
I have laid out my Number input widget as shown below. I think I will go ahead with this approach until someone has any different idea for the same.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final title = 'Increment Decrement Demo';
return MaterialApp(
title: title,
home: NumberInputWithIncrementDecrement(),
);
}
}
class NumberInputWithIncrementDecrement extends StatefulWidget {
#override
_NumberInputWithIncrementDecrementState createState() =>
_NumberInputWithIncrementDecrementState();
}
class _NumberInputWithIncrementDecrementState
extends State<NumberInputWithIncrementDecrement> {
TextEditingController _controller = TextEditingController();
#override
void initState() {
super.initState();
_controller.text = "0"; // Setting the initial value for the field.
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Number Field increment decrement'),
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: Container(
width: 60.0,
foregroundDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
border: Border.all(
color: Colors.blueGrey,
width: 2.0,
),
),
child: Row(
children: <Widget>[
Expanded(
flex: 1,
child: TextFormField(
textAlign: TextAlign.center,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(8.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
),
),
controller: _controller,
keyboardType: TextInputType.numberWithOptions(
decimal: false,
signed: true,
),
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
),
),
Container(
height: 38.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.5,
),
),
),
child: InkWell(
child: Icon(
Icons.arrow_drop_up,
size: 18.0,
),
onTap: () {
int currentValue = int.parse(_controller.text);
setState(() {
currentValue++;
_controller.text = (currentValue)
.toString(); // incrementing value
});
},
),
),
InkWell(
child: Icon(
Icons.arrow_drop_down,
size: 18.0,
),
onTap: () {
int currentValue = int.parse(_controller.text);
setState(() {
print("Setting state");
currentValue--;
_controller.text =
(currentValue > 0 ? currentValue : 0)
.toString(); // decrementing value
});
},
),
],
),
),
],
),
),
),
),
);
}
}
Update:
As I see many of us like this approach I created a package for the same. Maybe its helpful for some of us.
number_inc_dec
I was looking for a simple -/+ step counter, so I made one... don't pretend too much, I'm using flutter since a couple of days :-)
It has a maximum and minimum value, by default the minimum is set to zero and maximum to 10, but if you need negative values, just set it to -N.
Preview
Widget source
import 'package:flutter/material.dart';
class NumericStepButton extends StatefulWidget {
final int minValue;
final int maxValue;
final ValueChanged<int> onChanged;
NumericStepButton(
{Key key, this.minValue = 0, this.maxValue = 10, this.onChanged})
: super(key: key);
#override
State<NumericStepButton> createState() {
return _NumericStepButtonState();
}
}
class _NumericStepButtonState extends State<NumericStepButton> {
int counter= 0;
#override
Widget build(BuildContext context) {
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
icon: Icon(
Icons.remove,
color: Theme.of(context).accentColor,
),
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 18.0),
iconSize: 32.0,
color: Theme.of(context).primaryColor,
onPressed: () {
setState(() {
if (counter > widget.minValue) {
counter--;
}
widget.onChanged(counter);
});
},
),
Text(
'$counter',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black87,
fontSize: 18.0,
fontWeight: FontWeight.w500,
),
),
IconButton(
icon: Icon(
Icons.add,
color: Theme.of(context).accentColor,
),
padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 18.0),
iconSize: 32.0,
color: Theme.of(context).primaryColor,
onPressed: () {
setState(() {
if (counter < widget.maxValue) {
counter++;
}
widget.onChanged(counter);
});
},
),
],
),
);
}
}
Read the counter value
...
int yourLocalVariable = 0;
...
return Container(
child: NumericStepButton(
maxValue: 20,
onChanged: (value) {
yourLocalVariable = value;
},
),
)
],
...
Happy coding!
This is the most complete solution you can find
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
class NumberTextField extends StatefulWidget {
final TextEditingController? controller;
final FocusNode? focusNode;
final int min;
final int max;
final int step;
final double arrowsWidth;
final double arrowsHeight;
final EdgeInsets contentPadding;
final double borderWidth;
final ValueChanged<int?>? onChanged;
const NumberTextField({
Key? key,
this.controller,
this.focusNode,
this.min = 0,
this.max = 999,
this.step = 1,
this.arrowsWidth = 24,
this.arrowsHeight = kMinInteractiveDimension,
this.contentPadding = const EdgeInsets.symmetric(horizontal: 8),
this.borderWidth = 2,
this.onChanged,
}) : super(key: key);
#override
State<StatefulWidget> createState() => _NumberTextFieldState();
}
class _NumberTextFieldState extends State<NumberTextField> {
late TextEditingController _controller;
late FocusNode _focusNode;
bool _canGoUp = false;
bool _canGoDown = false;
#override
void initState() {
super.initState();
_controller = widget.controller ?? TextEditingController();
_focusNode = widget.focusNode ?? FocusNode();
_updateArrows(int.tryParse(_controller.text));
}
#override
void didUpdateWidget(covariant NumberTextField oldWidget) {
super.didUpdateWidget(oldWidget);
_controller = widget.controller ?? _controller;
_focusNode = widget.focusNode ?? _focusNode;
_updateArrows(int.tryParse(_controller.text));
}
#override
Widget build(BuildContext context) => TextField(
controller: _controller,
focusNode: _focusNode,
textInputAction: TextInputAction.done,
keyboardType: TextInputType.number,
maxLength: widget.max.toString().length + (widget.min.isNegative ? 1 : 0),
decoration: InputDecoration(
counterText: '',
isDense: true,
filled: true,
fillColor: Theme.of(context).colorScheme.surface,
contentPadding: widget.contentPadding.copyWith(right: 0),
suffixIconConstraints: BoxConstraints(
maxHeight: widget.arrowsHeight, maxWidth: widget.arrowsWidth + widget.contentPadding.right),
suffixIcon: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(widget.borderWidth), bottomRight: Radius.circular(widget.borderWidth))),
clipBehavior: Clip.antiAlias,
alignment: Alignment.centerRight,
margin: EdgeInsets.only(
top: widget.borderWidth,
right: widget.borderWidth,
bottom: widget.borderWidth,
left: widget.contentPadding.right),
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
Expanded(
child: Material(
type: MaterialType.transparency,
child: InkWell(
child: Opacity(opacity: _canGoUp ? 1 : .5, child: const Icon(Icons.arrow_drop_up)),
onTap: _canGoUp ? () => _update(true) : null))),
Expanded(
child: Material(
type: MaterialType.transparency,
child: InkWell(
child: Opacity(opacity: _canGoDown ? 1 : .5, child: const Icon(Icons.arrow_drop_down)),
onTap: _canGoDown ? () => _update(false) : null))),
]))),
maxLines: 1,
onChanged: (value) {
final intValue = int.tryParse(value);
widget.onChanged?.call(intValue);
_updateArrows(intValue);
},
inputFormatters: [_NumberTextInputFormatter(widget.min, widget.max)]);
void _update(bool up) {
var intValue = int.tryParse(_controller.text);
intValue == null ? intValue = 0 : intValue += up ? widget.step : -widget.step;
_controller.text = intValue.toString();
_updateArrows(intValue);
_focusNode.requestFocus();
}
void _updateArrows(int? value) {
final canGoUp = value == null || value < widget.max;
final canGoDown = value == null || value > widget.min;
if (_canGoUp != canGoUp || _canGoDown != canGoDown)
setState(() {
_canGoUp = canGoUp;
_canGoDown = canGoDown;
});
}
}
class _NumberTextInputFormatter extends TextInputFormatter {
final int min;
final int max;
_NumberTextInputFormatter(this.min, this.max);
#override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
if (const ['-', ''].contains(newValue.text)) return newValue;
final intValue = int.tryParse(newValue.text);
if (intValue == null) return oldValue;
if (intValue < min) return newValue.copyWith(text: min.toString());
if (intValue > max) return newValue.copyWith(text: max.toString());
return newValue.copyWith(text: intValue.toString());
}
}
Simple, BLoC-friendly approach:
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _controller = TextEditingController();
final _streamController = StreamController<int>();
Stream<int> get _stream => _streamController.stream;
Sink<int> get _sink => _streamController.sink;
int initValue = 1;
#override
void initState() {
_sink.add(initValue);
_stream.listen((event) => _controller.text = event.toString());
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
children: [
TextButton(
onPressed: () {
_sink.add(--initValue);
},
child: Icon(Icons.remove)),
Container(
width: 50,
child: TextField(
controller: _controller,
),
),
TextButton(
onPressed: () {
_sink.add(++initValue);
},
child: Icon(Icons.add)),
],
)
],
),
),
);
}
#override
void dispose() {
_streamController.close();
_controller.dispose();
super.dispose();
}
}