flutterwidgettextfield

Scroll multi-line textfield content to cursor position when assigning textfield value via controller


I'm trying to implement a multi-line textfield whose value can be altered via controller. My usecase is that you can press a button and a set of string will be added to the textfield on current cursor position. The problem is my code worked but the textfield viewport doesn't change, it showed its state as before the string was added to the textfield, not scrolling to the current cursor position.

I'd tried using controller(TextEditingController) and scrollController to manually manipulate the TextField's text value and scroll position. The text was inserted to cursor position beautifully, but the calculated scroll position was no use (I used controller.selection.baseOffset but it appears I have to calculate the selection point with textfield's width to find the line the cursor is at ???). Can someone help guiding me how to achieve that?

FYI: this is how I assign value to textfield

  void insertParam(String text, TextSelection selection) {
    final TextEditingValue value = controller.value;
    final int start = selection.baseOffset;
    int end = selection.extentOffset;
    if (selection.isValid) {
      String newText = '';
      if (value.selection.isCollapsed) {
        if (end > 0) {
          newText += value.text.substring(0, end);
        }
        newText += text;
        if (value.text.length > end) {
          newText += value.text.substring(end, value.text.length);
        }
      } else {
        newText = value.text.replaceRange(start, end, text);
        end = start;
      }
      controller.value = value.copyWith(
        text: newText,
        selection: selection.copyWith(
          baseOffset: end + text.length,
          extentOffset: end + text.length,
        ),
      );
    } else {
      controller.value = TextEditingValue(
        text: text,
        selection: TextSelection.fromPosition(TextPosition(offset: text.length)),
      );
    }
  }

Solution

  • Ok after talking to @pskink comment I reached the solution

    void initState() {
        super.initState();
    
      // other code
    
      SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
        context.findRenderObject()?.visitChildren(_visitor);
      });
    }
    
    void _visitor(RenderObject child) {
        if (child is RenderEditable) {
          setState(() {
            // assign RenderEditable node to widget state
            // make sure you get the correct child, for me there is only one textfield for testing
            reEdt = child;
          });
          return;
        }
        child.visitChildren(_visitor);
      }
    
    
    // call when inserting text and want to scroll to cursor
    void scrollToSelection(TextSelection selection) {
        // find local rect of cursor or starting selection in case of selecting text 
        final localRect = reEdt?.getLocalRectForCaret(TextPosition(offset: selection.baseOffset));
        if (localRect == null) return;
        scrollController.jumpTo(localRect.top);
      }
    

    and don't forget to assign scrollController to TextField