Flutter - How to implement multiline underlined TextField

Viewed 326

I want to implement the following Ui which contains a multiline TextField with underline below every single line. any idea on how I can implement this?

enter image description here

Expanded(
child: Padding(
    padding: const EdgeInsets.symmetric(horizontal: 5.0),
    child: TextField(
        controller: problemItem.titleController,
        decoration: const InputDecoration(
                                border: InputBorder.none),
        keyboardType: TextInputType.multiline,
        minLines: 3,
        maxLines: 100,
        )
    ),
),
3 Answers

You can use a Stack to stack the TextField onto lines. You need to set the expands attribute of the TextField to true in order to make it expand to the full three-line width right from the start.
I implemented a similar TextField to the one you are trying to create:

Stack(
  children: [
    for (int i = 0; i < 3; i++)
      Container(
        width: double.infinity,
        margin: EdgeInsets.only(
          top: 4 + (i + 1) * 28,
          left: 15,
          right: 15,
        ),
        height: 1,
        color: Colors.black,
      ),
    const SizedBox(
      height: 97,
      child: Padding(
        padding: EdgeInsets.symmetric(horizontal: 15),
        child: TextField(
          decoration: InputDecoration(border: InputBorder.none),
          cursorHeight: 22,
          style: TextStyle(
            fontSize: 20.0,
          ),
          keyboardType: TextInputType.multiline,
          expands: true,
          maxLines: null,
        ),
      ),
    ),
  ],
),

Result:
enter image description here

If you want to allocate a Fixed Lines Textfield, you can try the below widget:

style is required because it uses the text height to calculate the line

numberOfLines is the line number of underlines that need to be generated.

class UnderlineTextField extends StatelessWidget {
  const UnderlineTextField({
    required this.style,
    required this.numberOfLines,
    this.controller,
    this.decoration = const InputDecoration(isDense: true),
    this.keyboardType,
    this.maxLines = 1,
    this.minLines,
    Key? key,
  }) : super(key: key);

  final TextEditingController? controller;
  final InputDecoration? decoration;
  final TextInputType? keyboardType;
  final int? maxLines;
  final int? minLines;
  final int lines;
  final TextStyle style;

  @override
  Widget build(BuildContext context) {
    final textHeight = style.height ?? 20;
    return Stack(
      children: [
        TextField(
          style: style,
          controller: controller,
          decoration: decoration,
          keyboardType: keyboardType,
          maxLines: maxLines,
          minLines: minLines,
        ),
        Positioned.fill(
          child: Padding(
            padding: const EdgeInsets.only(top: 10),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: List.generate(
                numberOfLines,
                (index) => Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    SizedBox(height: textHeight),
                    const Divider(height: 1, thickness: 1),
                  ],
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }
}

If you want to let the underline auto-added with the text content, you may need to add the listener inside the text controller and try to use TextPainter to calculate the number of lines:

... (inside the UnderlineTextField)
double maxWidth = 0;

controller?.addListener(() {
  final text = controller?.text ?? '';
  final textPainter = TextPainter(
    text: TextSpan(text: text, style: style),
    maxLines: maxLines,
    textDirection: TextDirection.ltr,
  );

  textPainter.layout(maxWidth: maxWidth);
  final numberOfLines = textPainter.computeLineMetrics().length;
});

return LayoutBuilder(
  builder: (_, constrinat) {
    maxWidth = constrinat.maxWidth;
...
Stack(
          children: [
            for (int i = 0; i < 4; i++)
              Container(
                margin: EdgeInsets.only(
                  top: 4 + (i + 1) * 28,
                  left: 15,
                  right: 15,
                ),
                height: 2,
                color: Colors.black,
              ),
            const Padding(
              padding: EdgeInsets.symmetric(horizontal: 15),
              child: TextField(
                decoration: InputDecoration(border: InputBorder.none),
                style: TextStyle(
                  fontSize: 20.0,
                ),
                keyboardType: TextInputType.multiline,
                expands: true,
                maxLines: null,
              ),
            ),
          ],
        ),
Related