Skip to content

Commit 010c8a8

Browse files
Revert "Autocomplete Options Width" (flutter#161666)
Reverts flutter#143249 This broke some Google tests sadly - see b/390128156
1 parent fd2b563 commit 010c8a8

File tree

4 files changed

+241
-1666
lines changed

4 files changed

+241
-1666
lines changed

packages/flutter/lib/src/material/autocomplete.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ class Autocomplete<T extends Object> extends StatelessWidget {
134134
onSelected: onSelected,
135135
options: options,
136136
openDirection: optionsViewOpenDirection,
137-
optionsMaxHeight: optionsMaxHeight,
137+
maxOptionsHeight: optionsMaxHeight,
138138
);
139139
},
140140
onSelected: onSelected,
@@ -176,7 +176,7 @@ class _AutocompleteOptions<T extends Object> extends StatelessWidget {
176176
required this.onSelected,
177177
required this.openDirection,
178178
required this.options,
179-
required this.optionsMaxHeight,
179+
required this.maxOptionsHeight,
180180
});
181181

182182
final AutocompleteOptionToString<T> displayStringForOption;
@@ -185,7 +185,7 @@ class _AutocompleteOptions<T extends Object> extends StatelessWidget {
185185
final OptionsViewOpenDirection openDirection;
186186

187187
final Iterable<T> options;
188-
final double optionsMaxHeight;
188+
final double maxOptionsHeight;
189189

190190
@override
191191
Widget build(BuildContext context) {
@@ -198,7 +198,7 @@ class _AutocompleteOptions<T extends Object> extends StatelessWidget {
198198
child: Material(
199199
elevation: 4.0,
200200
child: ConstrainedBox(
201-
constraints: BoxConstraints(maxHeight: optionsMaxHeight),
201+
constraints: BoxConstraints(maxHeight: maxOptionsHeight),
202202
child: ListView.builder(
203203
padding: EdgeInsets.zero,
204204
shrinkWrap: true,

packages/flutter/lib/src/widgets/autocomplete.dart

Lines changed: 37 additions & 239 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,18 @@
66
library;
77

88
import 'dart:async';
9-
import 'dart:math' show max, min;
109

11-
import 'package:flutter/rendering.dart';
12-
import 'package:flutter/scheduler.dart';
1310
import 'package:flutter/services.dart';
1411

1512
import 'actions.dart';
1613
import 'basic.dart';
17-
import 'constants.dart';
1814
import 'editable_text.dart';
1915
import 'focus_manager.dart';
2016
import 'framework.dart';
2117
import 'inherited_notifier.dart';
22-
import 'layout_builder.dart';
2318
import 'overlay.dart';
2419
import 'shortcuts.dart';
2520
import 'tap_region.dart';
26-
import 'value_listenable_builder.dart';
2721

2822
// Examples can assume:
2923
// late BuildContext context;
@@ -219,10 +213,10 @@ class RawAutocomplete<T extends Object> extends StatefulWidget {
219213
/// {@template flutter.widgets.RawAutocomplete.optionsViewBuilder}
220214
/// Builds the selectable options widgets from a list of options objects.
221215
///
222-
/// The options are displayed floating below or above the field inside of an
223-
/// [Overlay], not at the same place in the widget tree as [RawAutocomplete].
224-
/// To control whether it opens upward or downward, use
225-
/// [optionsViewOpenDirection].
216+
/// The options are displayed floating below or above the field using a
217+
/// [CompositedTransformFollower] inside of an [Overlay], not at the same
218+
/// place in the widget tree as [RawAutocomplete]. To control whether it opens
219+
/// upward or downward, use [optionsViewOpenDirection].
226220
///
227221
/// In order to track which item is highlighted by keyboard navigation, the
228222
/// resulting options will be wrapped in an inherited
@@ -313,10 +307,6 @@ class RawAutocomplete<T extends Object> extends StatefulWidget {
313307
class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>> {
314308
final GlobalKey _fieldKey = GlobalKey();
315309
final LayerLink _optionsLayerLink = LayerLink();
316-
317-
/// The box constraints that the field was last built with.
318-
final ValueNotifier<BoxConstraints?> _fieldBoxConstraints = ValueNotifier<BoxConstraints?>(null);
319-
320310
final OverlayPortalController _optionsViewController = OverlayPortalController(
321311
debugLabel: '_RawAutocompleteState',
322312
);
@@ -449,22 +439,30 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
449439
}
450440

451441
Widget _buildOptionsView(BuildContext context) {
452-
return ValueListenableBuilder<BoxConstraints?>(
453-
valueListenable: _fieldBoxConstraints,
454-
builder: (BuildContext context, BoxConstraints? constraints, Widget? child) {
455-
return _RawAutocompleteOptions(
456-
fieldKey: _fieldKey,
457-
optionsLayerLink: _optionsLayerLink,
458-
optionsViewOpenDirection: widget.optionsViewOpenDirection,
459-
overlayContext: context,
460-
textDirection: Directionality.maybeOf(context),
442+
final TextDirection textDirection = Directionality.of(context);
443+
final Alignment followerAlignment = switch (widget.optionsViewOpenDirection) {
444+
OptionsViewOpenDirection.up => AlignmentDirectional.bottomStart,
445+
OptionsViewOpenDirection.down => AlignmentDirectional.topStart,
446+
}.resolve(textDirection);
447+
final Alignment targetAnchor = switch (widget.optionsViewOpenDirection) {
448+
OptionsViewOpenDirection.up => AlignmentDirectional.topStart,
449+
OptionsViewOpenDirection.down => AlignmentDirectional.bottomStart,
450+
}.resolve(textDirection);
451+
452+
return CompositedTransformFollower(
453+
link: _optionsLayerLink,
454+
showWhenUnlinked: false,
455+
targetAnchor: targetAnchor,
456+
followerAnchor: followerAlignment,
457+
child: TextFieldTapRegion(
458+
child: AutocompleteHighlightedOption(
461459
highlightIndexNotifier: _highlightedOptionIndex,
462-
fieldConstraints: _fieldBoxConstraints.value!,
463-
builder: (BuildContext context) {
464-
return widget.optionsViewBuilder(context, _select, _options);
465-
},
466-
);
467-
},
460+
child: Builder(
461+
builder:
462+
(BuildContext context) => widget.optionsViewBuilder(context, _select, _options),
463+
),
464+
),
465+
),
468466
);
469467
}
470468

@@ -506,7 +504,6 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
506504
widget.focusNode?.removeListener(_updateOptionsViewVisibility);
507505
_internalFocusNode?.dispose();
508506
_highlightedOptionIndex.dispose();
509-
_fieldBoxConstraints.dispose();
510507
super.dispose();
511508
}
512509

@@ -520,224 +517,25 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
520517
_onFieldSubmitted,
521518
) ??
522519
const SizedBox.shrink();
523-
return LayoutBuilder(
524-
builder: (BuildContext context, BoxConstraints constraints) {
525-
// TODO(victorsanni): Also track the width of the field box so that the
526-
// options view maintains the same width as the field if its width
527-
// changes but its constraints remain unchanged.
528-
_fieldBoxConstraints.value = constraints;
529-
return OverlayPortal.targetsRootOverlay(
520+
return OverlayPortal.targetsRootOverlay(
521+
controller: _optionsViewController,
522+
overlayChildBuilder: _buildOptionsView,
523+
child: TextFieldTapRegion(
524+
child: SizedBox(
530525
key: _fieldKey,
531-
controller: _optionsViewController,
532-
overlayChildBuilder: _buildOptionsView,
533-
child: TextFieldTapRegion(
534-
child: Shortcuts(
535-
shortcuts: _shortcuts,
536-
child: Actions(
537-
actions: _actionMap,
538-
child: CompositedTransformTarget(link: _optionsLayerLink, child: fieldView),
539-
),
526+
child: Shortcuts(
527+
shortcuts: _shortcuts,
528+
child: Actions(
529+
actions: _actionMap,
530+
child: CompositedTransformTarget(link: _optionsLayerLink, child: fieldView),
540531
),
541532
),
542-
);
543-
},
544-
);
545-
}
546-
}
547-
548-
class _RawAutocompleteOptions extends StatefulWidget {
549-
const _RawAutocompleteOptions({
550-
required this.fieldKey,
551-
required this.optionsLayerLink,
552-
required this.optionsViewOpenDirection,
553-
required this.overlayContext,
554-
required this.textDirection,
555-
required this.highlightIndexNotifier,
556-
required this.builder,
557-
required this.fieldConstraints,
558-
});
559-
560-
final WidgetBuilder builder;
561-
final GlobalKey fieldKey;
562-
563-
final LayerLink optionsLayerLink;
564-
final OptionsViewOpenDirection optionsViewOpenDirection;
565-
final BuildContext overlayContext;
566-
final TextDirection? textDirection;
567-
final ValueNotifier<int> highlightIndexNotifier;
568-
final BoxConstraints fieldConstraints;
569-
570-
@override
571-
State<_RawAutocompleteOptions> createState() => _RawAutocompleteOptionsState();
572-
}
573-
574-
class _RawAutocompleteOptionsState extends State<_RawAutocompleteOptions> {
575-
VoidCallback? removeCompositionCallback;
576-
Offset fieldOffset = Offset.zero;
577-
578-
// Get the field offset if the field's position changes when its layer tree
579-
// is composited, which occurs for example if the field is in a scroll view.
580-
Offset _getFieldOffset() {
581-
final RenderBox? fieldRenderBox =
582-
widget.fieldKey.currentContext?.findRenderObject() as RenderBox?;
583-
final RenderBox? overlay =
584-
Overlay.of(widget.overlayContext).context.findRenderObject() as RenderBox?;
585-
return fieldRenderBox?.localToGlobal(Offset.zero, ancestor: overlay) ?? Offset.zero;
586-
}
587-
588-
void _onLeaderComposition(Layer leaderLayer) {
589-
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
590-
if (!mounted) {
591-
return;
592-
}
593-
final Offset nextFieldOffset = _getFieldOffset();
594-
if (nextFieldOffset != fieldOffset) {
595-
setState(() {
596-
fieldOffset = nextFieldOffset;
597-
});
598-
}
599-
});
600-
}
601-
602-
@override
603-
void initState() {
604-
super.initState();
605-
removeCompositionCallback = widget.optionsLayerLink.leader?.addCompositionCallback(
606-
_onLeaderComposition,
607-
);
608-
}
609-
610-
@override
611-
void didUpdateWidget(_RawAutocompleteOptions oldWidget) {
612-
super.didUpdateWidget(oldWidget);
613-
if (widget.optionsLayerLink.leader != oldWidget.optionsLayerLink.leader) {
614-
removeCompositionCallback?.call();
615-
removeCompositionCallback = widget.optionsLayerLink.leader?.addCompositionCallback(
616-
_onLeaderComposition,
617-
);
618-
}
619-
}
620-
621-
@override
622-
void dispose() {
623-
removeCompositionCallback?.call();
624-
super.dispose();
625-
}
626-
627-
@override
628-
Widget build(BuildContext context) {
629-
return CompositedTransformFollower(
630-
link: widget.optionsLayerLink,
631-
followerAnchor: switch (widget.optionsViewOpenDirection) {
632-
OptionsViewOpenDirection.up => Alignment.bottomLeft,
633-
OptionsViewOpenDirection.down => Alignment.topLeft,
634-
},
635-
// When the field goes offscreen, don't show the options.
636-
showWhenUnlinked: false,
637-
child: CustomSingleChildLayout(
638-
delegate: _RawAutocompleteOptionsLayoutDelegate(
639-
layerLink: widget.optionsLayerLink,
640-
fieldOffset: fieldOffset,
641-
optionsViewOpenDirection: widget.optionsViewOpenDirection,
642-
textDirection: Directionality.of(context),
643-
fieldConstraints: widget.fieldConstraints,
644-
),
645-
child: TextFieldTapRegion(
646-
child: AutocompleteHighlightedOption(
647-
highlightIndexNotifier: widget.highlightIndexNotifier,
648-
// optionsViewBuilder must be able to look up
649-
// AutocompleteHighlightedOption in its context.
650-
child: Builder(builder: widget.builder),
651-
),
652533
),
653534
),
654535
);
655536
}
656537
}
657538

658-
/// Positions the options view.
659-
class _RawAutocompleteOptionsLayoutDelegate extends SingleChildLayoutDelegate {
660-
_RawAutocompleteOptionsLayoutDelegate({
661-
required this.layerLink,
662-
required this.fieldOffset,
663-
required this.optionsViewOpenDirection,
664-
required this.textDirection,
665-
required this.fieldConstraints,
666-
}) : assert(layerLink.leaderSize != null);
667-
668-
/// Links the options in [RawAutocomplete.optionsViewBuilder] to the field in
669-
/// [RawAutocomplete.fieldViewBuilder].
670-
final LayerLink layerLink;
671-
672-
/// The position of the field in [RawAutocomplete.fieldViewBuilder].
673-
final Offset fieldOffset;
674-
675-
/// A direction in which to open the options view overlay.
676-
final OptionsViewOpenDirection optionsViewOpenDirection;
677-
678-
/// The [TextDirection] of this part of the widget tree.
679-
final TextDirection textDirection;
680-
681-
/// The [BoxConstraints] for the field in [RawAutocomplete.fieldViewBuilder].
682-
final BoxConstraints fieldConstraints;
683-
684-
// A big enough height for about one item in the default
685-
// Autocomplete.optionsViewBuilder. The assumption is that the user likely
686-
// wants the list of options to move to stay on the screen rather than get any
687-
// smaller than this. Allows Autocomplete to work when it has very little
688-
// screen height available (as in b/317115348) by positioning itself on top of
689-
// the field, while in other cases to size itself based on the height under
690-
// the field.
691-
static const double _kMinUsableHeight = kMinInteractiveDimension;
692-
693-
// Limits the child to the space above/below the field, with a minimum, and
694-
// with the same maxWidth constraint as the field has.
695-
@override
696-
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
697-
final Size fieldSize = layerLink.leaderSize!;
698-
return BoxConstraints(
699-
// The field width may be zero if this is a split RawAutocomplete with no
700-
// field of its own. In that case, don't change the constraints width.
701-
maxWidth: fieldSize.width == 0.0 ? constraints.maxWidth : fieldSize.width,
702-
maxHeight: max(_kMinUsableHeight, switch (optionsViewOpenDirection) {
703-
OptionsViewOpenDirection.down => constraints.maxHeight - fieldOffset.dy - fieldSize.height,
704-
OptionsViewOpenDirection.up => fieldOffset.dy,
705-
}),
706-
);
707-
}
708-
709-
// Positions the child above/below the field and aligned with the left/right
710-
// side based on text direction.
711-
@override
712-
Offset getPositionForChild(Size size, Size childSize) {
713-
final Size fieldSize = layerLink.leaderSize!;
714-
final double dx = switch (textDirection) {
715-
TextDirection.ltr => 0.0,
716-
TextDirection.rtl => fieldSize.width - childSize.width,
717-
};
718-
final double dy = switch (optionsViewOpenDirection) {
719-
OptionsViewOpenDirection.down => min(
720-
fieldSize.height,
721-
size.height - childSize.height - fieldOffset.dy,
722-
),
723-
OptionsViewOpenDirection.up => size.height - min(childSize.height, fieldOffset.dy),
724-
};
725-
return Offset(dx, dy);
726-
}
727-
728-
@override
729-
bool shouldRelayout(_RawAutocompleteOptionsLayoutDelegate oldDelegate) {
730-
if (!fieldOffset.isFinite || !layerLink.leaderSize!.isFinite) {
731-
return false;
732-
}
733-
return layerLink != oldDelegate.layerLink ||
734-
fieldOffset != oldDelegate.fieldOffset ||
735-
optionsViewOpenDirection != oldDelegate.optionsViewOpenDirection ||
736-
textDirection != oldDelegate.textDirection ||
737-
fieldConstraints != oldDelegate.fieldConstraints;
738-
}
739-
}
740-
741539
class _AutocompleteCallbackAction<T extends Intent> extends CallbackAction<T> {
742540
_AutocompleteCallbackAction({required super.onInvoke, required this.isEnabledCallback});
743541

packages/flutter/test/material/autocomplete_test.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,6 @@ void main() {
592592

593593
await tester.tap(find.byType(RawAutocomplete<String>));
594594
await tester.enterText(find.byType(RawAutocomplete<String>), 'a');
595-
await tester.pump();
596595
expect(find.text('aa').hitTestable(), findsOneWidget);
597596
});
598597
});

0 commit comments

Comments
 (0)