Reputation: 2511
How do I make the autocomplete box be the same size with the TextField
which doesn't have a specific width, it takes up the maximum width.
Autocomplete(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return ['aa', 'bb', 'cc', 'aa', 'bb', 'cc'];
}
return ['aa', 'bb', 'cc', 'aa', 'bb', 'cc']
.where((String option) {
return option
.toString()
.contains(textEditingValue.text.toLowerCase());
});
},
onSelected: (option) {
print(option);
},
),
Upvotes: 42
Views: 21323
Reputation: 29
I just do like default builder source, and it just work property. I think it's error happened because not has parent config.
optionsViewBuilder: (context, onSelected, options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: 100.r),
child: Container(
width: 100.r,
height: 100.r,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: context.oldTheme.lightGrey,
)),
child: ListView.builder(
itemBuilder: (context, index) {
return Text(
options.toList()[index],
style: context.oldTheme.textTheme.body2,
);
},
itemCount: options.length,
),
),
),
),
);
},
Upvotes: 0
Reputation: 113
In my case, I just had to wrap the widget that I'm returning in optionsBuilder
in an Align
. After doing that, it started working as intended, creating the menu under the input field without stretching out of the screen. I have also used LayoutBuilder
's constraints.maxWidth
to define the menu's width.
Example:
LayoutBuilder(
builder: (context, constraints) {
return RawAutocomplete<String>(
optionsViewBuilder: (context, onSelected, options) {
return Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: constraints.maxWidth,
child: Material(
elevation: 4,
clipBehavior: Clip.antiAlias,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: options.map((e) {
return ListTile(title: Text(e));
}).toList(),
),
),
),
);
},
);
},
);
Upvotes: 7
Reputation: 161
Still happen in flutter 3.3.5 ...
My workaround is wrapping the Autocomplete
inside a LayoutBuilder
and using the constraints.maxWidth
to set the width for the optionsViewBuilder
's ListView.
The detailed snippet is here: https://github.com/flutter/flutter/issues/78746#issuecomment-1326488333
Upvotes: 3
Reputation:
I have seen source code autocomplete.dart
, there is missing parameter width, but height exists, I have added width by hand, and it worked.
class Autocomplete<T extends Object> extends StatelessWidget {
/// Creates an instance of [Autocomplete].
const Autocomplete({
Key? key,
required this.optionsBuilder,
this.displayStringForOption = RawAutocomplete.defaultStringForOption,
this.fieldViewBuilder = _defaultFieldViewBuilder,
this.onSelected,
this.optionsMaxHeight = 200.0,
this.optionsMaxWidth = 285.0 // I added this line
this.optionsViewBuilder,
this.initialValue,
/// The default value is set to 200.
final double optionsMaxHeight;
final double optionsMaxWidth; // Added this line
@override
Widget build(BuildContext context) {
return RawAutocomplete<T>(
displayStringForOption: displayStringForOption,
fieldViewBuilder: fieldViewBuilder,
initialValue: initialValue,
optionsBuilder: optionsBuilder,
optionsViewBuilder: optionsViewBuilder ?? (BuildContext context, AutocompleteOnSelected<T> onSelected, Iterable<T> options) {
return _AutocompleteOptions<T>(
displayStringForOption: displayStringForOption,
onSelected: onSelected,
options: options,
maxOptionsHeight: optionsMaxHeight,
maxOptionsWidth: optionsMaxWidth, // Added this line
);
},
onSelected: onSelected,
);
}
// The default Material-style Autocomplete options.
class _AutocompleteOptions<T extends Object> extends StatelessWidget {
const _AutocompleteOptions({
Key? key,
required this.displayStringForOption,
required this.onSelected,
required this.options,
required this.maxOptionsHeight,
required this.maxOptionsWidth // Added this line
}) : super(key: key);
final AutocompleteOptionToString<T> displayStringForOption;
final AutocompleteOnSelected<T> onSelected;
final Iterable<T> options;
final double maxOptionsHeight;
final double maxOptionsWidth;
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: maxOptionsHeight, maxWidth: maxOptionsWidth), // Added maxWidth which was missing here
child: ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
Upvotes: 2
Reputation: 1523
As pointed out here, at the time of writing this, the best option seems to be to alter the BoxConstraints
width and height included in the default _AutoCompleteOptions
code (that can be found here), and pass it as the optionsViewBuilder
argument of Autocomplete
.
This requires that you are able to calculate the required width in advance, though.
Pasting my code, just for reference:
Autocomplete<Animal>(
optionsBuilder: (textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<Animal>.empty();
}
return animalsList.where((Animal option) {
return "${option.id}".contains(textEditingValue.text.toLowerCase()) ||
option.nome!.toLowerCase().contains(textEditingValue.text.toLowerCase()) ||
option.ownerCF!.toLowerCase().contains(textEditingValue.text.toLowerCase());
});
},
optionsViewBuilder: (context, onSelected, options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200, maxWidth: 600), //RELEVANT CHANGE: added maxWidth
child: ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final Animal option = options.elementAt(index);
return InkWell(
onTap: () {
onSelected(option);
},
child: Builder(builder: (BuildContext context) {
final bool highlight = AutocompleteHighlightedOption.of(context) == index;
if (highlight) {
SchedulerBinding.instance!.addPostFrameCallback((Duration timeStamp) {
Scrollable.ensureVisible(context, alignment: 0.5);
});
}
return Container(
color: highlight ? Theme.of(context).focusColor : null,
padding: const EdgeInsets.all(16.0),
child: Text(option.getInfo() + " - " + option.getOwnerInfo()),
);
}),
);
},
),
),
),
);
},
displayStringForOption: (option) => option.getInfo() + " - " + option.getOwnerInfo(),
onSelected: (option) => selected = option,
);
Upvotes: 8
Reputation: 20369
This is a known bug for Autocomplete widget, the bug is being tracked here: https://github.com/flutter/flutter/issues/78746
So the only solution at the moment is to create your own customized widget. I wrote my own widget and published as a package for reuse and is also being maintained.
you can find it here: https://pub.dev/packages/searchfield
Upvotes: 2
Reputation: 75
I have the same issue and resolved it by using a ValueListenableBuilder
.
The width of Material
widget is depends on width of the widget returned by field ViewBuilder
(default is a TextFormField
), so one solution is get width of TextFormField
after one frame completed and notify your custom optionsViewBuilder
sample code:
import 'package:flutter/material.dart';
class AutocompleteText extends StatefulWidget {
@override
State<StatefulWidget> createState() => AutocompleteTextState();
}
class AutocompleteTextState extends State<AutocompleteText> {
final ValueNotifier<double?> optionsViewWidthNotifier = ValueNotifier(null);
static const List<User> _userOptions = <User>[
User(name: 'Alice', email: '[email protected]'),
User(name: 'Bob', email: '[email protected]'),
User(name: 'Charlie', email: '[email protected]'),
];
static String _displayStringForOption(User option) => option.name;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(20),
child: Autocomplete<User>(
displayStringForOption: _displayStringForOption,
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<User>.empty();
}
return _userOptions.where((User option) {
return option
.toString()
.contains(textEditingValue.text.toLowerCase());
});
},
onSelected: (User selection) {
print('You just selected ${_displayStringForOption(selection)}');
},
fieldViewBuilder: (BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted) {
return OrientationBuilder(builder: (context, orientation) {
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
optionsViewWidthNotifier.value =
(context.findRenderObject() as RenderBox).size.width;
});
return TextFormField(
controller: textEditingController,
focusNode: focusNode,
onFieldSubmitted: (String value) {
onFieldSubmitted();
},
);
});
},
optionsViewBuilder: (
BuildContext context,
AutocompleteOnSelected<User> onSelected,
Iterable<User> options,
) {
return ValueListenableBuilder<double?>(
valueListenable: optionsViewWidthNotifier,
builder: (context, width, _child) {
return Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: SizedBox(
width: width,
height: 200.0,
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final User option = options.elementAt(index);
return InkWell(
onTap: () {
onSelected(option);
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(_displayStringForOption(option)),
),
);
},
),
),
),
);
});
},
),
));
}
@override
void dispose() {
optionsViewWidthNotifier.dispose();
super.dispose();
}
}
@immutable
class User {
const User({
required this.email,
required this.name,
});
final String email;
final String name;
@override
String toString() {
return '$name, $email';
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is User && other.name == name && other.email == email;
}
@override
int get hashCode => hashValues(email, name);
}
Upvotes: 6
Reputation: 471
I got the same problem while trying to make my own autocomplete widget based on RawAutocomplete and I used a layout builder to get the perfect container width size inside my optionsViewBuilder:
LayoutBuilder(
builder: (context, constraints) => RawAutocomplete<String>(
focusNode: focusNode,
fieldViewBuilder: fieldViewBuilder,
optionsViewBuilder: (context, onSelected, options) => Align(
alignment: Alignment.topLeft,
child: Material(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(bottom: Radius.circular(4.0)),
),
child: Container(
height: 52.0 * options.length,
width: constraints.biggest.width, // <-- Right here !
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: options.length,
shrinkWrap: false,
itemBuilder: (BuildContext context, int index) {
final String option = options.elementAt(index);
return InkWell(
onTap: () => onSelected(option),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(option),
),
);
},
),
),
),
),
optionsBuilder: (textEditingValue) =>
suggestions.where((element) => element.contains(textEditingValue.text.trim().toUpperCase())),
textEditingController: controller,
),
)
Result: Expected result on my component
Upvotes: 47
Reputation: 191
I ran into this issue a lot too, and the options builder is very specific about where it wants the size or padding to be placed in the hierarchy. Here's my working example:
optionsViewBuilder: (context, onAutoCompleteSelect, options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
color: Theme.of(context).primaryColorLight,
elevation: 4.0,
// size works, when placed here below the Material widget
child: Container(
// I have the text field wrapped in a container with
// EdgeInsets.all(20) so subtract 40 from the width for the width
// of the text box. You could also just use a padding widget
// with EdgeInsets.only(right: 20)
width: MediaQuery.of(context).size.width - 40,
child: ListView.separated(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
itemCount: options.length,
separatorBuilder: (context, i) {
return Divider();
},
itemBuilder: (BuildContext context, int index) {
// some child here
},
)
),
)
);
}
Upvotes: 18
Reputation: 195
Did you try to put the whole Autocomplete widget in a sized box? If the sizing widgets are not working this probably maybe a bug.
Upvotes: 0