skip to Main Content

I’m currently facing an issue with scrolling text selection in a TextFormField. I have a custom TextAreaFormField widget with a fixed height and a ScrollView for handling longer texts. However, when I select and move the text, the selection can go outside the text field’s border and become hidden.

video

The issue arises when I select and move the text within the TextFormField. The text selection can go out of the visible area, and I need the TextFormField to scroll appropriately to keep the selected text within the visible region.

How to make the TextFormField scroll when selecting and moving text, so that the selected text remains within the visible area? Any guidance or code examples would be greatly appreciated. Thank you!

Here’s my code:

class _CustomTextAreaFormFieldContent extends StatefulWidget {
  final String? value;
  final ValueChanged<String> onChanged;
  final EdgeInsets scrollPadding;
  final double? height;
  final bool readOnly;

  const _CustomTextAreaFormFieldContent({
    required this.value,
    required this.onChanged,
    required this.scrollPadding,
    required this.height,
    this.readOnly = false,
    Key? key,
  }) : super(key: key);

  @override
  _CustomTextAreaFormFieldContentState createState() =>
      _CustomTextAreaFormFieldContentState();
}

class _CustomTextAreaFormFieldContentState
    extends State<_CustomTextAreaFormFieldContent> {
  final _scrollController = ScrollController();
  final _textController = TextEditingController();

  @override
  void initState() {
    super.initState();

    _textController.text = widget.value ?? '';
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const SizedBox(
          height: 12,
        ),
        const Div(),
        Scrollbar(
          controller: _scrollController,
          thumbVisibility: true,
          child: SizedBox(
            height: widget.height == null ? null : widget.height! - 121,
            child: SingleChildScrollView(
              controller: _scrollController,
              child: Column(
                children: [
                  const SizedBox(
                    height: 16,
                  ),
                  TextFormField(
                    controller: _textController,
                    style: Theme.of(context).textTheme.bodyLarge,
                    scrollPadding: widget.scrollPadding,
                    decoration: const InputDecoration.collapsed(
                      hintText: '',
                    ),
                    readOnly: widget.readOnly,
                    onChanged: (value) => widget.onChanged(
                      value.trim(),
                    ),
                    minLines: 6,
                    maxLines: null,
                    keyboardType: TextInputType.multiline,
                  ),
                ],
              ),
            ),
          ),
        ),
      ],
    );
  }
}

2

Answers


  1. To address the issue of scrolling the TextFormField’s content while maintaining text selection visibility, you’ll need to update the scroll position in sync with the text selection changes.

    class _CustomTextAreaFormFieldContentState
        extends State<_CustomTextAreaFormFieldContent> {
      final _scrollController = ScrollController();
      final _textController = TextEditingController();
      late TextSelection _currentSelection;
    
      @override
      void initState() {
        super.initState();
    
        _textController.text = widget.value ?? '';
        _currentSelection = _textController.selection;
      }
    
      void _updateSelection(TextSelection newSelection) {
        setState(() {
          _currentSelection = newSelection;
        });
    
        
        final scrollMaxExtent = _scrollController.position.maxScrollExtent;
        final visibleScrollExtent = _scrollController.position.viewportDimension;
    
        
        final selectionOffset = _textController.selection.extentOffset;
        final selectionRect = _textController.selection.extent;
        final selectionTopOffset = selectionRect.top;
        final selectionBottomOffset = selectionRect.bottom;
    
        
        if (selectionTopOffset < _scrollController.offset) {
          _scrollController.jumpTo(selectionTopOffset);
        } else if (selectionBottomOffset > _scrollController.offset + visibleScrollExtent) {
          _scrollController.jumpTo(selectionBottomOffset - visibleScrollExtent);
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const SizedBox(
              height: 12,
            ),
            const Div(),
            Scrollbar(
              controller: _scrollController,
              thumbVisibility: true,
              child: SizedBox(
                height: widget.height == null ? null : widget.height! - 121,
                child: SingleChildScrollView(
                  controller: _scrollController,
                  child: Column(
                    children: [
                      const SizedBox(
                        height: 16,
                      ),
                      TextFormField(
                        controller: _textController,
                        style: Theme.of(context).textTheme.bodyLarge,
                        scrollPadding: widget.scrollPadding,
                        decoration: const InputDecoration.collapsed(
                          hintText: '',
                        ),
                        readOnly: widget.readOnly,
                        onChanged: (value) {
                          widget.onChanged(value.trim());
                          _updateSelection(_textController.selection);
                        },
                        minLines: 6,
                        maxLines: null,
                        keyboardType: TextInputType.multiline,
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ],
        );
      }
    }
    
    Login or Signup to reply.
  2. Here is my Main.dart file code i have used TextField

    main.dart

    import 'package:flutter/material.dart';
    
    const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      final ScrollController _scrollController = ScrollController();
      final TextEditingController _textController = TextEditingController();
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData.dark().copyWith(
            scaffoldBackgroundColor: darkBlue,
          ),
          debugShowCheckedModeBanner: false,
          home: Scaffold(
            body: Center(
              child: MyWidget(
                scrollController: _scrollController,
                textController: _textController,
              ),
            ),
          ),
        );
      }
    }
    
    class MyWidget extends StatefulWidget {
      final ScrollController scrollController;
      final TextEditingController textController;
    
      const MyWidget({
        required this.scrollController,
        required this.textController,
      }) : super();
    
      @override
      State<MyWidget> createState() => _MyWidgetState();
    }
    
    class _MyWidgetState extends State<MyWidget> {
      @override
      void dispose() {
        widget.scrollController.dispose();
        widget.textController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const SizedBox(
              height: 12,
            ),
            const Divider(),
            Scrollbar(
              controller: widget.scrollController,
              thumbVisibility: true,
              child: SingleChildScrollView(
                controller: widget.scrollController,
                child: Column(
                  children: [
                    const SizedBox(
                      height: 16,
                    ),
                    TextField(
                      controller: widget.textController,
                      style: TextStyle(
                        color: Colors.black, // Set text color to black
                      ),
                      decoration: const InputDecoration(
                        hintText: '', filled: true,
                        fillColor: Colors.white, // Set background color to white
                        border: OutlineInputBorder(borderSide: BorderSide.none),
                      ),
                      minLines: 6,
                      maxLines: 8,
                      keyboardType: TextInputType.multiline,
                      onChanged: (value) {},
                    ),
                  ],
                ),
              ),
            ),
          ],
        );
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search