26

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?

6
  • could you add your sample code ? Commented Apr 3, 2019 at 22:52
  • @diegoveloper The layout composition is explained in the question. The code itself is very uninteresting: Wrap(children:[Chip(), Container(color: red, child: TextField(controller: _controller))]). Commented Apr 3, 2019 at 22:58
  • @user1032613 did you already try wrap your Container() in an Expanded() widget ? Commented Apr 3, 2019 at 23:08
  • Have you tried pub.dev/packages/fitted_text_field_container? Commented Jun 29, 2020 at 20:36
  • I am facing similar problem. I have used flutter_typeahead pub where after selection add chips before textfield. But textField take entire width. Commented Sep 8, 2020 at 14:22

4 Answers 4

73

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

Sign up to request clarification or add additional context in comments.

3 Comments

awesome way to do it!
This almost works in my case. But I want the text field to not be smaller than the decoration label.
Magical answer! This idea works with ListTile too! I never imagined that this can be done so easily!
3

Over a whole year has passed since I asked and forgot about this question... I gave it a little bit more thoughts today, and took a different approach this time.

The key problem is that, we are not able to let TextField occupy just the right amount of space. So this approach uses a simple Text to display the text content, and use a very thin TextField (at 4 px) just to make it render the blinking cursor, shown in red:

widget composition diagram

Feel free to use this approach as a starting point if it helps anyone.

Usage:

TextChip()

Demo:

Code: (draft, works as demoed above; should only be used as a starting point)

class TextChip extends StatefulWidget { @override _TextChipState createState() => _TextChipState(); } class _TextChipState extends State<TextChip> { final _focus = FocusNode(); final _controller = TextEditingController(); String _text = ""; @override Widget build(BuildContext context) { return InputChip( onPressed: () => FocusScope.of(context).requestFocus(_focus), label: Stack( alignment: Alignment.centerRight, overflow: Overflow.visible, children: [ Text(_text), Positioned( right: 0, child: SizedBox( width: 4, // we only want to show the blinking caret child: TextField( scrollPadding: EdgeInsets.all(0), focusNode: _focus, controller: _controller, style: TextStyle(color: Colors.transparent), decoration: InputDecoration( border: InputBorder.none, ), onChanged: (_) { setState(() { _text = _controller.text; }); }, ), ), ), ], ), ); } } 

Comments

1

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:

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

2 Comments

+1 for the time and effort. Would this work when there are more chips and starts wrapping to the next line? For example, if 10 contacts are selected, it probably will take 2-3 lines to display the chips.
Yea, I should had tested lots of chip. InputDecoration obviously cannot handle multiple rows. I guess we need to detect when chips moves to the next line
0

If you also want to the decoration has the same size with textfield, use

isCollapsed

In my case, the app just allows the user input maximum 8 characters and do not need to show counter text or error widgets. Here is an example:

ConstrainedBox( constraints: const BoxConstraints(minWidth: 50), child: IntrinsicWidth( child: TextField( controller: _textController, keyboardType: TextInputType.number, maxLength: 8, cursorColor: MyTheme.grey2, decoration: const InputDecoration( border: textFieldBorder, focusedBorder: textFieldBorder, counterText: '', contentPadding: EdgeInsets.symmetric(vertical: 4, horizontal: 6), isCollapsed: true, ), style: Theme.of(context) .textTheme .labelSmall ?.copyWith(color: MyTheme.grey2), ), ), ), 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.