I have a list of buttons in row , there are 4 items or buttons in list,
I have made a model class for creating data for each button, the list is model type.
all working fine, I want to highlight or select the button, on which I pressed and rest index button should be deselected.
I'm able to highlight the pressed button but unable to deselect the rest buttons.
code
//Button model class
class TopButtonModel{
String buttonLable;
bool isOnTp;
TopButtonModel({this.buttonLable, this.isOnTp});
}
//initializing list with these data
int myIndex=0;
List<TopButtonModel> buttons = [
TopButtonModel(
buttonLable: 'Skin',
isOnTp: true
),
TopButtonModel(
buttonLable: 'Hair Style',
isOnTp: false
),
TopButtonModel(
buttonLable: 'Makeup',
isOnTp: false
),
TopButtonModel(
buttonLable: 'Handset & Feet',
isOnTp: false
),
];
buttonsRow() {
return Container(
color: Colors.white,
child: ListView.builder(
// Rpadding: const EdgeInsets.only(right: 9),
scrollDirection: Axis.horizontal,
itemCount: buttons.length,
itemBuilder:(context, index) {
// myIndex =in
return Padding(
padding: const EdgeInsets.only(right: 9,top: 9),
child: FlatButton(
color: buttons[index].isOnTp?Colors.indigo:Colors.grey[200],
onPressed: () {
print(index);
setState(() {
myIndex =index;
buttons[index].isOnTp =true;
});
// if (buttons.any((item) => item.isOnTp)) {
// setState(() {
// buttons[index].isOnTp = !buttons[index].isOnTp;
// });
// }
},
child: Text(buttons[index].buttonLable,
style: TextStyle(color: buttons[index].isOnTp?Colors.white:Colors.black),
)),
);
}
),
) ;
}
wanna achieve this
and what is happening
This should be as simple as this:
setState(() {
buttons[myIndex].isOnTp = false;
myIndex = index;
buttons[index].isOnTp = true;
});
Here is a working code example: Code Pen
Related
I have a code whereby the genders of a pet is listed in two separate cards and when the user taps on one of them, it changes color to indicate that it has been selected and is saved in the database. However, the app is letting the user continue to the next page without choosing any one of the values. I want to do a validation whereby the user will have to choose one of the cards to be able to move forward. How can I do this please?
Here is my code:
Expanded(
child: GridView.count(
crossAxisCount: 2,
primary: false,
scrollDirection: Axis.vertical,
children: List.generate(petGenders.length, (index) {
return GestureDetector(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
color:
selectedIndex == index ? primaryColor : null,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
petGenders[petKeys[index]],
SizedBox(
height: 15.0,
),
Text(
petKeys[index],
style: TextStyle(
color: selectedIndex == index
? Colors.white
: null,
fontSize: 18.0,
fontWeight: FontWeight.w600),
),
],
),
),
),
onTap: () {
setState(() {
widget.pet.gender = petKeys[index];
selectedIndex = index;
});
});
}),
),
),
The model:
Map<String, Image> genders() => {
"Male":
Image(image: AssetImage('Assets/images/male.png'), width: 50),
"Female":
Image(image: AssetImage('Assets/images/female.png'), width: 50)
};
Take one variable
bool isGenderSelected = false;
Then change its value to true on tap of card like
onTap: () {
setState(() {
isGenderSelected = true;
widget.pet.gender = petKeys[index];
selectedIndex = index;
});
});
Now check if it's true then only allow the user to go next page or show some message to the user
Scenario like this, I prefer using nullable selectedValue. In this case, I will create nullable int to hold and switch between selection.
int? selectedIndex;
And using color will be like
color: selectedIndex==index? SelectedColor:null,
you can replace null with inactive color.
For validation part, do null check on selectedIndex .
if(selectedIndex!=null){.....}
I want to figure out the position of scroll and depending on that results, I want to show some buttons.
This is my code of Scroll Widget.
Flexible(
child: Container(
constraints: BoxConstraints(
maxWidth: 730,
),
child: ListView.separated(
controller: _platformController,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
String platform = platformlistA[index].toLowerCase();
return InkWell(
onTap: () {
if (platformIndex !=
platformlistA.indexWhere(
(element) => element == platformlistA[index])) {
setState(() {
platformIndex = platformlistA.indexWhere(
(element) => element == platformlistA[index]);
});
} else {
setState(() {
platformIndex = -1;
});
}
},
child: Container(
alignment: Alignment.center,
height: 37,
width: platform != 'fortnite' ? 100 : 85,
padding: EdgeInsets.symmetric(
horizontal: 5,
),
child: WebsafeSvg.asset(
'assets/$platform.svg',
color: platformIndex ==
platformlistA.indexWhere(
(element) => element == platformlistA[index])
? Colors.black
: Color(0xffb7b7b7),
),
),
);
},
separatorBuilder: (context, index) {
return SizedBox(width: 7);
},
itemCount: platformlistA.length),
),
),
and this is the code getting the position of Scroll widget.
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
setState(() {
if (_platformController.position.maxScrollExtent > 0) {
if (_platformController.position.atEdge) {
if (_platformController.position.pixels == 0) {
print('left edge');
// show -> button at right
} else {
print('right edge');
// show <- button at left
}
} else {
print('middle of the scroll');
// show <-, -> buttons at both side
}
} else {
print('not scrollable');
// do not show any button.
}
});
});
I used WidgetsBinding.instance!.addPostFrameCallback because, it shows error if I handle with controller before the build.
Eventually, this works functionally, but it is too slow since WidgetsBinding.instance!.addPostFrameCallback in build function continues to run. I cannot put it in initstate because if() phrase has to be called everytime when the size of web application changes.
Is there any faster way than this method??? Please help!!
Map<String, dynamic> toJson() => {
'name': name,
'email': email,
};
Try adding => front of toJson()
Can anyone please help to implement this feature of Gmail that shows the counter to number of emails hidden when the email list becomes large ? I want to implement this in row widget where instead of being scrollable extra elements count is shown when overflow occurs.Gmail shows +15 counter for hidden emails
I was Curious to give a try to achieve the same effect, as asked.
Just in case, If anyone want a start for writing a custom one, then below code may help.
Here is my Code, Feel free to give any suggestions,
(For Now delete button in chips is not working bcoz of some logic problem, I will make it work another day)
import 'package:flutter/material.dart';
class Demo3 extends StatefulWidget {
#override
_Demo3State createState() => _Demo3State();
}
class _Demo3State extends State<Demo3> {
String temp = "";
bool showChips = false;
List<Widget> chipsList = new List();
TextEditingController textEditingController = new TextEditingController();
final _focusNode = FocusNode();
int countChipsToDeleteLater = 0;
#override
void initState() {
super.initState();
_focusNode.addListener(() {
print("Has focus: ${_focusNode.hasFocus}");
if (!_focusNode.hasFocus) {
showChips = false;
setState(() {});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: new Container(
height: 500,
child: new Center(
child: Container(
width: 300,
child: !showChips
? Row(
children: [
buildTextField(),
showNumberWidgetIfAny(),
],
)
: Center(
child: Wrap(
children: [
Wrap(
children: buildChips(),
),
buildTextField(),
],
),
),
),
),
),
),
);
}
buildChips() {
return chipsList;
}
buildTextField() {
return Container(
width: 200,
child: new TextField(
showCursor: true,
focusNode: _focusNode,
autofocus: true,
cursorColor: Colors.black,
style: new TextStyle(fontSize: 22.0, color: Colors.black),
controller: textEditingController,
// decoration: InputDecoration.collapsed(
// hintText: "",
// ),
onChanged: (value) {
if (value.contains(" ")) {
checkWhatToStoreInChips(value, countChipsToDeleteLater);
textEditingController.clear();
setState(() {
showChips = true;
});
countChipsToDeleteLater++;
}
},
),
);
}
checkWhatToStoreInChips(String val, int chipsIndex) {
temp = "";
for (int i = 0; i < val.length; i++) {
if (val[i] == " ") {
break;
}
temp = temp + val[i];
}
addToChips(temp, chipsIndex);
}
addToChips(String tmp, int chipsIndex) {
chipsList.add(Chip(
// onDeleted: () {
// if (chipsList.length == 0) {
// countChipsToDeleteLater = 0;
// }
// chipsList.removeAt(chipsIndex);
// print(chipsList.length);
// print(chipsIndex);
// setState(() {});
// },
avatar: CircleAvatar(
backgroundColor: Colors.grey.shade800,
child: Text(tmp[0]),
),
label: Text(temp),
));
}
showNumberWidgetIfAny() {
int len = chipsList.length;
if (len >= 1) {
return GestureDetector(
onTap: () {
showChips = true;
setState(() {});
},
child: new Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.blue,
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: new Text(
"${chipsList.length.toString()} ",
style: new TextStyle(color: Colors.white, fontSize: 22),
),
),
),
);
}
return Container();
}
}
How it works:
Write something in text field, then press space, showChips boolean will become true
onChanged will detect the space and will send the string to a function.
That function will extract the string before space and then will add the string to a chip,
Finally the chip will be added to a chipslist.
We will have a boolean variable to check if the textfield is in focus and when to show the textfield and numberwidget (a widget which will keep count of the total chips, same like you asked in your question) or when to show the chipslist and textfield wraped in a wrap widget.
You can play around by changing the decoration of textfield to collapsed, to it look like the same as gmail.
Check this package, if you want to use custom package for ease.
I was facing a similar issue. I found a way to implement the Overflow count text.
Sample image
You basically have to paint the overflow text, and get its width like below
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: style),
textDirection: TextDirection.ltr,
textScaleFactor: WidgetsBinding.instance.window.textScaleFactor,
)..layout();
var textSize = textPainter.size;
textSize.width;
Then subtract that from the width available. Lets call it x.
Then create a sum of width for each row item(using TextPainter.layout() method mentioned above), till its value is less than x.
This way you'll know how many items can be shown in the row.
I have created a Flutter library to help with this.
Sorry, I'm relatively new to Flutter and having some trouble. I want to initiate functions when I press my first, second, or third button. These buttons initiate map markers, polylines, etc. They work fine when assigned onPress with an icon button... I can't figure out how to get them to work with a ToggleButton... Do they have to be added to a list somehow? If so, how?
Here are my three buttons:
Consumer<ProviderMaps>(builder: (context, menu, widget) {
return Container(
padding: EdgeInsets.zero,
decoration: BoxDecoration(
border: Border.all(color: Colors.transparent, width: 1.0),
borderRadius: BorderRadius.all(Radius.circular(5.0)),
),
child: ToggleButtons(
selectedColor: Colors.red,
color: Colors.white,
children: <Widget>[
Icon(MdiIcons.vectorPolygon),
Icon(MdiIcons.ruler),
Icon(MdiIcons.pencil),
],
onPressed: (int index) {
setState(() {
for (int buttonIndex = 0;
buttonIndex < _selection.length;
buttonIndex++) {
if (buttonIndex == index) {
_selection[buttonIndex] = !_selection[buttonIndex];
} else {
_selection[buttonIndex] = false;
}
}
});
},
isSelected: _selection,
),
);
}),
My list:
List<bool> _selection = List.generate(3, (_) => false);
I'd like to call functions like these when I switch buttons:
menu.changestatutpolyg();
menu.changestatupolyli();
_initFreeDraw();
This is how I normally call these functions (works fine):
Consumer<ProviderMaps>(builder: (context, menu, widget) {
return IconButton(
icon: Icon(MdiIcons.ruler),
color: Color(0xffFFFFFF),
onPressed: () {
menu.changestatupolyli();
});
}),
Anybody know how to do this? Please advise.
Edit:
Attempt#:
onPressed: (int index) {
setState(
() {
switch (index) {
case 1:
{
menu.changestatutpolyg();
}
break;
case 2:
{
menu.changestatupolyli();
}
break;
case 3:
{
_initFreeDraw();
}
break;
}
},
);
},
isSelected: _selection,
),
);
}),
Well, the ToggleButtons's onPressed does provide you the index of the button pressed. So, the index would be 0 if the first button is pressed, one if the second, etc.
So in your onPressed, you can check the index, and based on that call the adequate function (regular if statements work fine as well):
onPressed: (int index) {
switch(index) {
case 0: {
callFunction1();
}
break;
case 1: {
callFunction2();
}
break;
//etc.
}
},
Or better, you can place the functions in an array (make sure to place them in the right order), and do something like:
var functions = <Function>[function1, function2, function3];
//...
//then, in the onPressed:
onPressed: (int index) {
//call the function of that index
functions[index]();
}
How to remove the TextField when user click on "clear icon" button ? (not just clear the Text of TextField)
User Story
The user click on a button to add player. (Technically this button add TextField)
The user can write the name of player on TextField.
The user click on a "clear icon" button to remove current TextField (opposite of add function).
new ListView.builder(
padding: EdgeInsets.all(0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: 5,
itemBuilder: (context, index) {
print(index);
return TextField(
maxLength: 20,
decoration: InputDecoration(
labelText: "Player ${index+1}",
counterText: "",
prefixIcon: const Icon(Icons.person),
suffixIcon: new IconButton(
icon: Icon(Icons.clear),
onPressed: () =>
setState(() {
this.dispose(); // -----Doesn't work----
})
),
),
);
}
),
For example, user set "John" on Player 4 if user click on "clear button" then Player 4 TextField is deleted. It will remain only 4 TextField
Facts I assume:
you want to be able to delete (or add) a field from (to) the list
you want the values of the remaining fields to remain when you delete the field
the list can be larger than 5
Solution:
If you want all the above to be true, then you actually need to track the TextEditingControllers of the TextFields, instead of the text fields themselves. This is because the value of the TextField is actually stored in the TextEditingController (which is created anew on the fly if you do not supply it for each widget). Check this out:
import 'package:flutter/material.dart';
// needs to be StatefulWidget, so we can keep track of the count of the fields internally
class PlayerList extends StatefulWidget {
const PlayerList({
this.initialCount = 5,
});
// also allow for a dynamic number of starting players
final int initialCount;
#override
_PlayerListState createState() => _PlayerListState();
}
class _PlayerListState extends State<PlayerList> {
int fieldCount = 0;
int nextIndex = 0;
// you must keep track of the TextEditingControllers if you want the values to persist correctly
List<TextEditingController> controllers = <TextEditingController>[];
// create the list of TextFields, based off the list of TextControllers
List<Widget> _buildList() {
int i;
// fill in keys if the list is not long enough (in case we added one)
if (controllers.length < fieldCount) {
for (i = controllers.length; i < fieldCount; i++) {
controllers.add(TextEditingController());
}
}
i = 0;
// cycle through the controllers, and recreate each, one per available controller
return controllers.map<Widget>((TextEditingController controller) {
int displayNumber = i + 1;
i++;
return TextField(
controller: controller,
maxLength: 20,
decoration: InputDecoration(
labelText: "Player $displayNumber",
counterText: "",
prefixIcon: const Icon(Icons.person),
suffixIcon: IconButton(
icon: Icon(Icons.clear),
onPressed: () {
// when removing a TextField, you must do two things:
// 1. decrement the number of controllers you should have (fieldCount)
// 2. actually remove this field's controller from the list of controllers
setState(() {
fieldCount--;
controllers.remove(controller);
});
},
),
),
);
}).toList(); // convert to a list
}
#override
Widget build(BuildContext context) {
// generate the list of TextFields
final List<Widget> children = _buildList();
// append an 'add player' button to the end of the list
children.add(
GestureDetector(
onTap: () {
// when adding a player, we only need to inc the fieldCount, because the _buildList()
// will handle the creation of the new TextEditingController
setState(() {
fieldCount++;
});
},
child: Container(
color: Colors.blue,
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
'add player',
style: TextStyle(
color: Colors.white,
),
),
),
),
),
);
// build the ListView
return ListView(
padding: EdgeInsets.all(0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: children,
);
}
#override
void initState() {
super.initState();
// upon creation, copy the starting count to the current count
fieldCount = widget.initialCount;
}
#override
void dispose() {
super.dispose();
}
#override
void didUpdateWidget(PlayerList oldWidget) {
super.didUpdateWidget(oldWidget);
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
}
}
With the above, you can:
start the app
change player 2 to 'bob'
change player 3 to 'steve'
change player 4 to 'charles'
delete player 3
observe that player 2 is 'bob' and the new player 3 is 'charles'
I think this is what you are looking for here.
You can have a property to check wheter the user clicked that button and depending on the value you show/hide the TextField. Below I just use a boolean property if user click on X button then I set hideField to true and the TextField will be replaced with a zero sized widget.
new ListView.builder(
padding: EdgeInsets.all(0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: 5,
itemBuilder: (context, index) {
print(index);
bool hideField = false; // I added it here
return hideField ? SizedBox.shrink() : TextField(
maxLength: 20,
decoration: InputDecoration(
labelText: "Player ${index + 1}",
counterText: "",
prefixIcon: const Icon(Icons.person),
suffixIcon: new IconButton(
icon: Icon(Icons.clear),
onPressed: () =>
setState(() {
hideField = true; // Now it works
})
),
),
);
}
)