Flutter TextField width should match width of contained text
I modified SteveM's answer a little bit to fix the bug when widget is not expanding correctly. We should respect these two small things when calculating widget's width:
TextField
merges giventextStyle
parameter and current theme'sTextStyle
. This has to be done in custom widget too:
final ThemeData themeData = Theme.of(context);
final TextStyle style = themeData.textTheme.subtitle1.merge(textStyle);
TextField
'scursorWidth
has to be taken into widget's width calculations. Since there's no way to get default cursor width fromTextField
class, I checked its code and added a new constant toFitsTextField
class. Don't forget to pass it toTextField
's constructor:
final textWidth = max(widget.minWidth, tp.width + _CURSOR_WIDTH);
// When building a TextField:
child: TextField(
cursorWidth: _CURSOR_WIDTH,
Full code:
import 'dart:math';
import 'package:flutter/material.dart';
class FitTextField extends StatefulWidget {
final String initialValue;
final double minWidth;
const FitTextField({
Key key,
this.initialValue,
this.minWidth: 30,
}) : super(key: key);
@override
State<StatefulWidget> createState() => new FitTextFieldState();
}
class FitTextFieldState extends State<FitTextField> {
// 2.0 is the default from TextField class
static const _CURSOR_WIDTH = 2.0;
TextEditingController txt = TextEditingController();
// We will use this text style for the TextPainter used to calculate the width
// and for the TextField so that we calculate the correct size for the text
// we are actually displaying
TextStyle textStyle = TextStyle(
color: Colors.grey[600],
fontSize: 16,
);
initState() {
super.initState();
// Set the text in the TextField to our initialValue
txt.text = widget.initialValue;
}
@override
Widget build(BuildContext context) {
// TextField merges given textStyle with text style from current theme
// Do the same to get final TextStyle
final ThemeData themeData = Theme.of(context);
final TextStyle style = themeData.textTheme.subtitle1.merge(textStyle);
// Use TextPainter to calculate the width of our text
TextSpan ts = new TextSpan(style: style, text: txt.text);
TextPainter tp = new TextPainter(
text: ts,
textDirection: TextDirection.ltr,
);
tp.layout();
// Enforce a minimum width
final textWidth = max(widget.minWidth, tp.width + _CURSOR_WIDTH);
return Container(
width: textWidth,
child: TextField(
cursorWidth: _CURSOR_WIDTH,
style: style,
controller: txt,
onChanged: (text) {
// Redraw the widget
setState(() {});
},
),
);
}
}
I was able to achieve the expected result by using a TextPainter
to compute the desired width of the text. I then used that width as the width of the Container
containing the TextField
.
Remember to call setState()
in your TextFields onChanged
method. This tells the widget to redraw itself causing the TextField to adjust to the new width of its content.
import 'package:flutter/material.dart';
class FitTextField extends StatefulWidget {
final String initialValue;
final double minWidth;
const FitTextField({Key key, this.initialValue, this.minWidth: 30}): super(key: key);
@override
State<StatefulWidget> createState() => new FitTextFieldState();
}
class FitTextFieldState extends State<FitTextField>{
TextEditingController txt = TextEditingController();
// We will use this text style for the TextPainter used to calculate the width
// and for the TextField so that we calculate the correct size for the text
// we are actually displaying
TextStyle textStyle = TextStyle(color: Colors.grey[600]);
initState() {
super.initState();
// Set the text in the TextField to our initialValue
txt.text = widget.initialValue;
}
@override
Widget build(BuildContext context) {
// Use TextPainter to calculate the width of our text
TextSpan ts = new TextSpan(style: textStyle, text: txt.text);
TextPainter tp = new TextPainter(text: ts, textDirection: TextDirection.ltr);
tp.layout();
var textWidth = tp.width; // We will use this width for the container wrapping our TextField
// Enforce a minimum width
if ( textWidth < widget.minWidth ) {
textWidth = widget.minWidth;
}
return Container(
width: textWidth,
child: TextField(
style: textStyle,
controller: txt,
onChanged: (text) {
// Tells the framework to redraw the widget
// The widget will redraw with a new width
setState(() {});
},
),
);
}
}
Flutter has IntrinsicWidth to do the calculation for you. Just wrap your TextField
or TextFormField
in it as follow:
IntrinsicWidth(child: TextField())