'Flutter - How to change button color on the click and disable other buttons?
I have a listview with several green buttons, and I need to change the color of a button to red on click. The problem is that in doing that all the other buttons need to go back to their base color green.
On this example below (working version at https://www.dartpad.dev/?id=b4ea6414b6a4ffcc7135579e673be845) All buttons change the color on click independently of the other buttons, but the desired effect is that all the other buttons should be green when the clicked button is red.
import 'package:flutter/material.dart';
const Color darkBlue = 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: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MyWidget(
text: 'Button 1',
onPressed: () => print('Click'),
),
MyWidget(
text: 'Button 2',
onPressed: () => print('Click'),
),
MyWidget(
text: 'Button 3',
onPressed: () => print('Click'),
),
MyWidget(
text: 'Button 4',
onPressed: () => print('Click'),
),
],
)),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({
Key? key,
required this.text,
required this.onPressed,
}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
final String text;
final VoidCallback onPressed;
}
class _MyWidgetState extends State<MyWidget> {
bool isFavourte = false;
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
primary: isFavourte ? Colors.red : Colors.green,
),
onPressed: () {
setState(() => isFavourte = !isFavourte);
widget.onPressed();
},
child: Text(widget.text));
}
}
How this can be done?
Solution 1:[1]
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
final selectedIndexNotifier = ValueNotifier<int?>(null);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ValueListenableBuilder<int?>(
valueListenable: selectedIndexNotifier,
builder: (_, selectedIndex, __) => Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
for (int i = 1; i <= 4; i++)
MyWidget(
key: ValueKey(i),
text: 'Button $i',
isFavorite: selectedIndex == i,
onPressed: () => selectedIndex == i ? selectedIndexNotifier.value = null : selectedIndexNotifier.value = i
)
],
))),
),
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget({
Key? key,
required this.text,
required this.isFavorite,
required this.onPressed,
}) : super(key: key);
@override
Widget build(BuildContext context) => ElevatedButton(
style: ElevatedButton.styleFrom(
primary: isFavorite ? Colors.red : Colors.green,
),
onPressed: onPressed,
child: Text(text));
final String text;
final bool isFavorite;
final VoidCallback onPressed;
}
Solution 2:[2]
Here's an example doing exactly what you want to achieve, by saving the state of each button on a List and updating them all as one changes:
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<Map> buttonList = [
{
'label': 'button1',
'active': true,
},
{
'label': 'button2',
'active': true,
},
{
'label': 'button3',
'active': true,
},
{
'label': 'button4',
'active': true,
},
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: buttonList.length,
itemBuilder: (context, index){
return ElevatedButton(
onPressed: () => onPressed(buttonList[index]),
style: ButtonStyle(
backgroundColor: buttonList[index]['active']
? MaterialStateProperty.all(Colors.green)
: MaterialStateProperty.all(Colors.red),
),
child: Text(buttonList[index]['label']),
);
},
),
);
}
void onPressed(Map button){
setState(() {
for (var element in buttonList) {
element['active'] = false;
}
button['active'] = true;
});
}
}
Solution 3:[3]
created selectedValue variable in myWidget2 and id for evrey button so when ever you press a button it going to set selectedValue = id so that only the button whit the id = selectedValue going to turn red
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
int selectedValue = 0 ;
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: MyWidget2() ,
);
}
}
class ValueChanged extends Notification {
final int selectedValue ;
ValueChanged(this.selectedValue);
}
class MyWidget2 extends StatefulWidget {
const MyWidget2({
Key? key,
}) : super(key: key);
@override
State<MyWidget2> createState() => _MyWidget2State();
}
class _MyWidget2State extends State<MyWidget2> {
int selectedValue = 0 ;
@override
Widget build(BuildContext context) {
return Scaffold(
body: NotificationListener<ValueChanged>(
onNotification: (n) {
setState(() {
selectedValue = n.selectedValue ;
// Trigger action on parent via setState or do whatever you like.
});
return true;
},
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MyWidget(
text: 'Button 1',
onPressed: () => print('Click'),
id: 1,
selectedValue :selectedValue ,
),
MyWidget(
text: 'Button 2',
onPressed: () => print('Click'),
id: 2,
selectedValue :selectedValue ,
),
MyWidget(
text: 'Button 3',
onPressed: () => print('Click'),
id:3,
selectedValue :selectedValue ,
),
MyWidget(
text: 'Button 4',
onPressed: () => print('Click'),
id:4,
selectedValue :selectedValue ,
),
],
)),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({
Key? key,
required this.text,
required this.onPressed,
required this.id,
required this.selectedValue,
}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
final int id;
final String text;
final VoidCallback onPressed;
final int selectedValue ;
}
class _MyWidgetState extends State<MyWidget> {
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
primary: widget.id == widget.selectedValue ? Colors.red : Colors.green,
),
onPressed: () {
setState(() => ValueChanged(widget.id).dispatch(context));
widget.onPressed();
},
child: Text(widget.text));
}
}
Solution 4:[4]
First I've made MyWidget Stateless and Create Two new things:
- ButtonData Class: Separate The Actual data that needs to be controlled and makes it scalable.
- MyButtonList: StatefulWidget that contains a List of boolean values to track the current active Button
here's an example:

Create new file and copy the following code and see the result:
class TestPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: Colors.blue[800],
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyButtonList(
buttons: [
ButtonData(text: 'Test'),
ButtonData(text: 'Test'),
ButtonData(text: 'Test'),
ButtonData(text: 'Test'),
ButtonData(text: 'Test'),
],
),
),
),
);
}
}
class MyButtonList extends StatefulWidget {
const MyButtonList({Key? key, required this.buttons}) : super(key: key);
final List<ButtonData> buttons;
@override
State<MyButtonList> createState() => _MyButtonListState();
}
class _MyButtonListState extends State<MyButtonList> {
late List<bool> favoriateState;
@override
void initState() {
favoriateState = List.generate(
widget.buttons.length, (index) => widget.buttons[index].isFavorite);
super.initState();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
for (var i = 0; i < widget.buttons.length; i++)
MyWidget(
text: widget.buttons[i].text,
onPressed: () {
for (var j = 0; j < favoriateState.length; j++) {
favoriateState[j] = false;
}
setState(() {
favoriateState[i] = true;
if (widget.buttons[i].onPressed != null) {
widget.buttons[i].onPressed!();
}
});
},
isFavourte: favoriateState[i],
),
],
);
}
}
class ButtonData {
final String text;
final Function()? onPressed;
final bool isFavorite;
ButtonData({required this.text, this.onPressed, this.isFavorite = false});
}
class MyWidget extends StatelessWidget {
const MyWidget(
{Key? key,
required this.text,
required this.onPressed,
this.isFavourte = false})
: super(key: key);
final String text;
final Function()? onPressed;
final bool isFavourte;
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
primary: isFavourte ? Colors.red : Colors.green,
),
onPressed: onPressed,
child: Text(text));
}
}
Solution 5:[5]
You seem to want to emulate RadioButtons by using TextButtons.
Withing a group of RadioListTile-s only one can be active. And this is what you want to achieve, if I understood you correctly.
May I suggest to use RadioListTile-s instead and then style (or theme) these as you like: Green for inactive Tiles, Red for active Tiles.
The following just demonstrates the usage of RadioListTile, further info on styling active-/nonactive-Tiles can be found easily.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// main application widget
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Application';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
/// stateful widget that the main application instantiates
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
@override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
enum Fruit { apple, banana }
/// private State class that goes with MyStatefulWidget
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
Fruit? _fruit = Fruit.apple;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: <Widget>[
RadioListTile<Fruit>(
title: const Text('Apple'),
value: Fruit.apple,
groupValue: _fruit,
onChanged: (Fruit? value) {
setState(() {
_fruit = value;
});
},
),
RadioListTile<Fruit>(
title: const Text('Banana'),
value: Fruit.banana,
groupValue: _fruit,
onChanged: (Fruit? value) {
setState(() {
_fruit = value;
});
},
),
],
),
);
}
}
Source
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | João Soares |
| Solution 3 | |
| Solution 4 | |
| Solution 5 |
