Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter - how to make TextField width fit its text ("wrap content")

Tags:

flutter

I'm trying to do a "search contact list" feature with some chips representing selected contacts, and a user can type on text field to filter and add more contacts:

Desired result

This is done with a Wrap widget, wrapping a list of Chip widgets, and ending the list with a Container of a TextField widget.

What I've tried:

If I do not set the width of the TextField, it defaults to occupy a whole line. Let's make it red for clarity:

Default width is whole line

I do not want a whole line for it, so I set it to a small value, 50. But this doesn't work if the text is long:

Fixing width hides long texts

Question:

Is it possible to make the TextField starts small, and auto expands to a whole line when needed? I've tried "minWidth" in BoxConstraint but since the TextField defaults to a whole line, that doesn't work. Is using Wrap and TextField the correct way here?

like image 531
user1032613 Avatar asked Apr 03 '19 22:04

user1032613


People also ask

How do you wrap a TextField in Flutter?

Here's how you wrap text on overflow in Flutter:Step 1: Make sure your Text widget is inside the Row widget. Step 2: Wrap your Text widget inside the Expanded widget. Step 3: Run your app.

How do I resize the TextField Flutter?

To change the TextField height by changing the Font Size: Step 1: Inside the TextField, Add the style parameter and assign the TextStyle(). Step 2: Inside the TextStyle(), Add the fontSize parameter and set the appropriate value. Step 3: Run the app.

How do I clean my TextField Flutter?

You can control the TextField in your Flutter App. The TextField widget of Flutter has an inbuilt method TextEditingController. clear() which is used to clear or empty the typed text inside the Text Input widget. The clear() method would empty the entered string text and set its default value is empty.


2 Answers

Use IntrinsicWidth widget to size a child to the child's maximum intrinsic width. In this case, effectively shrink wrapping the TextField:

IntrinsicWidth(
  child: TextField(),
)

However, this will make the TextField too small when it's empty. To fix that, we can use ConstrainedBox to force a minimum width constraint. For example:

ConstrainedBox(
  constraints: BoxConstraints(minWidth: 48),
  child: IntrinsicWidth(
    child: TextField(),
  ),
)

End result:

enter image description here

like image 105
user1032613 Avatar answered Sep 20 '22 09:09

user1032613


I tried but failed. I have issues figuring out when the TextField overflows. This solution cannot work with dynamically changing chips since tp.layout(maxWidth: constraints.maxWidth/2); is hard coded.

There are two options to fix this solution:

  • TextController has a overflow flag

  • In tp.layout(maxWidth: constraints.maxWidth/2), LayoutBuilder can figure out the width left over from chips.

Here is my attempt

enter image description here

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  TextEditingController _controller;
  String _text = "";
  bool _textOverflow = false;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _textOverflow = false;
    _controller = TextEditingController();
    _controller.addListener((){
      setState(() {
        _text = _controller.text;
      });
    });
  }
  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _controller.dispose();
  }

  Widget chooseChipInput(BuildContext context, bool overflow, List<Widget> chips) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.start,
      children: <Widget>[
        overflow ? Wrap(children: chips, alignment: WrapAlignment.start,): Container(),
        Container(
          color: Colors.red,
          child: TextField( 
            controller: _controller,
            maxLines: overflow ? null : 1,
            decoration:  InputDecoration(icon: overflow ? Opacity(opacity: 0,) : Wrap(children: chips,)),
          ),
        )

      ]
    );
  }

  @override
  Widget build(BuildContext context) {
    const _counter = 0;
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),

            LayoutBuilder(builder: (context, constraints){
                var textStyle = DefaultTextStyle.of(context).style;
                var span = TextSpan(
                  text: _text,
                  style: textStyle,
                );
                // Use a textpainter to determine if it will exceed max lines
                var tp = TextPainter(
                  maxLines: 1,
                  textAlign: TextAlign.left,
                  textDirection: TextDirection.ltr,
                  text: span,
                );
                // trigger it to layout
                tp.layout(maxWidth: constraints.maxWidth/2);

                // whether the text overflowed or not
                print("****** ${tp.didExceedMaxLines} ${constraints.maxWidth}");
                return chooseChipInput(
                  context, 
                  tp.didExceedMaxLines, 
                  <Widget>[Chip(label: Text("chip1"),), 
                      Chip(label: Text("chip2")),]
                );
            },),

          ],
        ),
      ),
    );
  }
}

This attempt comprised of a few parts:

  • Checking when TextField overflows with this hack https://stackoverflow.com/a/52272545
  • Uses ternary operators to ensure Flutter does not rebuild TextField in order to maintain cursor position.
  • Enable multiline TextField when text overflows https://docs.flutter.io/flutter/material/TextField/maxLines.html
  • Changing the layout between column and InputDecoration to sure the correct position of chips.

Edit3: Added picture when you add tons of chips and fix the Column(Warp) enter image description here enter image description here

Like I said, the largest problem is that I cannot figure out when the text box overflows.

Anyone else wants try? I think this question needs a custom plugin to solve

Edit2: I found the library but I did not test it https://github.com/danvick/flutter_chips_input

like image 42
user1462442 Avatar answered Sep 18 '22 09:09

user1462442