66library ;
77
88import 'dart:async' ;
9- import 'dart:math' show max, min;
109
11- import 'package:flutter/rendering.dart' ;
12- import 'package:flutter/scheduler.dart' ;
1310import 'package:flutter/services.dart' ;
1411
1512import 'actions.dart' ;
1613import 'basic.dart' ;
17- import 'constants.dart' ;
1814import 'editable_text.dart' ;
1915import 'focus_manager.dart' ;
2016import 'framework.dart' ;
2117import 'inherited_notifier.dart' ;
22- import 'layout_builder.dart' ;
2318import 'overlay.dart' ;
2419import 'shortcuts.dart' ;
2520import '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 {
313307class _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-
741539class _AutocompleteCallbackAction <T extends Intent > extends CallbackAction <T > {
742540 _AutocompleteCallbackAction ({required super .onInvoke, required this .isEnabledCallback});
743541
0 commit comments