Chris
Chris

Reputation: 2044

How to: add a blank space after every 4 characters when typing in TextFormField

I'm trying to find a way to add a blank space while typing in a TextFormField.

I found a way how to do this a while back but I cannot find it now, no matter what I search for on Google or here on StackOverflow ..

Basically I want to turn the text shown in the textfield from 000011112222333 to 0000 1111 2222 3333

I'm guessing I need to use some RegExp check in the onChanged function of a TextFormField and use it to change the TextEditingController's .text

Upvotes: 7

Views: 10606

Answers (6)

Chaibedraa Ibrahim
Chaibedraa Ibrahim

Reputation: 101

I have used this for inserting space after every 2 characters, you can change it for 4 characters:

class SpaceFormatter extends TextInputFormatter {
  @override
    TextEditingValue formatEditUpdate(
    TextEditingValue oldValue,
    TextEditingValue newValue,
  ) {
  String text = newValue.text.replaceAll(' ', '');
  String newText = '';

  for (int i = 0; i < text.length; i++) {
    if (i > 0 && i % 2 == 0) {
      newText += ' ';
    }
    newText += text[i];
  }

  return TextEditingValue(
    text: newText,
    selection: TextSelection.collapsed(offset: newText.length),
  );
}

}

And then you can use it like this:

inputFormatters: [
  FilteringTextInputFormatter.allow(RegExp(r"[0-9]")),
  SpaceFormatter(),
],

Upvotes: 1

TextFormField(
          inputFormatters: [
            LengthLimitingTextInputFormatter(12),
            TextInputFormatter.withFunction((oldValue, newValue) {
              String left = oldValue.text.substring(
                  0, min(oldValue.selection.start, newValue.selection.end));
              String right =
                  oldValue.text.substring(oldValue.selection.end);
              String inserted = newValue.text
                  .substring(left.length, newValue.selection.end);
              String modLeft = left.replaceAll(RegExp(r'\D'), "");
              String modRight = right.replaceAll(RegExp(r'\D'), "");
              String modInserted = inserted.replaceAll(RegExp(r'\D'), "");
              final regEx = RegExp(r'\d{1,4}');
              String processString = modLeft + modInserted + modRight;
              String updated = regEx
                  .allMatches(processString)
                  .map((e) => e.group(0))
                  .join(" ");
              int cursorPosition = regEx
                  .allMatches(modLeft + modInserted)
                  .map((e) => e.group(0))
                  .join(" ")
                  .length;
              return TextEditingValue(
                  text: updated,
                  selection:
                      TextSelection.collapsed(offset: cursorPosition));
            })
          ],
        )

Upvotes: 0

mezoni
mezoni

Reputation: 11210

This is very easy to do using a parser as a data processor.

import 'package:parser_combinator/parser/choice.dart';
import 'package:parser_combinator/parser/many.dart';
import 'package:parser_combinator/parser/take_while1.dart';
import 'package:parser_combinator/parser/take_while_m_n.dart';
import 'package:parser_combinator/parsing.dart';

void main(List<String> args) {
  for (var i = 0; i < 24; i++) {
    final text = String.fromCharCodes(List.generate(i, (i) => 0x61 + i));
    final newText = convert(text);
    print('$text => $newText');
  }

  const test = ' a bcd   ef  ghi  ';
  final r = convert(test);
  print('$test => $r');
}

String convert(String text) {
  text = text.replaceAll(' ', '');
  const word4 = TakeWhileMN(4, 4, isAnyChar);
  const rest = TakeWhile1(isAnyChar);
  const words = Many(Choice2(word4, rest));
  final r = parseString(words.parse, text);
  return r.join(' ');
}

bool isAnyChar(int c) => true;

Output:

 =>
a => a
ab => ab
abc => abc
abcd => abcd
abcde => abcd e
abcdef => abcd ef
abcdefg => abcd efg
abcdefgh => abcd efgh
abcdefghi => abcd efgh i
abcdefghij => abcd efgh ij
abcdefghijk => abcd efgh ijk
abcdefghijkl => abcd efgh ijkl
abcdefghijklm => abcd efgh ijkl m
abcdefghijklmn => abcd efgh ijkl mn
abcdefghijklmno => abcd efgh ijkl mno
abcdefghijklmnop => abcd efgh ijkl mnop
abcdefghijklmnopq => abcd efgh ijkl mnop q
abcdefghijklmnopqr => abcd efgh ijkl mnop qr
abcdefghijklmnopqrs => abcd efgh ijkl mnop qrs
abcdefghijklmnopqrst => abcd efgh ijkl mnop qrst
abcdefghijklmnopqrstu => abcd efgh ijkl mnop qrst u
abcdefghijklmnopqrstuv => abcd efgh ijkl mnop qrst uv
abcdefghijklmnopqrstuvw => abcd efgh ijkl mnop qrst uvw
 a bcd   ef  ghi   => abcd efgh i

Upvotes: 0

Saeed Ghasemi
Saeed Ghasemi

Reputation: 156

in flutter 2.2 you should use FilteringTextInputFormatter.allow(RegExp(r'[0-9]')) instead of FilteringTextInputFormatter.digitsOnly

Upvotes: -2

brocatz
brocatz

Reputation: 126

Inspired from Dung Ngo's Code

I added cursor tracking so when the user erases from the the TextFormField the cursor doesn't goes back to the beginning of field.

I commented the WhitelistingTextInputFormatter.digitsOnly / FilteringTextInputFormatter.digitsOnly in my pen since I used regular expressions.

Here's the pen https://codepen.io/brocatz/pen/dyNOqVy

@override
TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
    var text = newValue.text;
    text = text.replaceAll(RegExp(r'(\s)|(\D)'), '');

    int offset = newValue.selection.start;
    var subText =
        newValue.text.substring(0, offset).replaceAll(RegExp(r'(\s)|(\D)'), '');
    int realTrimOffset = subText.length;

    // if (newValue.selection.baseOffset == 0) {
    //   return newValue;
    // }

    var buffer = new StringBuffer();
    for (int i = 0; i < text.length; i++) {
      buffer.write(text[i]);
      var nonZeroIndex = i + 1;
      if (nonZeroIndex % 4 == 0 && nonZeroIndex != text.length) {
        buffer.write(
            ' '); // Replace this with anything you want to put after each 4 numbers
      }


      if (nonZeroIndex % 4 == 0 &&
          subText.length == nonZeroIndex &&
          nonZeroIndex > 4) {
        int moveCursorToRigth = nonZeroIndex ~/ 4 - 1;
        realTrimOffset += moveCursorToRigth;
      }

      if (nonZeroIndex % 4 != 0 && subText.length == nonZeroIndex) {
        int moveCursorToRigth = nonZeroIndex ~/ 4;
        realTrimOffset += moveCursorToRigth;
      }
    }

    var string = buffer.toString();
    return newValue.copyWith(
        text: string,
        selection: new TextSelection.collapsed(offset: realTrimOffset));
  }

Upvotes: 5

Dung Ngo
Dung Ngo

Reputation: 1472

I also faced this problem before and luckily found a way to do this somewhere. First create a class that extends TextInputFormatter

customInputFormatter.dart

class CustomInputFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
    var text = newValue.text;

    if (newValue.selection.baseOffset == 0) {
      return newValue;
    }

    var buffer = new StringBuffer();
    for (int i = 0; i < text.length; i++) {
      buffer.write(text[i]);
      var nonZeroIndex = i + 1;
      if (nonZeroIndex % 4 == 0 && nonZeroIndex != text.length) {
        buffer.write(' '); // Replace this with anything you want to put after each 4 numbers
      }
    }

    var string = buffer.toString();
    return newValue.copyWith(
        text: string,
        selection: new TextSelection.collapsed(offset: string.length)
    );
  }
}

And then add it to the list of inputFormatters[] of TextFormField

inputFormatters: [
   FilteringTextInputFormatter.digitsOnly,
   new CustomInputFormatter()
],   

Upvotes: 20

Related Questions