How to set dynamic height for dropdown popup in flutter - flutter

I am new to flutter development. I am using the dropdown button of my application. When opening the drop-down menu, the text is getting cut in the popup dialog. Below I attached a screenshot with coding. Please guide me in fixing this issue.
DropdownButtonHideUnderline(
child: new DropdownButton(
isExpanded: true,
value: dropDownValue,
isDense: true,
//icon: Icon(Icons.keyboard_arrow_down, color: Colors.white,),
onChanged: (String newValue) {
setState(() {
dropDownValue = newValue;
state.didChange(newValue);
});
},
items: dropDownList.map((String value) {
return new DropdownMenuItem(
value: value,
child: new SizedBox(
width: MediaQuery.of(context).size.width / 1.4,
child: new Text(value,
softWrap: true,
style: TextStyle(color: Colors.white, fontSize: 18.0),),)
);
}).toList(),
),
),
);

Copying the DropdownMenuItem class as someone else suggested will not be enough as DropdownButton requires items to be of type List<DropdownMenuItem<T>>.
I have created the following widget which should help with your issue:
import 'package:flutter/material.dart';
/// Looks like a DropdownButton but has a few differences:
///
/// 1. Can be opened by a single tap even if the keyboard is showing (this might be a bug of the DropdownButton)
///
/// 2. The width of the overlay can be different than the width of the child
///
/// 3. The current selection is highlighted in the overlay
class CustomDropdown<T> extends PopupMenuButton<T> {
CustomDropdown({
Key key,
#required PopupMenuItemBuilder<T> itemBuilder,
#required T selectedValue,
PopupMenuItemSelected<T> onSelected,
PopupMenuCanceled onCanceled,
String tooltip,
double elevation = 8.0,
EdgeInsetsGeometry padding = const EdgeInsets.all(8.0),
Icon icon,
Offset offset = Offset.zero,
Widget child,
String placeholder = "Please select",
}) : super(
key: key,
itemBuilder: itemBuilder,
initialValue: selectedValue,
onSelected: onSelected,
onCanceled: onCanceled,
tooltip: tooltip,
elevation: elevation,
padding: padding,
icon: icon,
offset: offset,
child: child == null ? null : Stack(
children: <Widget>[
Builder(
builder: (BuildContext context) => Container(
height: 48,
alignment: AlignmentDirectional.centerStart,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
DefaultTextStyle(
style: selectedValue!= null ? Theme.of(context).textTheme.subhead
: Theme.of(context).textTheme.subhead.copyWith(color:
Theme.of(context).hintColor),
child: Expanded(child: selectedValue== null ? Text(placeholder) : child),
),
IconTheme(
data: IconThemeData(
color: Theme.of(context).brightness == Brightness.light
? Colors.grey.shade700 : Colors.white70,
),
child: const Icon(Icons.arrow_drop_down),
),
],
),
),
),
Positioned(
left: 0.0,
right: 0.0,
bottom: 8,
child: Container(
height: 1,
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: Color(0xFFBDBDBD), width: 0.0)),
),
),
),
],
),
);
}
It actually extends PopupMenuButton as you can see, but I've made it look the same as the DropdownButton.
itemBuilder needs to return List<PopupMenuEntry<T>>, with each entry usually being a PopupMenuItem to which you can provide any child widget.
selectedValue is the currently selected value, which will be highlighted in the overlay. If it is null, a Text widget with the placeholder string is shown. If it is not null, the child widget is shown.
You should be able to disable the highlight by modifying this class to either call super() with an initialValue of null, or even better add a boolean to the constructor to control this from the outside.

The height of DropdownMenuItem is hardcoded to _kMenuItemHeight:
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/dropdown.dart#L486
The only thing you can do is copy this entire file and adjust to your needs.

Related

Flutter - How to select an item (tag) automatically in the UI - List<dynamic>

I am trying to preselect a particular tag shown on the right of the image.
However, I am unable to figure out where to set it. The tags are coming from an API (Postgres backend).
Once it is built to a list of overlay as shown on the right again in the screenshot. I just wanted it to preselect, "Morning", "Evening" or "Daytime" based on the time of the day.
To start off with, I am not able to preselect anything in "selectedTags". This can only be done manually by the user when clicked on a tag.
The method is shared below.
showTagPicker(context, allTags) async {
await showModalBottomSheet(
isDismissible: false,
enableDrag: false,
backgroundColor: Colors.transparent,
isScrollControlled: true,
context: context,
builder: (builder) => Center(
child: Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
color: Colors.white,
),
margin: EdgeInsets.all(16),
padding: EdgeInsets.all(24),
child: ListView(
shrinkWrap: true,
children: <Widget>[
Text(
"Please pick your tags",
style: TextStyle(fontSize: 16),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: TagPicker(
height: MediaQuery.of(context).size.height * .6,
tags: allTags,
onTagSelected: (_selectedTags) {
selectedTags = _selectedTags;
print("----->");
print(selectedTags);
print("<-----");
},
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
RaisedButton(
color: PRIMARY,
textColor: WHITE,
onPressed: () {
Navigator.of(context).pop();
navigateToAnalysis(context);
},
child: Text("Save"),
),
],
)
],
),
),
),
);
}
I tried, adding "print" to debug and see what and where things are being set but I did not get any further, I have also shown the debug screen if it helps.
Any direction here to preselect one/many tags would be helpful.
Please let me know if I must provide additional details to solve this.
Also, I know there are several things in the code which might be wrong, it is inherited code and I am struggling a bit.
Edit: Including TagPicker. It is not a public library but our widget.
class TagPicker extends StatefulWidget {
const TagPicker(
{Key key, this.height, this.tags, this.onTagSelected, this.selectedTags})
: super(key: key);
#override
TagPickerState createState() => TagPickerState();
final double height;
final List tags;
final List selectedTags;
final Function onTagSelected;
}
class TagPickerState extends State<TagPicker> {
List selectedTags = [];
#override
void initState() {
super.initState();
if (widget.selectedTags != null) {
setState(() {
selectedTags = widget.selectedTags;
});
}
}
#override
Widget build(BuildContext context) {
return widget.tags != null
? Container(
constraints: widget.height != null
? BoxConstraints(maxHeight: widget.height, minHeight: 60)
: BoxConstraints(),
child: SingleChildScrollView(
child: Wrap(
spacing: 0.0,
children: List.generate(
widget.tags.length,
(index) {
return Padding(
padding: const EdgeInsets.only(right: 4.0),
child: ChoiceChip(
selectedColor: PRIMARY,
labelStyle: TextStyle(
fontSize: 12,
color: selectedTags.contains(widget.tags[index])
? WHITE
: Colors.black),
label: Text(widget.tags[index]['tag_name']),
selected: selectedTags.contains(widget.tags[index]),
onSelected: (selected) {
setState(() {
selectedTags.contains(widget.tags[index])
? selectedTags.remove(widget.tags[index])
: selectedTags.add(widget.tags[index]);
widget.onTagSelected(selectedTags);
});
},
),
);
},
),
),
),
)
: Container();
}
}
Pass selectedTags as an argument to TagPicker and modify TagPicker to render an initial set of selected tags. As before onTagSelected callback will provide an updated set.

How to customize the dropdownlist in flutter , like flikart dropdownlist

I want to dropdownlist but i'm not able to do following things.
change the width of the dropdownbutton
make the dropdownlist to start at the dropdownbutton's height , not above it as default
adjust the height of the dropdownmenuitem
Want i want is
What i have is
The code goes like
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
// isDense: true,
// isExpanded: true,
itemHeight: null,
// menuMaxHeight: 10,
alignment: AlignmentDirectional.center,
elevation: 0,
value: selectedQuantity,
selectedItemBuilder: (BuildContext context) {
return _dropDownQuantities.map<Widget>((String item) {
return Container(
alignment: Alignment.center,
// color: Colors.green,
child: Text(
'Qty: $item',
style: TextStyle(fontSize: 14),
),
);
}).toList();
},
items: _dropDownQuantities.map((e) {
return DropdownMenuItem(
alignment: AlignmentDirectional.topStart,
child: Container(
child: Column(
children: [Container(child: Text(e))],
)),
value: e,
);
}).toList(),
hint: Text("Qty: 1 "),
onChanged: (value) {
setState(() {
selectedQuantity = value!;
});
}),
),
),
Use DropdownButton2 to achieve that.
Use buttonWidth property to change the width of the dropdownbutton.
Use offset property to change the position of the dropdown menu. You should add button's height to the dy offset to make it start at the dropdownbutton's height like this: Offset(0.0, "button's height")
Use itemHeight property to adjust the height of the dropdownmenuitem.
Disclaimer: I am the author of the package mentioned above.

Flutter get class field value back and forth

I'm trying to create a custom checkbox widget and I'm having trouble getting the bool value of that checkbox from another class:
So am having a form in stateful widget Signup, within this form I'm calling my CustomCheckBox widget (also a stateful widget).
The issue: When I click on the checkbox its value change to true in the CustomCheckBox widget however after submitting the form in Signup widget the value always false (seams to be no back communication between the the two widgets)
My CustomCheckBox code:
import 'package:flutter/material.dart';
import 'package:tunimmo/Constants/palette.dart';
class CustomCheckBox extends StatefulWidget {
bool checked;
final String label;
CustomCheckBox({this.checked, this.label});
#override
_CustomCheckBoxState createState() => _CustomCheckBoxState();
}
class _CustomCheckBoxState extends State<CustomCheckBox> {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 15.0, left: 30, right: 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
height: 30,
decoration: BoxDecoration(
color: myPrimaryColor,
),
child: Text(" "),
),
Text(' ${widget.label}', style: Theme.of(context).textTheme.bodyText1),
],
),
Center(
child: InkWell(
onTap: () {
setState(() {
widget.checked = !widget.checked;
});
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: widget.checked ? myPrimaryColor : myWhiteColor,
border: Border.all(
color: myPrimaryColor,
width: 3,
),
),
child: Padding(
padding: const EdgeInsets.all(3.0),
child: widget.checked
? Icon(
Icons.check,
size: 25.0,
color: myWhiteColor,
)
: Icon(
Icons.check_box_outline_blank,
size: 25.0,
color: myWhiteColor,
),
),
),
)),
],
),
);
}
}
In the Signup widget I'm just calling the constructor and passing a bool field (expected to send/get the value in the CustomCheckBox widget) and a string label.
PS: I have more than one checkbox in my form.
Please advice!
The problem is that you are not changing the checked value in the screen(signup) but changing in the custom checkbox, to solve this issue define checked variable in SignUp if not already did, then define Function call back in the checkbox widget as follows:
final Function(bool)checkChanged;
CustomCheckBox({this.checked, this.label,this.checkedChanged});
then call it in ontap and give the value change
onTap: () {
widget.checkedChanged(!widget.checked);
setState(() {
widget.checked = !widget.checked;
});
}
do the following when calling the CustomCheckbox:
CustomCheckBox(
checked:false,
label:'a label',
checkedChanged:(val){
checked=val;
}
)
and use that checked variable when submitting the form and that should solve your problem.

SwitchListTile and Container or RaisedButton within ListView.builder not working properly

I have a Dialog class in which I want to show different designations that could be assigned to an employee.
In the beginning, I tried to use only a RaisedButton to select the desired designations. Within the App, the Button should change Colors. This part is found within a StatefulWidget.
I also tried a modified version, where I created a new StatefulWidget only for the Dialog part but this part did not have any effect, thus I thought to implement a SwitchListTile to do the same thing.
The SwitchListTile gets activated and deactivated although only the true value gets registered. This means that when I deactivate (swipe to left) the code does not go within the following setState:
setState(() { hEnabled[hDesignations[index].designation] = value; });
Also when the hEnabled Map gets changed within the setState method the following code does not re-run to change the color of the container:
color: hEnabled[hDesignations[index].designation] ? Colors.green : Colors.grey,
Part with the Dialog:
Widget buildChooseDesignations(
BuildContext context, List<Designation> hDesignations) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadiusDirectional.circular(8.0),
),
child: _buildDialogChild(context, hDesignations),
);
}
_buildDialogChild(BuildContext context, List<Designation> hDesignations) {
//todo: when editing an employee I need the chosen designations (have to pass a list)
Map<String, bool> hEnabled = new Map<String, bool>();
for (var i = 0; i < hDesignations.length; i++) {
hEnabled[hDesignations[i].designation] = false;
}
return Container(
height: 200.0,
//todo: width not working properly
width: 50,
child: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: hDesignations.length,
itemBuilder: (context, index) {
return Row(
children: <Widget>[
Expanded(
child: Container(
width: 10,
color: hEnabled[hDesignations[index].designation]
? Colors.green
: Colors.grey,
padding: EdgeInsets.only(left: 80),
child: Text(hDesignations[index].designation,
style: TextStyle(fontWeight: FontWeight.bold),),
),
),
Expanded(
child: SwitchListTile(
value: hEnabled[hDesignations[index].designation],
onChanged: (bool value) {
setState(() {
hEnabled[hDesignations[index].designation] =
value;
});
}),
)
],
);
}),
),
SizedBox(
height: 15.0,
),
RaisedButton(
color: Colors.blueGrey,
child: Text(
'set',
style: TextStyle(color: Colors.white),
),
onPressed: () {
//todo: save the 'newly' selected designations in a list on set click
},
)
],
),
);
}
The Dialog is called when I click on the Add + FlatButton and looks like this:
ButtonTheme(
height: 30.0,
// child: Container(),
child: FlatButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
color: Colors.blueGrey.shade200,
onPressed: () {
//todo add Dialog
// List<Designation> hList = state.designations;
showDialog(
context: context,
builder: (context) => buildChooseDesignations(
context, state.designations));
// DesignationDialog(
// designations:state.designations));
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)),
child: Text(
'Add +',
style: TextStyle(color: Colors.black),
),
),
),
Found the problem :)
First I did re-write everything into a new StatefulWidget. This I needed since I want that my widget gets re-build after I click on the SwitchListTile to re-color my Container.
Then I had to move my hEnabled (re-named hChecked) map outside the state. The reason was that the widget would re-build all the everything including the initialization of this map, making the user's input useless.
The same applies to the RaisedButton Widget.
Here is my code:
class DesignationDialog extends StatefulWidget {
final List<Designation> designations;
final Map<String, bool> hChecked;
DesignationDialog({Key key, this.designations, this.hChecked}) : super(key: key);
#override
_DesignationDialogState createState() => _DesignationDialogState();
}
class _DesignationDialogState extends State<DesignationDialog> {
_buildDialogChild(BuildContext context, List<Designation> hDesignations) {
//todo: when editing an employee I need the chosen designations (have to pass a list)
// for (var i = 0; i < hDesignations.length; i++) {
// hChecked[hDesignations[i].designation] = false;
// }
return Container(
height: 200.0,
child: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: hDesignations.length,
itemBuilder: (context, index) {
// return ButtonTheme(
// //todo: fix the width of the buttons is not working
// minWidth: 20,
// child: RaisedButton(
// color: widget.hChecked[hDesignations[index].designation]
// ? Colors.green
// : Colors.grey,
// child: Text(hDesignations[index].designation),
// onPressed: () {
// //todo mark designation and add to an array
// setState(() {
// widget.hChecked[hDesignations[index].designation] =
// !widget
// .hChecked[hDesignations[index].designation];
// });
// },
// ),
// );
// -- With Switch
return Row(
children: <Widget>[
Expanded(
child: Container(
child: Text(hDesignations[index].designation),
width: 10,
color: widget.hChecked[hDesignations[index].designation]
? Colors.green
: Colors.grey,
)),
Expanded(
child: SwitchListTile(
value: widget.hChecked[hDesignations[index].designation],
onChanged: (bool value) {
setState(() {
widget.hChecked[hDesignations[index].designation] =
value;
});
}),
)
],
);
// -- end
}),
),
SizedBox(
height: 15.0,
),
RaisedButton(
color: Colors.blueGrey,
child: Text(
'set',
style: TextStyle(color: Colors.white),
),
onPressed: () {
//todo: save the 'newly' selected designations in a list on set click
},
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadiusDirectional.circular(8.0),
),
child: _buildDialogChild(context, widget.designations),
);
}

Can't figure out how to properly style DropdownButton

I'm kinda new to Flutter and I'm building an app for a college project, but I'm having problems with this widget.
DropdownButton input value in white color
DropdownButton input value in black color
This is my DropdownButton code, it appears with the Hint in white color, but when I select an item the value in the button appears as black. If I change the DropdownButton color to white, then when the popup appears the background-color is white and so the font-color. This way I can't see the items, because they're the same color as the background.
class DropdownWidget extends StatelessWidget {
final IconData icon;
final IconData arrowIcon;
final String hint;
final List items;
final Stream stream;
final Function onChanged;
DropdownWidget({this.icon, this.arrowIcon, this.hint, this.items, this.stream, this.onChanged});
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: stream,
builder: (context, snapshot) {
print("Snapshot data -> ${snapshot.data}");
return InputDecorator(
child: DropdownButton(
icon: Icon( arrowIcon, color: Colors.white,),
hint: Text( hint, style: TextStyle(color: Colors.white),),
items: items.map((value) {
print("Valor do item $value");
return DropdownMenuItem(
value: value,
child: Text(value.runtimeType == int ? value.toString() : value, style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: onChanged,
value: snapshot.data,
isExpanded: true,
style: TextStyle(
// color: Colors.black,
color: Theme.of(context).textSelectionColor,
fontSize: 18.0,
),
underline: Container(),
isDense: true,
),
decoration: InputDecoration(
icon: icon == null ? null : Icon(icon, color: Colors.white,),
hintText: hint,
hintStyle: TextStyle(color: Colors.white),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Theme.of(context).primaryColor)
),
contentPadding: EdgeInsets.only(
left: 5,
right: 0,
bottom: 24,
top: 30
),
errorText: snapshot.hasError ? snapshot.error : null,
),
);
}
);
}
}
What could I do to solve this? Is there a way to make the popup's background-color darker or just the value inside the button in a different color from the item's color?
You have to wrap your DropDownButton in a Theme. Example code:
Theme(
data: ThemeData(canvasColor: Colors.black), //this is where the magic happens
child: DropdownButton<String>(
value: dropDownValue,
onChanged: (String newValue) {
setState(() {
dropDownValue = newValue;
});
},
For those, who have also smashed against the brutal reality finding a way to add a dropdown with Flutter.
As mentioned by #asterisk12 adding canvasColor to the Theme is the way to change the background for the dropdown list.
My answer is for the rest of you still battling with OTHER styling issues
I am leaving here an example of how I managed to achieve (almost) what I needed:
list appears below the button
button is rectangular
there is a hint text
list is the same width as button
For it to work you will need a dropdown_button2 dependency (https://pub.dev/packages/dropdown_button2/install)
class DropDownButton extends StatefulWidget {
final List<String> options;
const DropDownButton({Key? key, required this.options}) : super(key: key);
#override
State<DropDownButton> createState() => _DropDownButtonState();
}
class _DropDownButtonState extends State<DropDownButton> {
String? selectedValue;
#override
Widget build(BuildContext context) {
return SizedBox(
height: 70,
child: Container(
margin: const EdgeInsets.fromLTRB(2, 5, 2, 5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(3),
),
child: DropdownButtonHideUnderline(
child: DropdownButton2<String>(
dropdownElevation: 0,
hint: const Text(
'Select Item',
),
icon: const Icon(
Icons.arrow_downward,
),
iconSize: 30,
isExpanded: true,
iconEnabledColor: Colors.teal,
buttonPadding: EdgeInsets.all(12),
value: selectedValue,
dropdownMaxHeight: 150,
scrollbarAlwaysShow: true,
items: widget.options
.map((e) => DropdownMenuItem(value: e, child: Text(e)))
.toList(),
offset: const Offset(0, 3),
onChanged: (value) {
setState(() {
selectedValue = value;
});
}),
),
),
);
}
}
Details:
remove the shadow from the list: dropdownElevation: 0
add a custom icon to DropdownButton2:
icon: const Icon(
Icons.arrow_downward,),
iconSize: 30,
Making the list scrollbar(you see it only when all elements do not fit in the dropdown, you can make dropdownMaxHeight smaller to see the difference).
scrollbarAlwaysShow: true,
Last but not least change the position of the list:
you can go wild :D and make some weird position
offset: const Offset(-20, -3),
or you can go not that wild and keep it as in my example so, that there is no space between button and the list
offset: const Offset(0, 3),
Hope I could help someone as desperate as I have recently been and save a bit of time for you.