Flutter: Is there a widget to flex toggle buttons - flutter

So I'm the ToggleButtons introduced in 1.9.1 like this:
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(padding: EdgeInsets.only(top: 8),),
Text('Sort By: '),
Align(
alignment: Alignment.topLeft,
child: ToggleButtons(
borderColor: Color(0xffED7D31),
selectedBorderColor: Color(0xffED7D31),
selectedColor: Color(0xffAD7651),
fillColor: Color(0xffFBE5D6),
color: Color(0xffBF987E),
children: <Widget>[
...state.sortBy.keys.map((name) => Text(name)).toList()
],
onPressed: (index) => state.toggleSortBy(index),
isSelected: state.sortBy.values.toList(),
),
),
],
),
This obviously creates a list of button widgets from a map, the problem I'm facing is that I'm seeing the following warning in my screen
Right overflowed by X pixels
So my question is simply whether there is a way to "flex" the buttons like in CSS, meaning when the number of buttons exceeds the screen size, the buttons would automatically start from the next line.
EDIT:
The state.sortBy is just a Map where I store the texts for my widgets alongside their selected values:
LinkedHashMap<String,bool> sortBy = LinkedHashMap.from({
'Ascending' : true,
'Descending' : false,
})

So this was a bit of work, but I've made a Widget that I've called WrapIconToggleButtons and that should fit what you are looking for. It's rudimentary but you can customize it as you see fit. Please take a look:
How to use (similar to ToggleButtons)
class Main extends StatefulWidget {
#override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
List<bool> isSelected = [
false,
false,
false,
false,
false,
false,
false,
false,
];
#override
Widget build(BuildContext context) {
return Center(
child: Container(
child: WrapToggleIconButtons(
iconList: [
Icons.ac_unit,
Icons.shopping_cart,
Icons.shopping_cart,
Icons.done,
Icons.fiber_pin,
Icons.sentiment_satisfied,
Icons.looks_6,
Icons.apps,
],
isSelected: isSelected,
onPressed: (int index) {
setState(() {
for (int buttonIndex = 0; buttonIndex < isSelected.length; buttonIndex++) {
if (buttonIndex == index) {
isSelected[buttonIndex] = !isSelected[buttonIndex];
} else {
isSelected[buttonIndex] = false;
}
}
});
},
),
),
);
}
}
WrapToggleIconButtons Widget
class WrapToggleIconButtons extends StatefulWidget {
final List<IconData> iconList;
final List<bool> isSelected;
final Function onPressed;
WrapToggleIconButtons({
#required this.iconList,
#required this.isSelected,
#required this.onPressed,
});
#override
_WrapToggleIconButtonsState createState() => _WrapToggleIconButtonsState();
}
class _WrapToggleIconButtonsState extends State<WrapToggleIconButtons> {
int index;
#override
Widget build(BuildContext context) {
assert(widget.iconList.length == widget.isSelected.length);
index = -1;
return Wrap(
children: widget.iconList.map((IconData icon){
index++;
return IconToggleButton(
active: widget.isSelected[index],
icon: icon,
onTap: widget.onPressed,
index: index,
);
}).toList(),
);
}
}
class IconToggleButton extends StatelessWidget {
final bool active;
final IconData icon;
final Function onTap;
final int width;
final int height;
final int index;
IconToggleButton({
#required this.active,
#required this.icon,
#required this.onTap,
#required this.index,
this.width,
this.height,
});
#override
Widget build(BuildContext context) {
return Container(
width: width ?? 60,
height: height ?? 60,
child: InkWell(
child: Icon(icon,
color: active ? Theme.of(context).accentColor : Theme.of(context).disabledColor,
),
onTap: () => onTap(index),
),
);
}
}

Related

Provider does not refresh the list of a DropDownMenu - Flutter

I have a personal API from where I retrieve all the information I need, the connection works fine, but the problem is when I want to show this values, I have a provider where I put all the data I need from the API, and I call it to put those items in the DropDownMenu, but the problem is that the values does not appear, even after the data have arrived, I know this thanks to debugging, here is some code:
this is the code of the widget where I'm calling the provider:
Column(
children: [
Container(
color: Colors.green,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
margin: const EdgeInsets.only(top: 20, bottom: 20),
height: 10,
width: 100,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.grey),
)
]),
),
IconList(
labelText: 'Landmarks',
icon: Icons.panorama_horizontal_outlined,
items: context.watch<Landmarks>().getLandmarksNameList(), //Here is my provider
),
const IconInput(
labelText: '1', icon: Icons.addchart_outlined),
const IconInput(
labelText: '1', icon: Icons.addchart_outlined),
ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: 2,
itemBuilder: (BuildContext context, int index) {
return UserCard(user: usersList[index]);
},
),
],
),
this is the widget iconList, where I have the dropDownMenu:
import 'package:flutter/material.dart';
class IconList extends StatefulWidget {
final String labelText;
final IconData icon;
final List<String> items = [];
IconList({super.key, required this.labelText, required this.icon, items});
#override
State<IconList> createState() => _IconListState();
}
class _IconListState extends State<IconList> {
#override
Widget build(BuildContext context) {
String selectedItem = '';
if (widget.items.isNotEmpty) {
selectedItem = widget.items.first;
}
return Container(
margin: const EdgeInsets.all(10.0),
child: Row(
children: [
Icon(widget.icon),
DropdownButton<String>(
value: selectedItem,
items: widget.items.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? value) {
setState(() {
selectedItem = value!;
});
},
),
],
),
);
}
}
And this is my provider:
class Landmarks with ChangeNotifier {
final List<Landmark> _items = [];
List<Landmark> get landmarks => _items;
void addLandmarks(List<Landmark> value) {
_items.addAll(value);
notifyListeners();
}
List<String> getLandmarksNameList() {
List<String> list = [];
print(_items.length);
for (int i = 0; i < _items.length; i++) {
list.add(_items[i].name);
}
return list;
}
}
You have two problems with you code:
Problem 1
In your IconList constructor
IconList({super.key, required this.labelText, required this.icon, items});
you are accessing items, but really you should access this.items.
To do so, you also shouldn't initialize items with:
final List<String> items = [];
Your code should look like this:
class IconList extends StatefulWidget {
final String labelText;
final IconData icon;
final List<String> items;
IconList(
{super.key,
required this.labelText,
required this.icon,
required this.items});
Problem 2
Within your build method:
#override
Widget build(BuildContext context) {
String selectedItem = '';
if (widget.items.isNotEmpty) {
selectedItem = widget.items.first;
}
you are initializing selectedItem, which means that every setState, selectedItem will get reset to its original value since it's within the build method. Instead, move selectedItem to outside the build, to your _state class:
class _IconListState extends State<IconList> {
String selectedItem = ''; //<-- Initialize it here
#override
Widget build(BuildContext context) {
if (widget.items.isNotEmpty) {
selectedItem = widget.items.first;
}
return Container(
Your IconList should look like this:
class IconList extends StatefulWidget {
final String labelText;
final IconData icon;
final List<String> items;
IconList(
{super.key,
required this.labelText,
required this.icon,
required this.items});
#override
State<IconList> createState() => _IconListState();
}
class _IconListState extends State<IconList> {
String selectedItem = ''; //<-- Initialize it here
#override
Widget build(BuildContext context) {
if (widget.items.isNotEmpty) {
selectedItem = widget.items.first;
}
return Container(
margin: const EdgeInsets.all(10.0),
child: Row(
children: [
Icon(widget.icon),
DropdownButton<String>(
value: selectedItem,
items: widget.items.map<DropdownMenuItem<String>>((String value) {
print("item is $value");
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? value) {
setState(() {
selectedItem = value!;
});
},
),
],
),
);
}
}

Flutter Question: How to select/highlight widget based on widget key or unique key properties upon tap

Im trying to build dynamically generated widgets, each of them need to be selected/highlighted when I tap on them.
here is code borrowed by another solution on stackoverflow, reference code below is DartPad friendly to paste and play.
this is perfect solution for me except, I don't want hard coded integer to identify which widget is tapped on ( I don't have finite number of widget), instead need to check against key or unique key property of myContainer instance
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
debugShowCheckedModeBanner: false,
home: HomeScreen(),
);
}
}
class MyContainer extends StatelessWidget {
final VoidCallback ontap;
bool isSelected;
Key myKey = UniqueKey();
MyContainer({
required this.ontap,
this.isSelected = false,
});
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10),
child: GestureDetector(
onTap: ontap,
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(20),
border: isSelected == true
? Border.all(width: 2, color: Colors.blue)
: null,
),
),
),
);
}
}
class HomeScreen extends StatefulWidget {
HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int? selectedIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: Row(
children: [
Expanded(
child: MyContainer(
isSelected: selectedIndex == 1,
ontap: () {
selectedIndex = 1;
setState(() {});
},
),
),
Expanded(
child: MyContainer(
isSelected: selectedIndex == 2,
ontap: () {
selectedIndex = 2;
setState(() {});
},
),
)
],
)),
Expanded(
child: MyContainer(
isSelected: selectedIndex == 3,
ontap: () {
selectedIndex = 3;
setState(() {});
},
),
),
],
),
);
}
} ```
you can use the spread operator ... to generate widgets dynamically based on your needs elements length, and use the index for each one :
inside the Column children:
Column(
children: <Widget>[
...List.generate(100, (index) =>
Expanded(
child: MyContainer(
isSelected: selectedIndex == index,
ontap: () {
selectedIndex = index;
setState(() {});
},
),
)
],
)),),],),
this will basically generate 100 widgets, each of them having an index from 0-99.
if you have an already List of your data, you can use its length instead of hardcoded 100, so it will be dynamic.

flutter dynamic Checkboxes don't check

Hi im new to Flutter and coding and tried do build my first to do app. I've created a textformfield to add new todos with a button in a container above. By pressing a button, a new todo will appear on the todo Container. I managed to dynamically give a column new todos with a CheckboxListtTitle. However, when adding a new todo, the todo with a checkbox appears but can't be checked. What did I do wrong here?
to_do_section.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/key.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_application_1/presentation/widgets/landing_page.dart';
import 'package:flutter_application_1/responsive_layout.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
var toDoList = <String>[userInput];
class ToDos extends StatefulWidget {
final List<String> list;
const ToDos({
Key? key,
required this.list,
}) : super(key: key);
#override
State<ToDos> createState() => _ToDosState();
}
class _ToDosState extends State<ToDos> {
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
bool checked= false;
bool? value_1;
var isSelected = [false, false];
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Padding(
padding: EdgeInsets.only(
top: SizeConfig.blockSizeHorizontal * 10,
left: SizeConfig.blockSizeVertical * 2.5,
right: SizeConfig.blockSizeVertical * 2.5,
bottom: SizeConfig.screenHeight / 8),
child: SizedBox(
width: SizeConfig.blockSizeHorizontal * 100,
height: SizeConfig.blockSizeVertical * 40,
child: Container(
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: Colors.black45, style: BorderStyle.solid, width: 4)),
child: Padding(
padding: EdgeInsets.all(8.0),
child: ToDoColumn(setState, checked),
),
),
),
);
});
}
Column ToDoColumn(
StateSetter setState, bool checked) {
return Column(children: [
for (final value in widget.list)
CheckboxListTile(
value: checked,
onChanged: (_value_1) {
setState(() {
checked = _value_1!;
});
},
title: Text('${value}'),
activeColor: Colors.green,
checkColor: Colors.black,
)
]);
}
}
landing_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_application_1/presentation/widgets/to_do_section.dart';
final _textController = TextEditingController();
String userInput = "";
class LandingPage extends StatefulWidget {
const LandingPage({Key? key}) : super(key: key);
#override
State<LandingPage> createState() => _LandingPageState();
}
class _LandingPageState extends State<LandingPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Center(child: Text("To-Do-App")),
backgroundColor: Colors.redAccent,
),
body: SingleChildScrollView(
child: Column(
children: [ToDos(list: toDoList), ToDoAdd()],
),
),
);
}
Column ToDoAdd() {
return Column(
children: [
Padding(
padding: EdgeInsets.only(top: 8.0, left: 20, right: 20, bottom: 20),
child: TextField(
onChanged: (value) => setState(() => userInput = value) ,
textAlign: TextAlign.center,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: "Add a new ToDo",
) ,
),
),
MaterialButton(
color: Colors.redAccent,
onPressed: () {
setState(() {
toDoList.add(userInput);
});
},
child: Text("Admit", style: TextStyle(color: Colors.white),),
),
Text(userInput)
],
);
}
}
Every variable declared inside the build method will be redeclared on each build. You could move its declaration outside and assign a value in the initState method.
Also, the use of the StatefulBuilder is not justified in your code. You could simply use the StatefulWidget as it is.
I will take the model class from #yeasin-sheikh
class Task {
final String text;
final bool isChecked;
Task({
required this.text,
this.isChecked = false,
});
Task copyWith({
String? text,
bool? isChecked,
}) {
return Task(
text: text ?? this.text,
isChecked: isChecked ?? this.isChecked,
);
}
}
This example is a reduced one that accomplish what you are trying to do. Please notice that I'm not reproducing your design. The function addRandomToDo() could be replaced with your code for adding Tasks to the app.
class ToDoPage extends StatefulWidget {
const ToDoPage({Key? key}) : super(key: key);
#override
State<ToDoPage> createState() => _ToDoPageState();
}
class _ToDoPageState extends State<ToDoPage> {
final tasks = <Task>[];
void addRandomToDo() {
setState(() {
tasks.add(Task(text: 'Random ToDo ${tasks.length + 1}'));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ToDo App'),
),
body: ListView.builder(
itemCount: tasks.length,
itemBuilder: (context, index) {
return CheckboxListTile(
key: ObjectKey(tasks[index]),
title: Text(tasks[index].text),
value: tasks[index].isChecked,
onChanged: (isChecked) {
setState(() {
tasks[index] = tasks[index].copyWith(isChecked: isChecked);
});
},
);
}),
floatingActionButton: FloatingActionButton(
onPressed: addRandomToDo,
tooltip: 'Add ToDo',
child: const Icon(Icons.add),
),
);
}
}
Tips:
The use of Column for this example is not recommended. The best is to use a ListView.builder() and the reason is that when you use a Column and you have more element than the screen can render the app will also render the ones that are offscreen and will result in a lack of performance but the use of ListView will be best because it only renders a few elements offscreen (maybe two or three) to avoid a memory overload and it will load and render the elements as you scroll.
Is a best practice to not use functions to return a piece of the screen but to split it into widgets. (Instead of having a ToDoColumn function just change it for a ToDoColumnWidget). The explanation for this is that will incurre in a lack of performance because all functions will be re-rendered on every widget build.
This piece of code :
EdgeInsets.only(top: 8.0, left: 20, right: 20, bottom: 20)
Could be changed for this one:
EdgeInsets.fromLTRB(20, 8, 20, 20)
You have defined isSelected inside the build method. The build method is invoked each time you set the state. Please move it to initState.
bool checked= false;
Define bool checked = false outside the build method or in initstate
Putting variables inside build method will reset the bool and always be false. Also on your snippet you are just holding text without check state. On evey-build it will also reset on this point.
You can remove StatefulBuilder while this is already a StatefulWidget. Try using state-management like riverpod or bloc instead of using global variables in project cases.
As for the solution, I am creating a model class.
class Task {
final String text;
final bool isChecked;
Task({
required this.text,
this.isChecked = false,
});
Task copyWith({
String? text,
bool? isChecked,
}) {
return Task(
text: text ?? this.text,
isChecked: isChecked ?? this.isChecked,
);
}
}
Now the list will be var toDoList = [Task(text: "")];
And your ToDos
class ToDos extends StatefulWidget {
final List<Task> list;
const ToDos({
Key? key,
required this.list,
}) : super(key: key);
#override
State<ToDos> createState() => _ToDosState();
}
class _ToDosState extends State<ToDos> {
#override
Widget build(BuildContext context) {
//todo: remove [StatefulBuilder]
return StatefulBuilder(builder: (BuildContext context, setStateSB) {
return Container(
child: Padding(
padding: EdgeInsets.all(8.0),
child: ToDoColumn(setStateSB),
),
);
});
}
//current
Column ToDoColumn(
StateSetter setStateSB,
) {
return Column(children: [
for (int i = 0; i < widget.list.length; i++)
CheckboxListTile(
value: widget.list[i].isChecked,
onChanged: (_value_1) {
widget.list[i] = widget.list[i].copyWith(isChecked: _value_1);
setState(() {});
setStateSB(() {});
},
title: Text('${widget.list[i].isChecked}'),
activeColor: Colors.green,
checkColor: Colors.black,
)
]);
}
}
More about StatefulWidget and cookbook on flutter

How to make ToggleButtons with text under icon

I'm having a bit of a hard time with this idea.
The goal is to have a row of Toggle Icons with text that can overflow onto a second line.
The issue I'm having with the ToggleButtons is that I can't seem to place text underneath each icon.
I currently have a Map<String, Icon> where the string is the text I want below the Icon from that Map.
Is there an easy/possible way to do this?
Yea, you can achieve this by using the Column widget.
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.access_alarm),
SizedBox(height: 5.0,),
Text("Text"),
],
);
Please see the following code to put text under icon in a ToggleButton.
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(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Map<String, dynamic> map = {
"one": Icons.ac_unit,
"two": Icons.baby_changing_station,
"three": Icons.cached,
"four": Icons.dangerous,
"five": Icons.east,
"six": Icons.face,
};
List<bool> _isSelected = [];
#override
void initState() {
super.initState();
_isSelected = List.filled(map.length, false);
}
#override
Widget build(BuildContext context) {
return Wrap(
children: [
ToggleButtons(
isSelected: _isSelected,
children: [
...map.entries.map((ele) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(ele.value),
Text(ele.key),
],
);
}).toList(),
],
selectedColor: Colors.blueGrey,
onPressed: (value) {
setState(() {
_isSelected = List.filled(map.length, false);
_isSelected[value] = true;
});
},
),
],
);
}
}
I edited up modifying another answers code from another question to use my map
import 'package:flutter/material.dart';
class WrapToggleIconButtons extends StatefulWidget {
const WrapToggleIconButtons({
#required this.symptomIconDataMap,
#required this.isSelected,
#required this.onPressed,
});
final Map<String, IconData> symptomIconDataMap;
final List<bool> isSelected;
final Function onPressed;
#override
_WrapToggleIconButtonsState createState() => _WrapToggleIconButtonsState();
}
class _WrapToggleIconButtonsState extends State<WrapToggleIconButtons> {
int index;
#override
Widget build(BuildContext context) {
final List<String> symptomsList = widget.symptomIconDataMap.keys.toList();
assert(symptomsList.length == widget.isSelected.length);
index = -1;
return Wrap(
children: symptomsList.map((String symptom) {
index++;
return IconToggleButton(
active: widget.isSelected[index],
iconData: widget.symptomIconDataMap[symptom],
text: symptom,
onTap: widget.onPressed,
index: index,
);
}).toList(),
);
}
}
class IconToggleButton extends StatelessWidget {
const IconToggleButton({
#required this.active,
#required this.iconData,
#required this.text,
#required this.onTap,
#required this.index,
this.width,
this.height,
});
final bool active;
final IconData iconData;
final String text;
final Function onTap;
final double width;
final double height;
final int index;
#override
Widget build(BuildContext context) {
return Container(
width: 80.0,
height: height ?? 60.0,
child: Column(
children: [
InkWell(
child: Icon(
iconData,
color: active ? Theme.of(context).accentColor : Theme.of(context).disabledColor,
),
onTap: () => onTap(index),
),
Wrap(
direction: Axis.horizontal,
children: [
Text(
text,
textAlign: TextAlign.center,
),
],
)
],
),
);
}
}
Flutter: Is there a widget to flex toggle buttons
You can also create a custom widget and use it when you need it.
///CustomTextIcon.dart
import 'package:flutter/material.dart';
class MyIconWithText extends StatelessWidget {
final IconData icon;
final String? text;
const MyIconWithText(this.icon, {Key? key,
this.text
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(icon),
const SizedBox(height: 5.0,),
Text(text ?? ""),
],
);
}
}
and use it as follow:
///Used as a widget
MyIconWithText(Icons.disabled_by_default, text: "Description")

Flutter : Custom Radio Button

How can I create a custom radio button group like this in flutter
Here is the full code
class CustomRadio extends StatefulWidget {
#override
createState() {
return new CustomRadioState();
}
}
class CustomRadioState extends State<CustomRadio> {
List<RadioModel> sampleData = new List<RadioModel>();
#override
void initState() {
// TODO: implement initState
super.initState();
sampleData.add(new RadioModel(false, 'A', 'April 18'));
sampleData.add(new RadioModel(false, 'B', 'April 17'));
sampleData.add(new RadioModel(false, 'C', 'April 16'));
sampleData.add(new RadioModel(false, 'D', 'April 15'));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListItem"),
),
body: new ListView.builder(
itemCount: sampleData.length,
itemBuilder: (BuildContext context, int index) {
return new InkWell(
//highlightColor: Colors.red,
splashColor: Colors.blueAccent,
onTap: () {
setState(() {
sampleData.forEach((element) => element.isSelected = false);
sampleData[index].isSelected = true;
});
},
child: new RadioItem(sampleData[index]),
);
},
),
);
}
}
class RadioItem extends StatelessWidget {
final RadioModel _item;
RadioItem(this._item);
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.all(15.0),
child: new Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Container(
height: 50.0,
width: 50.0,
child: new Center(
child: new Text(_item.buttonText,
style: new TextStyle(
color:
_item.isSelected ? Colors.white : Colors.black,
//fontWeight: FontWeight.bold,
fontSize: 18.0)),
),
decoration: new BoxDecoration(
color: _item.isSelected
? Colors.blueAccent
: Colors.transparent,
border: new Border.all(
width: 1.0,
color: _item.isSelected
? Colors.blueAccent
: Colors.grey),
borderRadius: const BorderRadius.all(const Radius.circular(2.0)),
),
),
new Container(
margin: new EdgeInsets.only(left: 10.0),
child: new Text(_item.text),
)
],
),
);
}
}
class RadioModel {
bool isSelected;
final String buttonText;
final String text;
RadioModel(this.isSelected, this.buttonText, this.text);
}
To use :
void main() {
runApp(new MaterialApp(
home: new CustomRadio(),
));
}
Screenshot :
Screenshot (Null safe)
Full code:
Create this custom class.
class MyRadioListTile<T> extends StatelessWidget {
final T value;
final T groupValue;
final String leading;
final Widget? title;
final ValueChanged<T?> onChanged;
const MyRadioListTile({
required this.value,
required this.groupValue,
required this.onChanged,
required this.leading,
this.title,
});
#override
Widget build(BuildContext context) {
final title = this.title;
return InkWell(
onTap: () => onChanged(value),
child: Container(
height: 56,
padding: EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
_customRadioButton,
SizedBox(width: 12),
if (title != null) title,
],
),
),
);
}
Widget get _customRadioButton {
final isSelected = value == groupValue;
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: isSelected ? Colors.blue : null,
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: isSelected ? Colors.blue : Colors.grey[300]!,
width: 2,
),
),
child: Text(
leading,
style: TextStyle(
color: isSelected ? Colors.white : Colors.grey[600]!,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
);
}
}
Use it in your widget like a regular RadioListTile.
class _MyPageState extends State<MyPage> {
int _value = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
MyRadioListTile<int>(
value: 1,
groupValue: _value,
leading: 'A',
title: Text('One'),
onChanged: (value) => setState(() => _value = value!),
),
MyRadioListTile<int>(
value: 2,
groupValue: _value,
leading: 'B',
title: Text('Two'),
onChanged: (value) => setState(() => _value = value!),
),
MyRadioListTile<int>(
value: 3,
groupValue: _value,
leading: 'C',
title: Text('Three'),
onChanged: (value) => setState(() => _value = value!),
),
],
),
);
}
}
I achieved that with the following logic.
reply if you need a detailed explanation
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
Parent({
Key key,
}) : super(key: key);
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
int _selectedItem = 0;
selectItem(index) {
setState(() {
_selectedItem = index;
print(selectItem.toString());
});
}
#override
Widget build(BuildContext context) {
//...YOUR WIDGET TREE HERE
return ListView.builder(
shrinkWrap: true,
itemCount: 5,
itemBuilder: (context, index) {
return CustomItem(
selectItem, // callback function, setstate for parent
index: index,
isSelected: _selectedItem == index,
title: index.toString(),
);
},
);
}
}
class CustomItem extends StatefulWidget {
final String title;
final int index;
final bool isSelected;
Function(int) selectItem;
CustomItem(
this.selectItem, {
Key key,
this.title,
this.index,
this.isSelected,
}) : super(key: key);
_CustomItemState createState() => _CustomItemState();
}
class _CustomItemState extends State<CustomItem> {
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Text("${widget.isSelected ? "true" : "false"}"),
RaisedButton(
onPressed: () {
widget.selectItem(widget.index);
},
child: Text("${widget.title}"),
)
],
);
}
}
You can create it with ListView and List Item with one local variable to store the selected item. And you can render the selected the ListItem based on the variable.
P.S. Let me know if you need code snippet.
[EDIT]
As you have requested, Here is code snipper which will show you how you can maintain the state of each ListView item.
Now you can play with it and make it the way you want. If you want only one selected item you can write the logic that way.
void main() {
runApp(new MaterialApp(
home: new ListItemDemo(),
));
}
class ListItemDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListItem"),
),
body: new ListView.builder(
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return new MyListItem(
title: "Hello ${index + 1}",
);
}),
);
}
}
class MyListItem extends StatefulWidget {
final String title;
MyListItem({this.title});
#override
_MyListItemState createState() => new _MyListItemState();
}
class _MyListItemState extends State<MyListItem> {
bool isSelected;
#override
void initState() {
super.initState();
isSelected = false;
}
#override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new Text("${widget.title} ${isSelected ? "true" : "false"}"),
new RaisedButton(
onPressed: () {
if (isSelected) {
setState(() {
isSelected = false;
});
} else {
setState(() {
isSelected = true;
});
}
},
child: new Text("Select"),
)
],
);
}
}
https://i.stack.imgur.com/Hq0O2.png
Here is Custom Radio Group Button Widget. You can easily customize all property as per your requirement. Use:
GroupRadioButton(
label: [Text("A"), Text("B"), Text("C"), Text("D")],
padding: EdgeInsets.symmetric(vertical: 10),
spaceBetween: 5,
radioRadius: 10,
color: Const.mainColor,
onChanged: (listIndex) {
print(listIndex);
},
),
This is GroupRadioButton widget
import 'package:flutter/material.dart';
class GroupRadioButton extends StatefulWidget {
GroupRadioButton({
#required this.label,
#required this.padding,
#required this.onChanged,
this.color = Colors.blue,
this.radioRadius = 14.0,
this.spaceBetween = 5.0,
this.mainAxisAlignment = MainAxisAlignment.start,
this.crossAxisAlignment = CrossAxisAlignment.start,
});
final Color color;
final List<Widget> label;
final EdgeInsets padding;
final Function(int) onChanged;
final double radioRadius;
final double spaceBetween;
final MainAxisAlignment mainAxisAlignment;
final CrossAxisAlignment crossAxisAlignment;
#override
_GroupRadioButtonState createState() => _GroupRadioButtonState();
}
class _GroupRadioButtonState extends State<GroupRadioButton> {
int selectedIndex = 0;
#override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
itemCount: widget.label != null ? widget.label.length : 0,
itemBuilder: (context, index) {
return LabeledRadio(
selectedIndex: selectedIndex,
color: widget.color,
onChanged: (value) {
setState(() {
selectedIndex = value;
widget.onChanged(value);
// print(value);
});
},
index: index,
label: widget.label[index],
crossAxisAlignment: widget.crossAxisAlignment,
mainAxisAlignment: widget.mainAxisAlignment,
radioRadius: widget.radioRadius,
spaceBetween: widget.spaceBetween,
padding: widget.padding,
);
});
}
}
class LabeledRadio extends StatelessWidget {
LabeledRadio({
#required this.label,
#required this.index,
#required this.color,
//#required this.groupValue,
//#required this.value,
#required this.onChanged,
#required this.radioRadius,
#required this.padding,
#required this.spaceBetween,
#required this.mainAxisAlignment,
#required this.crossAxisAlignment,
this.selectedIndex,
});
final Color color;
final int selectedIndex;
final Widget label;
final index;
final EdgeInsets padding;
//final bool groupValue;
//final bool value;
final Function(int) onChanged;
final double radioRadius;
final double spaceBetween;
final MainAxisAlignment mainAxisAlignment;
final CrossAxisAlignment crossAxisAlignment;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
onChanged(index);
},
child: Padding(
padding: padding,
child: Row(
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment,
children: <Widget>[
Container(
decoration: BoxDecoration(
//color: Const.mainColor,
shape: BoxShape.circle,
border: Border.all(color: color, width: 2),
),
padding: EdgeInsets.all(2),
child: selectedIndex == index
? Container(
height: radioRadius,
width: radioRadius,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
)
: Container(
height: radioRadius,
width: radioRadius,
),
),
SizedBox(
width: spaceBetween,
),
label,
],
),
),
);
}
}
My RadioButton is like the 'Radio' widget:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class RadioButton<T> extends StatefulWidget {
RadioButton({
Key key,
#required this.value,
#required this.caption,
#required this.groupValue,
#required this.onChanged,
}) : assert(value != null),
assert(caption != null),
assert(groupValue != null),
assert(onChanged != null),
super(key: key);
final T value;
final T groupValue;
final String caption;
final Function onChanged;
#override
State<StatefulWidget> createState() => _RadioButtonState();
}
class _RadioButtonState extends State<RadioButton> {
#override
Widget build(BuildContext context) {
final bool selected = widget.value == widget.groupValue;
return GestureDetector(
onTap: () {
widget.onChanged(widget.value);
},
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: selected ? Colors.red : Colors.white),
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
widget.caption,
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.button
.copyWith(color: selected ? Colors.white : Colors.red),
),
),
),
);
}
}
import 'package:flutter/material.dart';
class CustomRadio extends StatefulWidget {
#override
createState() {
return new CustomRadioState();
}
}
class CustomRadioState extends State<CustomRadio> {
List<RadioModel> sampleData = new List<RadioModel>();
#override
void initState() {
// TODO: implement initState
super.initState();
sampleData.add(new RadioModel(true, 'A',0xffe6194B));
sampleData.add(new RadioModel(false, 'B',0xfff58231));
sampleData.add(new RadioModel(false, 'C',0xffffe119));
sampleData.add(new RadioModel(false, 'D',0xffbfef45));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListItem"),
),
body: new ListView.builder(
itemCount: sampleData.length,
itemBuilder: (BuildContext context, int index) {
return new InkWell(
splashColor: Colors.blueAccent,
onTap: () {
setState(() {
sampleData.forEach((element) => element.isSelected = false);
sampleData[index].isSelected = true;
});
},
child: new RadioItem(sampleData[index]),
);
},
),
);
}
}
class RadioItem extends StatelessWidget {
final RadioModel _item;
RadioItem(this._item);
#override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.all(15.0),
child: new Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Container(
height: 25.0,
width: 25.0,
alignment: Alignment.center,
child:Container(
height: 15.0,
width: 15.0,
decoration: new BoxDecoration(
color:Color(_item.colorCode),
borderRadius: const BorderRadius.all(const Radius.circular(15)),
)
),
decoration: new BoxDecoration(
color: Colors.transparent,
border: new Border.all(
width: 3.0,
color: _item.isSelected
? Color(_item.colorCode)
: Colors.transparent),
borderRadius: const BorderRadius.all(const Radius.circular(25)),
),
),
new Container(
margin: new EdgeInsets.only(left: 10.0)
)
],
),
);
}
}
class RadioModel {
bool isSelected;
final String buttonText;
final int colorCode;
RadioModel(this.isSelected, this.buttonText,this.colorCode);
}
void main() {
runApp(new MaterialApp(
home: new CustomRadio(),
));
}
Click here to check out put-> Here