flutter CheckboxListTile loses checked state when scroll - flutter

I am trying to use CheckboxListTile in a popup widget so the user can check as many items as needed and then click OK to accept the checked items which adds them to a list of items in main widget
What happens is CheckboxListTile loses checked state when scrolling down for some items, when getting back up in the list it is not checked anymore,
here is my code for the CheckboxListTile
class ItemTileWithImage extends StatefulWidget {
final Model model;
ItemTileWithImage(this.model);
#override
_ItemTileWithImageState createState() => _ItemTileWithImageState();
}
class _ItemTileWithImageState extends State<ItemTileWithImage> {
bool _checked = false;
#override
Widget build(BuildContext context) {
Color hintColor = Theme.of(context).hintColor;
Color primaryColor = Theme.of(context).primaryColor;
return Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
height: 85,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: hintColor, width: 0.5),
),
),
child: CheckboxListTile(
activeColor: primaryColor,
checkColor: Colors.white,
value: _checked,
onChanged: (val) {
setState(() {
_checked = val;
});
if (val) {
Provider.of<OrderProvider>(context, listen: false)
.insertModelItems(widget.model.code);
} else {
Provider.of<OrderProvider>(context, listen: false)
.removeModelItems(widget.model.remoteId);
}
},
secondary: widget.model.imageUrl != null
? CachedNetworkImage(
height: 80,
width: 60,
imageUrl: widget.model.imageUrl,
progressIndicatorBuilder: (context, url, downloadProgress) {
return Center(
child: Container(
width: 30,
height: 30,
child: CircularProgressIndicator(
value: downloadProgress.progress,
backgroundColor: primaryColor,
strokeWidth: 2,
),
),
);
},
errorWidget: (context, url, error) => Icon(Icons.error),
)
: Image.asset(
'assets/images/mini_logo.png',
width: 50,
height: 50,
),
title: Text(widget.model.code ?? '${widget.model.barCode}'),
subtitle: Text(
widget.model.name ?? '',
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
);
}
}

Flutter apps typically 'clean up' your off screen state. Some off-screen widgets will experience the loss of certain view related attributes. This is discussed some in the Keys section here. You can instantiate stateless widgets in a way where their variable attributes are stored off for later recall.
Here is a demo utilizing rows of CheckboxListTile widgets which keep their state even with scrolling/going off-screen. You can use a DartPad of the below code here.
// Remember CheckboxListTile status with scrolling demo.
// Currently utilizes rows in implementation.
// This can be cut down to maintain a single checkbox list tile.
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(
home: Home(),
));
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
// Refresh Callback for descendant widgets (if checkboxes are generated in seperate widget)
refresh() {setState(() {});}
// Rows with checkbox title and check value
// Must update second value in entry to maintain check status while scrolling
List<List> rowData = [
['row1', true], ['row2', false], ['row3', false],
['row4', false], ['row5', false], ['row6', false],
['row7', false], ['row8', false], ['row9', false],
['row10', false], ['row11', false], ['row12', false],
['row13', false], ['row14', false], ['row15', false],
['row16', false], ['row17', false], ['row18', false],
['row19', false], ['row20', false], ['row21', false],
['row22', false], ['row23', false], ['row24', false]];
List<Row> rows = []; // Holds row objects generated from rowData (with checkboxes)
int row = 0;
int rowCount = 0;
#override
Widget build(BuildContext context) {
rows = []; // Refresh rows
for (int i=0; i<rowData.length; i++) { // Init/refresh row widgets
rows.add(Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded (
child: CheckboxListTile(
value: rowData[i][1] as bool, // Check value
title: SizedBox(
width: 100,
child: Text(rowData[i][0], style: const TextStyle(fontSize: 50))
), // Title for check box
checkColor: Colors.black,
dense: false,
onChanged: (newValue) { // On click of check box
setState(() {
print("Checkbox clicked");
rowData[i][1] = newValue; // Update value in the RowData list
});
}
),
)
]
));
}
return Scaffold(
appBar: AppBar(
title: const Text('Remember Checkboxes Demo'),
centerTitle: true,
backgroundColor: const Color(0xff66b366),
),
body: Column (
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: ListView(
children: <Widget>[for (var i in rows) i,] // Fill in rows
),
)
]
)
);
}
}
Abstracting the idea for the question's implementation, you can replace your _checked variable to be a passed in boolean from the parent of the ItemTileWithImage widget. Also, you would want to pass in a function, like the refresh() function in the demo, to call in the onChange block after updating your shared check value. This allows you to refresh the state of your parent widget to include the updated check status.
Note: I am a relative novice with Flutter so apologies if this solution is considered bad practice.
The above implementation was developed with Flutter 2.5.3 and Dart 2.14.4.

Related

Flutter slider value is not update inside getx dialog

I am trying to build a slider widget inside a dialog. I am using GetX, and a GetxController but the value of the slider or any other widget is not updating when I am trying to change it.
It only updates after I reopen the dialog. This is my code for the controller:
class SliderController extends GetxController {
static SliderController get to => Get.find();
var quality = 0.0.obs;
void setQuality(double quality) {
quality.value = quality;
update();
}
}
and my widget looks like this:
class SliderWidget extends StatelessWidget {
const SliderWidget ({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Opacity(
opacity: 0.8,
child: GetBuilder<SliderController>(
init: SliderController(),
builder: (ctrl) => Container(
decoration: BoxDecoration(
color: Colors.white70,
borderRadius: const BorderRadius.all(Radius.circular(16)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.25),
spreadRadius: 2,
blurRadius: 2,
),
],
),
child: Stack(
children: <Widget>[
Positioned(
right: 0,
top: 0,
child: CustomCheckBox(
value: false,
shouldShowBorder: true,
borderColor: Colors.blueGrey,
checkedFillColor: Colors.blueGrey,
borderRadius: 8,
borderWidth: 2,
checkBoxSize: 22,
onChanged: (checked) {
if (checked) {
Get.defaultDialog(
textConfirm: "Save",
textCancel: "Cancel",
content: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text("Quality (${ctrl.quality})"),
Slider(
value: ctrl.quality.value,
min: 0,
max: 100,
divisions: 100,
onChanged: (double value) {
ctrl.setQuality(value);
},
)
],
),
);
}
},
),
),
]
)
)
)
);
}
}
So, the dialog opens, I try to drag the slider around but it doesn't change. Seems like the value is saved in the controller because if I close the dialog and open it again, the slider has the new values.
I don't know how to make the controller update the value inside the dialog's slider
I am seeing you are using the wrong combination. In this case you are using observables (.obs) in the controller, but using GetBuilder in the widget.
Keep in mind that GetX or Obx is used to observe the observables (Reactive State Management). GetBuilder is used for simple state management (Non-reactive/Non-observables).
So you can either change your variable type as normal dart types instead of Rx (.obs) in your controller like:
class SliderController extends GetxController {
static SliderController get to => Get.find();
double quality = 0.0;
void setQuality(double quality) {
this.quality = quality;
update();
}
}
Or you can use GetX or Obx in you widget. In that case no need to call update() on the controller.

How to create a/ instantiate a constructor from one file to the main dart file in flutter?

I am working on creating a hashMap for my flutter program and would like some input on it. As I created my hashMap in another dart file that is not the main dart file and I have no idea on how to connect it even when I created constrictors for the hashMaps. This is very important as the hashMap will be used on several files within the program hence why it is not in the main dart. Therefore I would like your guys input on how I could connect the two files.
This is part of my code:
main dart file:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
//Always use Stateless first then use stateful or stateless widgets afterward
#override
Widget build(BuildContext context) {
return MaterialApp(
//only used at the beginning of the program
title: 'The Cafe',
//just a title to the app it does not show for there is nothing telling it to show on the screen
debugShowCheckedModeBanner: false,
//takes out the ribbon at the top right corner of the screen and app
theme: ThemeData(
primarySwatch: Colors.green,
brightness: Brightness.dark,
fontFamily: 'georgia',
textTheme: TextTheme(headline1: TextStyle(fontSize: 100))
//controls the color of the very top part of the application
),
home: StartPage(),
//used to connect the Stateless widget to the Stateful widget below
);
}
}
class StartPage extends StatefulWidget {
#override
_StartPageState createState() => _StartPageState();
}
// do not forget the } prior to this comment if you do it will result in error and the program does not known why either
class _StartPageState extends State<StartPage> {
String value = "";
//stating the string is not seen until you have started to compute the drop-downs
//have the drop down's take you to the item page
//void main(){
//HashMap map = new HashMap<String, double>();
// LinkedHashMap linkedHashMap = new LinkedHashMap<int, String>();
// SplayTreeMap treeMap = new SplayTreeMap<int, String>();
//}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('The Campus Cafe'),
//where the main title is computed to be shown on the screen
centerTitle: true,
//centers the title
),
body: Center(
//This is Header that is after the main Title
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
//Header Container
Expanded(
child: Image.asset('assets/images/campus-cafe-logo-350sidebar.png',)
),
Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: Text("Our Menu",style: TextStyle(fontSize: 30),
),
),
Expanded(
//Expands is used to create a body if you want a header and body...can also be used for other things but at the moment this is all I know
child: Column(
//there can be different types of Columns
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
//Padding is how far away one container or item is away from another as shown below
flex:3,
child: DropdownButton<String>(
//items are basically like an array or list
items: [
DropdownMenuItem<String>(
value: "1",
child: Center(
child: Text('Grilled Cheese'),
),
),
DropdownMenuItem<String>(
value: "2",
child: Center(
child: Text('Grilled Ham & Cheese'),
),
),
DropdownMenuItem<String>(
value: "3",
child: Center(
child: Text('BLT'),
),
),
DropdownMenuItem<String>(
value: "4",
child: Center(
child: Text('Western Chicken Sandwich'),
),
),
DropdownMenuItem<String>(
value: "5",
child: Center(
child: Text('Crispy Chicken Wrap'),
),
),
DropdownMenuItem<String>(
value: "6",
child: Center(
child: Text('Cheese Steak'),
),
),
],
onChanged: (_value) => {
print(_value.toString()),
setState(() {
value = _value;
}),
},
hint: Text('Sandwiches')
//This hint displays on your drop-box before you open it to see the items list
),
),
Flexible(
flex:3,
child: DropdownButton<String>(
items: [
DropdownMenuItem<String>(
value: "1",
child: Center(
child: Text('Quantum Burger'),
),
),
DropdownMenuItem<String>(
value: "2",
child: Center(
child: Text('Cheeseburger'),
),
),
DropdownMenuItem<String>(
value: "3",
child: Center(
child: Text('Double Cheeseburger 1/4'),
),
),
DropdownMenuItem<String>(
value: "4",
child: Center(
child: Text('Hamburger 1/4'),
),
),
DropdownMenuItem<String>(
value: "5",
child: Center(
child: Text('Cheeseburger'),
),
),
DropdownMenuItem<String>(
value: "6",
child: Center(
child: Text('Veggie Burger'),
),
),
],
onChanged: (_value) => {
print(_value.toString()),
setState(() {
value = _value;
}),
},
hint: Text('Burgers')),
),
This is my hashMap file:
List<String> sandwich = ["Veggie Melt", "Crispy Chicken Wrap", "Italian Meatball Sub",
"Chicken Parm Grinder", "Grill Cheese", "Grilled Ham & Cheese", "Bacon Bagel Melt"];
List<double> sandwichPrice = [4.50, 6.95, 6.99, 6.59, 3.59, 4.59, 5.29];
Map<String, double> map1 = Map.fromIterables(sandwich, sandwichPrice);
List<String> burgers = ["Veggie Burger", "The Quantum Burger", "Cafe Melt",
"The Bull Rider", "Double Cheese Burger", "Hamburger"];
List<double> burgerPrice = [4.99, 7.25, 6.59, 5.79, 5.89, 3.99, 3.79];
Map<String, double> map2 = Map.fromIterables(burgers, burgerPrice);
List<String> otherItems = ["Chicken Quesadilla", "Cheese Quesadilla",
"Chicken Strips", "Popcorn Chicken", "Jalapeno Poppers"];
List<double> otherItemsPrice = [6.79, 6.29, 4.99, 4.59, 3.49];
Map<String, double> map3 = Map.fromIterables(otherItems, otherItemsPrice);
List<String> sides = ["French Fries", "Onion Rings", "Jalapeno Cheese Curds",
"Tater Tots", "Pretzel Bites", "Nachos & Cheese"];
List<double> sidesPrice = [3.29, 4.79, 4.99, 3.19, 4.59, 3.50];
Map<String, double> map4 = Map.fromIterables(sides, sidesPrice);
List<String> pizza = ["7-inch Cheese", "7-inc with topping"];
List<double> pizzaPrice = [4.59, 4.99];
Map<String, double> map5 = Map.fromIterables(pizza, pizzaPrice);
class Menu {
String sandwich;
String burger;
String otherItems;
String sides;
String pizza;
double sandwichPrice;
double burgerPrice;
double otherItemsPrice;
double sidesPrice;
double pizzaPrice;
Menu.s(this.sandwich, this.sandwichPrice){}
Menu.b(this.burger, this.burgerPrice){}
Menu.o(this.otherItems, this.otherItemsPrice){}
Menu.q(this.sides, this.sidesPrice){}
Menu.p(this.pizza, this.pizzaPrice){}
}
First of all, you need to import your hashMap file. Then update your MyWidget as follows:
I have displayed here an example of how you can use the Dropdown with map1 (i.e, Sandwiches).
You have to just iterate over the keys of the hashmap 'map1' & create the list of DropdownMenuItem from it & pass this list to the items property.
For each dropdown, you will need to save the selected option, so instead of using String value = '';, I have changed it to String selectedSandwich = 'Sandwiches';. This part is crucial as the Dropdown widget can have its value only as one of the options available. So, if you do not have the Sandwiches option in your dropdown list, there will be an error. Hence, I have added the Sandwiches option in the initState of your MyAppWidget.
You have to do the same for the remaining of the hashmaps. Let me know if you need any more help.
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
// Instead of value use selectedSandwich
String selectedSandwich = 'Sandwiches';
// List of sandwiches, you have to do the same for rest of the maps
// like: burgers, otherItems etc.
List<String> sandwiches = map1.keys.toList();
#override
void initState() {
super.initState();
// Adding sandwiches as an option is necessay as the dropdown's value
// must be equal to one of its options.
// I have done this only for sandwiches, but you need to do the same
// for rest.
sandwiches.insert(0, 'Sandwiches');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
DropdownButton<String>(
items: sandwiches // using map1.keys.toList
.map(
(e) => DropdownMenuItem<String>(
value: e,
child: Center(
child: Text(e),
),
),
)
.toList(),
onChanged: (_value) => {
print(_value.toString()),
setState(() {
selectedSandwich = _value; // Set selected sandwich
}),
},
value: selectedSandwich, // Use value
),
],
),
);
}
}

Flutter: How to remove extra paddings of checkbox (multiple CheckboxListTile)?

I am trying to create a checkbox list like this:
my plan
I used CheckBoxListTile with dense and contentPadding to remove all possible paddings, and this is what I have:
my result
Is there any way to remove the extra "padding" between CheckBoxListTile?
Here is my code (a single checkbox):
class Checklist extends StatefulWidget {
Checklist({#required this.content});
final String content;
#override
_ChecklistState createState() => _ChecklistState();
}
class _ChecklistState extends State<Checklist> {
bool _checked = false;
#override
Widget build(BuildContext context) {
return CheckboxListTile(
contentPadding: EdgeInsets.all(0),
//dense: true,
title: Text(widget.content, style: TextStyle(color: kBlackColor),),
value: _checked,
onChanged: (bool value) {
setState(() {
_checked = value;
});
},
controlAffinity: ListTileControlAffinity.leading,
activeColor: kBlackColor,
);
}
}
Multiple checkboxes:
Column(
children: [
Checklist(content: 'Develop an app'),
Checklist(content: 'Develop a good app'),
Checklist(content: 'Develop a really good app'),
],
),
wrap your CheckBox inside SizedBox will resize the padding of the check box
SizedBox(
height: whatYouWant,
width: whatYouWant,
child: Checkbox(...),
)
please check this solution: https://stackoverflow.com/a/59420505/11989529

Change Widget on Dropdown onchange event

I have a dropdown menu with several options. For sake of simplicity lets say they are: "A", "B" and "C".
This is the snippet of my code:
children: <Widget>[
FormBuilder(
key: _fbKey,
autovalidate: true,
child: Column(
children: <Widget>[
FormBuilderDropdown(
attribute: "value",
decoration: InputDecoration(
labelText: "Choose something?"),
hint: Text('Select Option'),
validators: [FormBuilderValidators.required()],
items: user.option.map((v) {
return DropdownMenuItem(
value: v,
child: ListTile(
leading: Image.asset(
'assets/img/image.png',
width: 50,
height: 50,
),
title: Text("${v.option}"),
));
}).toList(),
),
],
),
// if v.option == "A" is selected here build Widget A()
// if v.option == "B" is selected here build Widget B()
// if v.option == "C" is selected here build Widget C()
),
So, based on Dropdown selection I want to render appropriate Widget.
How can I build widget A if A is selected in the Dropdown menu, B if B is selected or C if C is selected?
This has to change dynamically though, any help is good.
Most simple way I see it is to create a function that renders your widgets, let's call it _renderWidget(), inside of that function you could have something as follows:
_renderWidget() {
if(condition == A) {
return Text('Widget A'); // this could be any Widget
} else if(condition == B) {
return Text('Widget B');
} else {
return Text('Widget C');
}
}
Then inside your DropdownButton onChanged function, you can change the condition based on the dropdown value:
onChanged(String value) {
if(value == 'something') {
setState(() {
condition = A; // A, B or C
});
}
}
You would call your _renderWidget() function inside the widget where you want to show them, for example let's say a Container widget.
Container(
child: _renderWidget()
)
Of course, all of this needs to be done inside a StatefulWidget.
use a stateful widget. you can have a field that says wich widget to show and call set state every time a different option is selected. all three widgets could be added to the main List<Widget> using collection if as follows:
children: <Widget>[
FormBuilder(
key: _fbKey,
autovalidate: true,
child: Column(
children: <Widget>[
FormBuilderDropdown(
attribute: "value",
decoration: InputDecoration(
labelText: "Choose something?"),
hint: Text('Select Option'),
validators: [FormBuilderValidators.required()],
items: user.option.map((v) {
return DropdownMenuItem(
value: v,
child: ListTile(
leading: Image.asset(
'assets/img/image.png',
width: 50,
height: 50,
),
title: Text("${v.option}"),
));
}).toList(),
),
],
),
),
if (v.option == "A") A(),
if (v.option == "B") B(),
if (v.option == "C") C(),
]
you can make a widget that accept the option in its constructor, and call it under the Drop Down builder
Example :
class RenderOption extends StatelessWidget {
final option;
const RenderOption({Key key, this.option}) : super(key: key);
#override
Widget build(BuildContext context) {
switch (option) {
case 1:
return Container();
break;
case 2:
return Container();
break;
case 3:
return Container();
break;
default:
}
}
}
now in you code above
children: <Widget>[
FormBuilder(
key: _fbKey,
autovalidate: true,
child: Column(
children: <Widget>[
FormBuilderDropdown(
attribute: "value",
decoration: InputDecoration(
labelText: "Choose something?"),
hint: Text('Select Option'),
validators: [FormBuilderValidators.required()],
items: user.option.map((v) {
return DropdownMenuItem(
value: v,
child: ListTile(
leading: Image.asset(
'assets/img/image.png',
width: 50,
height: 50,
),
title: Text("${v.option}"),
));
}).toList(),
),
],
),
//_currentOption is declared above in the widget tree
// it indicates the current selected option
RenderOption(option:_currentOption)
),
Note : you have to make the Parent Widget (which has the column or the listView as a child ) Stateful widget to make the code run properly
Edit : I've added the _currentOption variable which indicated the current selected option and paste it to the RenderOption Widget. you should implement the onChanged function in the FormBuilderDropDown to update the selected option , like this
onChanged: (option) =>setState(()=>_currentOption = option)

Highlight selected value in DropdownButton (like PopupMenuButton)

When a PopupMenuButton is pressed, the currently selected value is highlighted,
but when a DropdownButton is pressed, the currently selected value is not highlighted.
Is there a way to highlight the selected value of a DropdownButton?
For reference here is some sample code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: MyHomePage());
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String letter = 'A';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Popup Menu Button')),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 16.0),
Text('PopupMenuButton'),
buildPopupMenuButton(),
SizedBox(height: 16.0),
Text('DropdownButton'),
buildDropdownButton(),
],
),
);
}
Widget buildPopupMenuButton() {
return PopupMenuButton<String>(
padding: EdgeInsets.zero,
initialValue: letter,
onSelected: (val) => setState(() => letter = val),
child: ListTile(
title: Text('The letter $letter'),
),
itemBuilder: (BuildContext context) {
return <PopupMenuItem<String>>[
PopupMenuItem<String>(
value: 'A',
child: Text('The letter A'),
),
PopupMenuItem<String>(
value: 'B',
child: Text('The letter B'),
),
];
},
);
}
Widget buildDropdownButton() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: DropdownButton<String>(
value: letter,
onChanged: (val) => setState(() => letter = val),
items: [
DropdownMenuItem<String>(
value: 'A',
child: Text('The letter A'),
),
DropdownMenuItem<String>(
value: 'B',
child: Text('The letter B'),
),
],
),
);
}
}
Here's a video that shows the issue:
The DropdownMenuItem doesn't support many custom modifications on the child element, as there's no style, background, anything actually in the DropdownMenuItem attributes to help you with that. Looking at the code, it really wasn't built for that,
Yet, there's something you could add, a simple check on the child attribute of the DropdownMenuItem, and wrap the Text child element in something else or style the Text element itself if it is checked.
One example:
Widget buildDropdownButton() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: DropdownButton<String>(
value: letter,
onChanged: (val) => setState(() => letter = val),
items: [
DropdownMenuItem<String>(
value: 'A',
child: Container(
color: letter == 'A' ? Colors.black12 : null,
child: Text('The letter A'),
),
),
DropdownMenuItem<String>(
value: 'B',
child: Container(
color: letter == 'B' ? Colors.black12 : null,
child: Text('The letter B'),
),
),
],
),
);
}
Note that in a real case scenario, you would have a method with a paremeter to build each dropdown item, so the verification wouldn't have to be hardcoded like letter == 'A'.
This would be the output:
This approach allows you to style a bit, but it has an ugly result in some cases. Although it is customizable, there will always be a white margin around the item, and it also shows the same styles when the dropdown list is closed, so it gets a bit ugly on the main page.
Instead of changing the background, you can also change text colors, underline, icons on the side, something like that make it much better, like:
DropdownMenuItem<String>(
value: 'A',
child: Text('The letter A',
style: TextStyle(
color: letter == 'A' ? Colors.red : Colors.black87,
),
),
)
Well, as far as I know this grey overlay is a so called 'Ripple effect' in the material design library. It seems that Flutter does not adapt the full design in all widgets yet.
However you can try to use the InkWell widget to add this kind of animations/colors to current widgets:
https://flutter.io/docs/cookbook/gestures/ripples
E.g:
PopupMenuItem<String>(
value: 'B',
child: InkWell(child: Text('The letter B'))
),
I am not sure if the width will be correct, but at least it should show the grey overlay when you press on the entry.
You can also check the Flutter source:
https://github.com/flutter/flutter/blob/237fc2fb45639312001e947bf7465ef9f23bb699/packages/flutter/lib/src/material/popup_menu.dart#L933
Here you can see that a Inkwell is standard being used for the PopupMenuButton.
Responding to your original issue which was: "I'm interested in the darker background from the currently selected value when all of the values are shown."
Your PopupMenuButton will look at its initialValue: parameter each time it is opened--the item corresponding to this value will be highlighted. You will need to update the initialValue parameter each time using the onSelected function.
Make sure the parent widget is a StatefulWidget widget and create a reference to whatever your initialValue is. The PopupMenuButton has an onSelected parameter that takes in a function with parameter String.
Whenever you select an option from the PopupMenuButton, call
setState(() {
...
this.initialValue = value;
});
The full class will look something like this.
Class YourClass extends StatefulWidget {
#override
createState() => _YourClassState();
}
class _YourClassState extends State<YourClass> {
...
String initialValue = 'foo';
#override
Widget build(BuildContext context) {
final items = [
PopupMenuItem(
value: 'foo',
child: Text('foo'),
),
PopupMenuItem(
value: 'nice',
child: Text('nice'),
),
}
return Scaffold(
appBar: ...,
drawer: ...,
body: PopupMenuButton(
icon: ...,
itemBuilder: (_) => items,
initialValue: this.initialValue,
onSelected: (value) => bar(value),
),
);
}
void bar(String value) {
setState(() {
...
this.initialValue = value;
});
}
}
You can wrap the widget with Theme to set a highlight color.
return Theme(
data: ThemeData(highlightColor: Colors.grey[300]),
child: DropdownButton()
You may try it:
class CustomDropdownMenuItem<T> extends DropdownMenuItem<T> {
const CustomDropdownMenuItem({
super.key,
super.onTap,
super.value,
super.enabled = true,
super.alignment,
required this.current,
required super.child,
});
final T current;
#override
Widget build(BuildContext context) {
return Container(
color: current == value ? Theme.of(context).highlightColor : null,
child: super.build(context),
);
}
}
However, the element will not be completely covered in color. You can also add a check on the current device to exclude those that work correctly (web and desktop).
Basically, we have to wait for this issue to be solved.
Update:
Alternatively, you can use color selection if you use Text:
final theme = Theme.of(context);
...
return DropdownMenuItem<AppLocale>(
value: value,
onTap: () => {},
child: Text(
value.name,
style: theme.textTheme.titleMedium?.copyWith(
color: value == current ? theme.colorScheme.secondary : null),
),
);