0

So I have this unique requirement as shown in the picture below, where the blue texts are user inputs. I have been doing my own research regarding this and have found a few viable solutions, out of which a custom TextEditingController seems most promising. I haven't started implementation but out of the top of my head, I think I might run into some problems with the touch and cursor controls later on.

Now my question is, Is there a better way that I might have overlooked? Is there a way to gracefully handle which areas are touchable, handle the cursor when deleting, and moving to the next focus?

enter image description here

Stefano Amorelli
  • 4,553
  • 3
  • 14
  • 30
Ritesh Shakya
  • 575
  • 5
  • 17
  • Our thinking could be wrong/right, but after applying our thoughts into code-structure, we face many issues. Then we look and ask by showing errors that we get from applied code. Can you apply your thoughts and share the issue with code-snippet? – Md. Yeasin Sheikh Oct 28 '21 at 12:38
  • You might try to use `Wrap` widget with `Text` and `TextField` as children – Andrey Gordeev Oct 28 '21 at 12:48
  • @YeasinSheikh Currently in the drafting stage and collecting valid solutions or possible steps before diving in. I would definitely be posting updates. As for now, I have gathered that `TextEditingController` can definitely be used for creating individual spans as I've gone through some of the libraries that do something similar. And with the help of `TextSelectionControls` and `TextEditingValue`, it definitely seems feasible. – Ritesh Shakya Oct 28 '21 at 14:05

2 Answers2

2

As suggested by @Andrey Gordeev, we can use a Wrap Widget with a children defined as follows:

Wrap(
  alignment: WrapAlignment.center,
  runAlignment: WrapAlignment.center,
  children: [
    const Text('Hala, we\'re '),
    SizedBox(
      width: 50,
      height: 20,
      child: TextField(
        controller: TextEditingController.fromValue(
          const TextEditingValue(text: 'aryab'),
        ),
      ),
    ),
    const Text(' You? I\'m '),
    SizedBox(
      width: 150,
      height: 20,
      child: TextField(
        controller: TextEditingController.fromValue(
          const TextEditingValue(text: 'Someone Awesome'),
        ),
      ),
    ),
  ],
)

This renders the result of the screenshot attached:

screenshot

A full example snippet can be found below:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: 
        Wrap(
          alignment: WrapAlignment.center,
          runAlignment: WrapAlignment.center,
          children: [
            const Text('Hala, we\'re '),
            SizedBox(
              width: 50,
              height: 20,
              child: TextField(
                controller: TextEditingController.fromValue(
                  const TextEditingValue(text: 'aryab'),
                ),
              ),
            ),
            const Text(' You? I\'m '),
            SizedBox(
              width: 150,
              height: 20,
              child: TextField(
                controller: TextEditingController.fromValue(
                  const TextEditingValue(text: 'Someone Awesome'),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Stefano Amorelli
  • 4,553
  • 3
  • 14
  • 30
  • Thanks for the prompt response, definitely a much cleaner implementation. Will run this through the team, for now, I can foresee that the TextField won't flow to the next line breaking it up, which might be a point of rejection. This can definitely be something I can fall back to if the `TextEditingController` doesn't go as planned. – Ritesh Shakya Oct 28 '21 at 13:59
2

While there are two types of widgets, <plainText,inputText> can be placed here, and we need to concern about retrieving full text from the form/text.

Creating a map for the widgets will be a good choice, I think.

  final Map<String, String?> _textsInForm = {
    "Hala, we're": null,
    "aryab": "groupName",
    ". You? I'm": null,
    "Someone Awysome": "myself",
    ",a": null,
    "Driver": "job",
  };

As you can see, I'm using key as main value and value as nullable string. If we find null-string, it will be simply Text widget, else TextFiled.

I'm using RichText to handle this situation.

We will create list List<InlineSpan> for RichText and List<TextEditingController> to handle input text.

I'm using StatelessWidget, while having stateFullWidget handle creation on initState.

There is one issue, it is taking minimum hint text width. You can try with passing text on TextEditingController instead.

enter image description here

import 'package:flutter/material.dart';

class SomeTextEditable extends StatelessWidget {
  SomeTextEditable({Key? key}) : super(key: key);

  final Map<String, String?> _textsInForm = {
    "Hala, we're": null,
    "aryab": "groupName",
    ". You? I'm": null,
    "Someone Awysome": "myself",
    ",a": null,
    "Driver": "job",
  };

  final TextStyle _hintTextStyle = const TextStyle(
    color: Colors.grey,
  );

  final TextStyle _textFiledStyle = const TextStyle(
    color: Colors.blue,
  );

  WidgetSpan _textFiled(TextEditingController controller, String hint) =>
      WidgetSpan(
        alignment: PlaceholderAlignment.middle, // set middle according to texts
        child: IntrinsicWidth(
          //flexiable size
          child: TextField(
            style: _textFiledStyle,
            controller: controller,
            decoration: InputDecoration(
              hintStyle: _hintTextStyle,
              hintText: hint,
              border: InputBorder.none,
            ),
          ),
        ),
      );

  @override
  Widget build(BuildContext context) {
    List<TextEditingController> controllers = [];

    List<InlineSpan> textSpans = [];

    _textsInForm.forEach((key, value) {
      if (value != null) {
        TextEditingController controller = TextEditingController();

        controllers.add(controller);
        textSpans.add(_textFiled(controller, key));
      } else {
        textSpans.add(TextSpan(text: "$key "));
      }
    });

    return Scaffold(
      body: Column(
        children: [
          RichText(
            text: TextSpan(children: textSpans),
          ),
          ElevatedButton(
            onPressed: () {
              String result = "";
              int i = 0;

              _textsInForm.forEach((key, value) {
                if (value != null) {
                  final textFromIcontroller = controllers[i++].text;
                  result += "$textFromIcontroller ";
                } else {
                  result += "$key ";
                }
              });

              print(result);
            },
            child: Text("Get Text"),
          ),
        ],
      ),
    );
  }
}

Md. Yeasin Sheikh
  • 54,221
  • 7
  • 29
  • 56