如何调整TypeAheadFormField中复制粘贴工具提示的对齐方式
Hey there! I’ve dealt with this exact annoying tooltip alignment bug in TypeAheadFormField when working with multiline text and interactive selection before. Let’s walk through why it happens and how to fix it.
Why This Happens
The core problem is that the TypeAhead widget’s overlay positioning logic doesn’t account for external scrollable parents (like your SingleChildScrollView) or the dynamic height of a multiline TextField. When you select text, the tooltip calculates its position based on the original widget bounds instead of the visible viewport after scrolling, which makes it jump to the top of the screen.
Solution 1: Custom Text Selection Controls to Adjust Tooltip Position
We can create a custom TextSelectionControls that recalculates the tooltip position relative to the selected text, rather than relying on the default widget bounds. This ensures the tooltip stays anchored to your selected text no matter where it is in the scrollable area.
First, define the custom controls:
class CustomTextSelectionControls extends MaterialTextSelectionControls { @override Widget buildToolbar( BuildContext context, Rect globalEditableRegion, double textLineHeight, Offset selectionMidpoint, List<TextSelectionPoint> endpoints, TextSelectionDelegate delegate, ClipboardStatusNotifier? clipboardStatus, Offset? lastSecondaryTapDownPosition, ) { // Adjust the tooltip offset to sit just below the selected text final Offset adjustedOffset = Offset( selectionMidpoint.dx, selectionMidpoint.dy + textLineHeight, ); return super.buildToolbar( context, globalEditableRegion, textLineHeight, adjustedOffset, endpoints, delegate, clipboardStatus, lastSecondaryTapDownPosition, ); } }
Then add it to your TextFieldConfiguration:
textFieldConfiguration: TextFieldConfiguration( enableInteractiveSelection: true, controller: txtController, focusNode: txtFocus, textInputAction: TextInputAction.newline, keyboardType: TextInputType.multiline, maxLines: null, // Attach our custom selection controls selectionControls: CustomTextSelectionControls(), decoration: InputDecoration.collapsed( hintText: "Type a message", ), ),
Solution 2: Simplify the Widget Tree
Your SingleChildScrollView is redundant here because the multiline TextField already supports internal scrolling when constrained to a max height. Removing it eliminates the position mismatch that causes the tooltip to jump.
Here’s the revised widget tree:
Expanded( child: Container( child: ConstrainedBox( constraints: BoxConstraints( minHeight: 25, maxHeight: 100.0, ), // Remove the external SingleChildScrollView child: Padding( padding: const EdgeInsets.only(left: 5, bottom: 3), child: TypeAheadFormField( direction: AxisDirection.up, textFieldConfiguration: TextFieldConfiguration( enableInteractiveSelection: true, controller: txtController, focusNode: txtFocus, textInputAction: TextInputAction.newline, keyboardType: TextInputType.multiline, maxLines: null, decoration: InputDecoration.collapsed( hintText: "Type a message", ), ), validator: (value) { if (value.isEmpty) { return 'Please enter the message'; } return null; }, // Don't forget to add required TypeAhead callbacks suggestionsCallback: (pattern) async { // Replace with your actual suggestions logic return []; }, itemBuilder: (context, suggestion) { return ListTile(title: Text(suggestion.toString())); }, onSuggestionSelected: (suggestion) { txtController.text = suggestion.toString(); }, ), ), ), ), )
Which Solution Should You Use?
- Use Solution 1 if you absolutely need the external
SingleChildScrollView(for example, if you have other widgets inside it alongside the TextField). - Use Solution 2 for a cleaner, more performant fix—it removes unnecessary widget nesting and leverages the TextField’s built-in scrolling capabilities.
Either approach should resolve the tooltip alignment issue and keep your copy/paste controls anchored to the selected text.
内容的提问来源于stack exchange,提问作者VigneshK

