How to Make TextField in Multiple Colors in Flutter
At Akvelon, we made a code editor in Flutter with syntax highlighting. But out of the box the standard TextField
widget only works with a single color. Here we explain a simple way to upgrade that.
The TextField
widget does not itself impose any styling. Instead, it asks its TextEditingController
to produce a styled TextSpan
object, which is a piece of text with a style.
The TextField
passes its style to TextEditingController
, and the default implementation just puts it into the TextSpan
object, and this is how the color is normally applied.
To override this, subclass the TextEditingController
and override the method:
class GradientTextEditingController extends TextEditingController {
@override
TextSpan buildTextSpan({
required BuildContext context,
TextStyle? style,
bool? withComposing,
}) {
style ??= const TextStyle();
final leftStyle = style.copyWith(color: Colors.red);
final rightStyle = style.copyWith(color: Colors.indigo);
final children = <TextSpan>[];
for (final char in text.characters) {
children.add(
TextSpan(
text: char,
style: TextStyle.lerp(
leftStyle,
rightStyle,
children.length / text.length,
),
),
);
}
return TextSpan(style: style, children: children);
}
}
See the full code here.
You can do much more complex processing. For instance, we made code highlighting by parsing the syntax tree and colorizing keywords, literals, comments, etc. differently:
We start by importing the highlighting and the flutter_highlighting packages we made for another project:
import 'package:flutter_highlighting/themes/vs.dart';
import 'package:highlighting/highlighting.dart';
import 'package:highlighting/languages/java.dart';
Then we parse the text and get a simple form of a syntax tree:
class SyntaxTextEditingController extends TextEditingController {
@override
TextSpan buildTextSpan({
required BuildContext context,
TextStyle? style,
bool? withComposing,
}) {
final highlighted = highlight.parse(text, languageId: java.id);
return TextSpan(
style: style,
children: _buildList(
nodes: highlighted.nodes,
styles: vsTheme, // Built-in theme from flutter_highlighting
ancestorStyle: style,
),
);
}
// ...
Next is to walk the syntax tree and return a TextSpan
for each node:
List<TextSpan>? _buildList({
required List<Node>? nodes,
required Map<String, TextStyle> styles,
TextStyle? ancestorStyle,
}) {
return nodes
?.map(
(node) => _buildNode(
node: node,
styles: styles,
ancestorStyle: ancestorStyle,
),
)
.toList(growable: false);
}
TextSpan _buildNode({
required Node node,
required Map<String, TextStyle> styles,
TextStyle? ancestorStyle,
}) {
final style = styles[node.className] ?? ancestorStyle;
return TextSpan(
text: node.value,
children: _buildList(
nodes: node.children,
styles: styles,
ancestorStyle: style,
),
style: style,
);
}
See the full code here.
So the TextEditingController
class is the gate to all sorts of customization. We went farther that road and made an advanced code editor that can do this:
Check it out here if you are interested.