@@ -268,9 +268,9 @@ class TextSelectionOverlay {
268268 assert (handlesVisible != null ),
269269 _handlesVisible = handlesVisible,
270270 _value = value {
271- renderObject.selectionStartInViewport.addListener (_updateHandleVisibilities );
272- renderObject.selectionEndInViewport.addListener (_updateHandleVisibilities );
273- _updateHandleVisibilities ();
271+ renderObject.selectionStartInViewport.addListener (_updateTextSelectionOverlayVisibilities );
272+ renderObject.selectionEndInViewport.addListener (_updateTextSelectionOverlayVisibilities );
273+ _updateTextSelectionOverlayVisibilities ();
274274 _selectionOverlay = SelectionOverlay (
275275 context: context,
276276 debugRequiredFor: debugRequiredFor,
@@ -285,6 +285,7 @@ class TextSelectionOverlay {
285285 lineHeightAtEnd: 0.0 ,
286286 onEndHandleDragStart: _handleSelectionEndHandleDragStart,
287287 onEndHandleDragUpdate: _handleSelectionEndHandleDragUpdate,
288+ toolbarVisible: _effectiveToolbarVisibility,
288289 selectionEndPoints: const < TextSelectionPoint > [],
289290 selectionControls: selectionControls,
290291 selectionDelegate: selectionDelegate,
@@ -321,9 +322,11 @@ class TextSelectionOverlay {
321322
322323 final ValueNotifier <bool > _effectiveStartHandleVisibility = ValueNotifier <bool >(false );
323324 final ValueNotifier <bool > _effectiveEndHandleVisibility = ValueNotifier <bool >(false );
324- void _updateHandleVisibilities () {
325+ final ValueNotifier <bool > _effectiveToolbarVisibility = ValueNotifier <bool >(false );
326+ void _updateTextSelectionOverlayVisibilities () {
325327 _effectiveStartHandleVisibility.value = _handlesVisible && renderObject.selectionStartInViewport.value;
326328 _effectiveEndHandleVisibility.value = _handlesVisible && renderObject.selectionEndInViewport.value;
329+ _effectiveToolbarVisibility.value = renderObject.selectionStartInViewport.value || renderObject.selectionEndInViewport.value;
327330 }
328331
329332 /// Whether selection handles are visible.
@@ -339,7 +342,7 @@ class TextSelectionOverlay {
339342 if (_handlesVisible == visible)
340343 return ;
341344 _handlesVisible = visible;
342- _updateHandleVisibilities ();
345+ _updateTextSelectionOverlayVisibilities ();
343346 }
344347
345348 /// {@macro flutter.widgets.SelectionOverlay.showHandles}
@@ -413,9 +416,12 @@ class TextSelectionOverlay {
413416
414417 /// {@macro flutter.widgets.SelectionOverlay.dispose}
415418 void dispose () {
416- renderObject.selectionStartInViewport.removeListener (_updateHandleVisibilities);
417- renderObject.selectionEndInViewport.removeListener (_updateHandleVisibilities);
418419 _selectionOverlay.dispose ();
420+ renderObject.selectionStartInViewport.removeListener (_updateTextSelectionOverlayVisibilities);
421+ renderObject.selectionEndInViewport.removeListener (_updateTextSelectionOverlayVisibilities);
422+ _effectiveToolbarVisibility.dispose ();
423+ _effectiveStartHandleVisibility.dispose ();
424+ _effectiveEndHandleVisibility.dispose ();
419425 }
420426
421427 double _getStartGlyphHeight () {
@@ -562,6 +568,7 @@ class SelectionOverlay {
562568 this .onEndHandleDragStart,
563569 this .onEndHandleDragUpdate,
564570 this .onEndHandleDragEnd,
571+ this .toolbarVisible,
565572 required List <TextSelectionPoint > selectionEndPoints,
566573 required this .selectionControls,
567574 required this .selectionDelegate,
@@ -585,7 +592,6 @@ class SelectionOverlay {
585592 'Usually the Navigator created by WidgetsApp provides the overlay. Perhaps your '
586593 'app content was created above the Navigator with the WidgetsApp builder parameter.' ,
587594 );
588- _toolbarController = AnimationController (duration: fadeDuration, vsync: overlay! );
589595 }
590596
591597 /// The context in which the selection handles should appear.
@@ -682,6 +688,14 @@ class SelectionOverlay {
682688 /// handles.
683689 final ValueChanged <DragEndDetails >? onEndHandleDragEnd;
684690
691+ /// Whether the toolbar is visible.
692+ ///
693+ /// If the value changes, the toolbar uses [FadeTransition] to transition
694+ /// itself on and off the screen.
695+ ///
696+ /// If this is null the toolbar will always be visible.
697+ final ValueListenable <bool >? toolbarVisible;
698+
685699 /// The text selection positions of selection start and end.
686700 List <TextSelectionPoint > get selectionEndPoints => _selectionEndPoints;
687701 List <TextSelectionPoint > _selectionEndPoints;
@@ -780,9 +794,6 @@ class SelectionOverlay {
780794 /// Controls the fade-in and fade-out animations for the toolbar and handles.
781795 static const Duration fadeDuration = Duration (milliseconds: 150 );
782796
783- late final AnimationController _toolbarController;
784- Animation <double > get _toolbarOpacity => _toolbarController.view;
785-
786797 /// A pair of handles. If this is non-null, there are always 2, though the
787798 /// second is hidden when the selection is collapsed.
788799 List <OverlayEntry >? _handles;
@@ -826,7 +837,6 @@ class SelectionOverlay {
826837 }
827838 _toolbar = OverlayEntry (builder: _buildToolbar);
828839 Overlay .of (context, rootOverlay: true , debugRequiredFor: debugRequiredFor)! .insert (_toolbar! );
829- _toolbarController.forward (from: 0.0 );
830840 }
831841
832842 bool _buildScheduled = false ;
@@ -878,7 +888,6 @@ class SelectionOverlay {
878888 void hideToolbar () {
879889 if (_toolbar == null )
880890 return ;
881- _toolbarController.stop ();
882891 _toolbar? .remove ();
883892 _toolbar = null ;
884893 }
@@ -888,7 +897,6 @@ class SelectionOverlay {
888897 /// {@endtemplate}
889898 void dispose () {
890899 hide ();
891- _toolbarController.dispose ();
892900 }
893901
894902 Widget _buildStartHandle (BuildContext context) {
@@ -967,26 +975,115 @@ class SelectionOverlay {
967975
968976 return Directionality (
969977 textDirection: Directionality .of (this .context),
970- child: FadeTransition (
971- opacity: _toolbarOpacity,
972- child: CompositedTransformFollower (
973- link: toolbarLayerLink,
974- showWhenUnlinked: false ,
975- offset: - editingRegion.topLeft,
976- child: Builder (
977- builder: (BuildContext context) {
978- return selectionControls! .buildToolbar (
979- context,
980- editingRegion,
981- lineHeightAtStart,
982- midpoint,
983- selectionEndPoints,
984- selectionDelegate,
985- clipboardStatus! ,
986- toolbarLocation,
987- );
988- },
989- ),
978+ child: _SelectionToolbarOverlay (
979+ preferredLineHeight: lineHeightAtStart,
980+ toolbarLocation: toolbarLocation,
981+ layerLink: toolbarLayerLink,
982+ editingRegion: editingRegion,
983+ selectionControls: selectionControls,
984+ midpoint: midpoint,
985+ selectionEndpoints: selectionEndPoints,
986+ visibility: toolbarVisible,
987+ selectionDelegate: selectionDelegate,
988+ clipboardStatus: clipboardStatus,
989+ ),
990+ );
991+ }
992+ }
993+
994+ /// This widget represents a selection toolbar.
995+ class _SelectionToolbarOverlay extends StatefulWidget {
996+ /// Creates a toolbar overlay.
997+ const _SelectionToolbarOverlay ({
998+ Key ? key,
999+ required this .preferredLineHeight,
1000+ required this .toolbarLocation,
1001+ required this .layerLink,
1002+ required this .editingRegion,
1003+ required this .selectionControls,
1004+ this .visibility,
1005+ required this .midpoint,
1006+ required this .selectionEndpoints,
1007+ required this .selectionDelegate,
1008+ required this .clipboardStatus,
1009+ }) : super (key: key);
1010+
1011+ final double preferredLineHeight;
1012+ final Offset ? toolbarLocation;
1013+ final LayerLink layerLink;
1014+ final Rect editingRegion;
1015+ final TextSelectionControls ? selectionControls;
1016+ final ValueListenable <bool >? visibility;
1017+ final Offset midpoint;
1018+ final List <TextSelectionPoint > selectionEndpoints;
1019+ final TextSelectionDelegate ? selectionDelegate;
1020+ final ClipboardStatusNotifier ? clipboardStatus;
1021+
1022+ @override
1023+ _SelectionToolbarOverlayState createState () => _SelectionToolbarOverlayState ();
1024+ }
1025+
1026+ class _SelectionToolbarOverlayState extends State <_SelectionToolbarOverlay > with SingleTickerProviderStateMixin {
1027+ late AnimationController _controller;
1028+ Animation <double > get _opacity => _controller.view;
1029+
1030+ @override
1031+ void initState () {
1032+ super .initState ();
1033+
1034+ _controller = AnimationController (duration: SelectionOverlay .fadeDuration, vsync: this );
1035+
1036+ _toolbarVisibilityChanged ();
1037+ widget.visibility? .addListener (_toolbarVisibilityChanged);
1038+ }
1039+
1040+ @override
1041+ void didUpdateWidget (_SelectionToolbarOverlay oldWidget) {
1042+ super .didUpdateWidget (oldWidget);
1043+ if (oldWidget.visibility == widget.visibility) {
1044+ return ;
1045+ }
1046+ oldWidget.visibility? .removeListener (_toolbarVisibilityChanged);
1047+ _toolbarVisibilityChanged ();
1048+ widget.visibility? .addListener (_toolbarVisibilityChanged);
1049+ }
1050+
1051+ @override
1052+ void dispose () {
1053+ widget.visibility? .removeListener (_toolbarVisibilityChanged);
1054+ _controller.dispose ();
1055+ super .dispose ();
1056+ }
1057+
1058+ void _toolbarVisibilityChanged () {
1059+ if (widget.visibility? .value != false ) {
1060+ _controller.forward ();
1061+ } else {
1062+ _controller.reverse ();
1063+ }
1064+ }
1065+
1066+ @override
1067+ Widget build (BuildContext context) {
1068+ return FadeTransition (
1069+ opacity: _opacity,
1070+ child: CompositedTransformFollower (
1071+ link: widget.layerLink,
1072+ showWhenUnlinked: false ,
1073+ offset: - widget.editingRegion.topLeft,
1074+ child: Builder (
1075+ builder: (BuildContext context) {
1076+ return widget.selectionControls! .buildToolbar (
1077+ context,
1078+ widget.editingRegion,
1079+ widget.preferredLineHeight,
1080+ widget.midpoint,
1081+ widget.selectionEndpoints,
1082+ widget.selectionDelegate! ,
1083+ widget.clipboardStatus! ,
1084+ widget.toolbarLocation,
1085+ );
1086+ },
9901087 ),
9911088 ),
9921089 );
0 commit comments