Reputation: 3561
I am still new to Flutter. Is there an example of a material dropdown list text field? I saw the example on Material Text Field but I didn't find anywhere in the documentation on how to implement this. Thanks for your help on this.
Upvotes: 57
Views: 176927
Reputation: 17186
To get a text field with dropdown (combo box) rather than a conventional dropdown-selector, use DropdownMenu
using code like this:
TextEditingController controller = TextEditingController();
@override
Widget build(BuildContext context) {
return DropdownMenu<String>(
initialSelection: null,
controller: controller,
// By default this is false on mobile and true on desktop. If
// this is false on mobile, there seems to be no way to edit
// the text field, since tapping just opens the dropdown. If
// this is true, the dropdown still appears when the text area
// is clicked (and doesn't disappear after inputting text, even
// if the text matches none of the options).
requestFocusOnTap: true,
label: Text(widget.label),
// This is called when the user chooses an item from the dropdown
onSelected: (String? val) {
// TODO: store change with `setState` or whatever
},
searchCallback: (list, val) {
// There's no `onChanged` parameter, but this is called when
// text is input. But it is also called just after `build()`
// returns. If the handler triggers another `build` at that
// point, an exception will occur unless you use
// `addPostFrameCallback` like so. Also, the change handler
// should check whether `val` differs from the old value to
// prevent an endless series of re-renders.
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_oldValue != val)
// TODO: store the change
});
},
expandedInsets: EdgeInsets.zero, // expands the width to 100% of parent
dropdownMenuEntries: ["Option A", "Option B"]
.map<DropdownMenuEntry<String>>((String option) =>
DropdownMenuEntry<String>(
value: option, label: option, enabled: true))
.toList(),
);
}
Upvotes: 0
Reputation: 206
I guess what you are asking as I was searching for the same and came up with this solution.
Take a look at screenshots.
Text Field when focused dropdown appears
Selected item updated in TextField
Here I'm attaching my code:
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:paperbook/Constants/size_config.dart';
import 'package:paperbook/Constants/size_constant.dart';
import '../../../Constants/text_constants.dart';
class CourseDropdownTextField extends StatefulWidget {
const CourseDropdownTextField({super.key, required this.courseController});
final TextEditingController courseController;
@override
State<CourseDropdownTextField> createState() => CourseDropdownTextFieldState();
}
class CourseDropdownTextFieldState extends State<CourseDropdownTextField> {
late TextEditingController courseName;
List<String> dropdownList = <String>[
'One',
'Two',
'Three',
'Four',
'Five',
'Six',
'Seven',
'Eight',
'Nine',
'Ten'
];
final FocusNode _focusNode = FocusNode();
bool _isFocused = false;
void _onFocusChange() {
setState(() {
_isFocused = _focusNode.hasFocus;
});
// Perform your function when the TextField gains focus
if (_isFocused) {
showOverlay();
} else {
hideOverlay();
}
}
OverlayEntry? entry;
final layerLink = LayerLink();
void showOverlay() {
final overlay = Overlay.of(context);
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
entry = OverlayEntry(
builder: (context) => Positioned(
width: size.width ,
child: CompositedTransformFollower(
link: layerLink,
showWhenUnlinked: false,
offset: Offset(0, size.height + 10),
child: buildOverlay())
),
);
overlay.insert(entry!);
}
void hideOverlay() {
entry?.remove();
entry = null;
}
Widget buildOverlay() => NotificationListener<OverscrollIndicatorNotification>(
onNotification: (OverscrollIndicatorNotification? notification){
notification!.disallowIndicator();
return true;
},
child: Container(
clipBehavior: Clip.hardEdge,
height: SizeConfig.safeBlockVertical!* 42,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondary,
borderRadius: BorderRadius.circular(15),
),
child: ListView.separated(
padding: const EdgeInsets.all(0),
itemBuilder: (context, index){
return GestureDetector(
onTap: () {
courseName.text = dropdownList[index];
hideOverlay();
_focusNode.unfocus();
},
child: Container(
padding: EdgeInsets.all(mainpadding),
child: DefaultTextStyle(
style: const TextStyle(),
child: Text(
dropdownList[index],
style: GoogleFonts.robotoCondensed(
textStyle: const TextStyle(letterSpacing: 0.4),
fontSize: SizeConfig.safeBlockVertical! * 2.2,
fontWeight: FontWeight.w500,
fontStyle: FontStyle.normal,
color: Colors.grey
),
),
)
),
);
},
separatorBuilder: (context, index) {
return const Divider(
height: 0,
thickness: 3,
);
},
itemCount: dropdownList.length)
),
);
@override
void initState(){
super.initState();
courseName = TextEditingController();
_focusNode.addListener(_onFocusChange);
}
@override
void dispose(){
super.dispose();
courseName.dispose();
_focusNode.removeListener(_onFocusChange);
_focusNode.dispose();
}
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: layerLink,
child: TextFormField(
keyboardType: TextInputType.none,
readOnly: true,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.menu_book_rounded),
suffixIcon: Icon(Icons.arrow_drop_down_rounded),
labelText: courseText,
hintText: courseText,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15))
)
),
controller: courseName,
focusNode: _focusNode,
onChanged: (value) {
widget.courseController.text = value;
},
),
);
}
}
Upvotes: 0
Reputation: 190
I suppose you want a dropdown button wich is also a text field. So I found this video. It explains how to implement this kind of field using this package.
Upvotes: 1
Reputation: 449
This answer provide a example using a DropdownButtonFormField
a convenience widget that wraps a DropdownButton widget in a FormField.
Ideal if you are using a Material FormField
Upvotes: 0
Reputation: 740
Following Jeff Frazier's answer, You can have more customization by using DropdownButton2 or DropdownButtonFormField2 from DropdownButton2 package. It's based on Flutter's core DropdownButton with more options you can customize to your needs.
Upvotes: 3
Reputation: 579
You want the DropdownButton or DropdownButtonFormField https://api.flutter.dev/flutter/material/DropdownButton-class.html
and the DropdownMenuItem https://api.flutter.dev/flutter/material/DropdownMenuItem-class.html
return DropdownButtonFormField(
items: categories.map((String category) {
return new DropdownMenuItem(
value: category,
child: Row(
children: <Widget>[
Icon(Icons.star),
Text(category),
],
)
);
}).toList(),
onChanged: (newValue) {
// do other stuff with _category
setState(() => _category = newValue);
},
value: _category,
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(10, 20, 10, 20),
filled: true,
fillColor: Colors.grey[200],
hintText: Localization.of(context).category,
errorText: errorSnapshot.data == 0 ? Localization.of(context).categoryEmpty : null),
);
Upvotes: 41
Reputation: 521
Other answers have fully described what you need, but here is an example that puts it all together, this is a reusable dropdown textfield widget that allows you to specify a list of options of any type (without losing dart's beautiful type system).
class AppDropdownInput<T> extends StatelessWidget {
final String hintText;
final List<T> options;
final T value;
final String Function(T) getLabel;
final void Function(T) onChanged;
AppDropdownInput({
this.hintText = 'Please select an Option',
this.options = const [],
this.getLabel,
this.value,
this.onChanged,
});
@override
Widget build(BuildContext context) {
return FormField<T>(
builder: (FormFieldState<T> state) {
return InputDecorator(
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(
horizontal: 20.0, vertical: 15.0),
labelText: hintText,
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(5.0)),
),
isEmpty: value == null || value == '',
child: DropdownButtonHideUnderline(
child: DropdownButton<T>(
value: value,
isDense: true,
onChanged: onChanged,
items: options.map((T value) {
return DropdownMenuItem<T>(
value: value,
child: Text(getLabel(value)),
);
}).toList(),
),
),
);
},
);
}
}
And you may use it like this:
AppDropdownInput(
hintText: "Gender",
options: ["Male", "Female"],
value: gender,
onChanged: (String value) {
setState(() {
gender = value;
// state.didChange(newValue);
});
},
getLabel: (String value) => value,
)
Upvotes: 13
Reputation: 4728
UPDATED :
Text form field with a dropdown
var _currencies = [
"Food",
"Transport",
"Personal",
"Shopping",
"Medical",
"Rent",
"Movie",
"Salary"
];
FormField<String>(
builder: (FormFieldState<String> state) {
return InputDecorator(
decoration: InputDecoration(
labelStyle: textStyle,
errorStyle: TextStyle(color: Colors.redAccent, fontSize: 16.0),
hintText: 'Please select expense',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(5.0))),
isEmpty: _currentSelectedValue == '',
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: _currentSelectedValue,
isDense: true,
onChanged: (String newValue) {
setState(() {
_currentSelectedValue = newValue;
state.didChange(newValue);
});
},
items: _currencies.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
);
},
)
Hope this helps!
Upvotes: 79
Reputation: 1324
'Dropdown' may not be the correct word that you are using to describe the design of text field referred in your material design example.
Here is how to implement it in Flutter:
import 'package:flutter/material.dart';
void main() {
runApp(TextFieldExample());
}
class TextFieldExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Text Field Example',
home: HomePage(),
theme: ThemeData(
primaryColor: Colors.deepPurple,
accentColor: Colors.white,
),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Text Field Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
//Material example
TextField(
decoration: InputDecoration(
filled: true,
hintText: 'Enter text',
labelText: 'Default text field'),
controller: new TextEditingController(),
),
SizedBox(
height: 16.0,
),
//Alternate
TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter text',
labelText: 'Text field alternate'),
controller: new TextEditingController(),
),
],
),
),
);
}
}
This sample app contains two different examples of text field design that shrink and expand the associated label.
Upvotes: -20