flutter
flutter copied to clipboard
TextFormField doesn't render its border as said in the docs.
I think there's a problem in the way TextFormField
deals with its border.
As far as I understand from the docs, the border
parameter will take an InputDecoration
shape and will substitute the color and weight from the others parameters like enabledBorder
and focusedBorder
.
So if this is true, the following code should work:
TextFormField(
decoration: InputDecoration(
enabledBorder: const OutlineInputBorder(
borderSide: const BorderSide(color: Colors.orange),
) ,
focusedBorder: const OutlineInputBorder(
borderSide: const BorderSide(color: Colors.white),
) ,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(100)
)
),
)
In this code I define a border shape where corners are rounded and ONLY the color of the border changes if it's enabled or focused.
But in fact what I am seeing is that the shape in border
is being totally ignored and the TextFormField
is rendered as a square with orange or white colors, no rounded edges.
The only way I can get it to work is with this very repetitive code:
TextFormField(
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.orange),
borderRadius: BorderRadius.circular(100)
) ,
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
borderRadius: BorderRadius.circular(100)
) ,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(100)
)
),
)
Is this really the correct way to achieive this?
I tried @HansMuller short test case here: https://github.com/flutter/flutter/pull/19694#issuecomment-469376553 but since it doesn't have a rounded border it doesn't match what I see. I think the problem lies in borderRadius: BorderRadius.circular(100)
.
OutlineInputBorder's borderRadius parameter defaults to BorderRaidus.circular(4)
so there's no avoiding providing that parameter for each InputDecoration border. This version of the test case appears to do what you want: https://gist.github.com/HansMuller/7b9c44ee67e9fa260678f423af6350bd, albeit with a smaller border radius.
That said, I can see two real problems here:
- Using large border radius values (like 100) breaks the trig code that computes the focused border's gap.
- I assume that what you're looking for is a "stadium" border outline. What we probably need for that case is a
StadiumInputBorder
class.
so there's no avoiding providing that parameter for each InputDecoration border.
Well this is the case then I think the docs should be modified, the way I understood them was that the border would be maintained as defined by boder
and only color and minor things would change. Maybe what will change and what will not should be better documented?
Using large border radius values (like 100) breaks the trig code that computes the focused border's gap.
Yes, I saw some weird behavior using the label, so I am only using hint
.
I assume that what you're looking for is a "stadium" border outline. What we probably need for that case is a StadiumInputBorder class.
Yes, I want a "stadium" border outline and created an issue for it here #28611 already.
I have same problem with TextField(), i think its a bug.
My InputDecoration code:
InputDecoration(
labelText: 'Insert your name...',
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.amberAccent,
width: 3,
),
borderRadius: const BorderRadius.all(Radius.circular(0)),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.red,
width: 3,
),
borderRadius: const BorderRadius.all(Radius.circular(0)),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.indigo,
width: 3,
),
borderRadius: const BorderRadius.all(Radius.circular(0)),
),
disabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.blueGrey,
width: 3,
),
borderRadius: const BorderRadius.all(Radius.circular(0)),
),
border: UnderlineInputBorder(),
),
According to code, there are some situations:
-
if your TextField's
enabled:
is false, -- then workdisabledBorder:
,
--- there is NO FOCUS! in this situation, because it is disabled. -
if your TextField's
enabled:
is true, -- then workenabledBorder:
,
--- there is a FOCUS! ---- when i focus TextField, then workfocusedBorder:
. -
if you have an error (or you fill
errorText:
in your TextField), -- then workerrorBorder:
, --- there is a FOCUS! ---- when i focus TextField, i expectfocusedBorder:
will work again. BUT NOT WORKING -----errorBorder:
andborder:
work together. (border:
only work in this situation.)
Advices:
-
InputDecoration class need an
errorFocusedBorder:
parameter and don't needborder:
parameter. It's just a mess. -
focusedBorder:
parameter should change or deprecate withenabledFocusedBorder:
parameter.
Hi @feinstein Do you think this issue still valid? Thank you
Yes, I would still like to see a StadiumInputBorder
.
I have same problem with TextField(), i think its a bug.
My InputDecoration code:
InputDecoration( labelText: 'Insert your name...', focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.amberAccent, width: 3, ), borderRadius: const BorderRadius.all(Radius.circular(0)), ), errorBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.red, width: 3, ), borderRadius: const BorderRadius.all(Radius.circular(0)), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.indigo, width: 3, ), borderRadius: const BorderRadius.all(Radius.circular(0)), ), disabledBorder: OutlineInputBorder( borderSide: BorderSide( color: Colors.blueGrey, width: 3, ), borderRadius: const BorderRadius.all(Radius.circular(0)), ), border: UnderlineInputBorder(), ),
According to code, there are some situations:
* if your TextField's `enabled:` is **_false_**, -- then work **`disabledBorder:`** , --- there is **NO FOCUS!** in this situation, because it is disabled. * if your TextField's `enabled:` is **_true_**, -- then work **`enabledBorder:`** , --- there is a **FOCUS!** ---- when i focus TextField, then work **`focusedBorder:`** . * if you have an error (or you fill `errorText:` in your TextField), -- then work **`errorBorder:`** , --- there is a **FOCUS!** ---- when i focus TextField, **i expect `focusedBorder:` will work** again. BUT NOT WORKING ----- **`errorBorder:`** and **`border:`** work together. (`border:` **only work in this situation**.)
Advices:
1. InputDecoration class need an **`errorFocusedBorder:`** parameter and don't need `border:` parameter. It's just a mess. 2. `focusedBorder:` parameter should change or deprecate with **`enabledFocusedBorder:`** parameter.
This bug is still there.
I recently made some changes (https://github.com/flutter/flutter/pull/93933) that help eliminate the gap in the border that can be seen in this issue. Still not a full fix for a stadium border though.
It's been 5 years since this issue came out and there is no solution. Why is there a contentPadding that works for both content AND label? How hard is it to add a new field labelPadding and work with that on the label, and use contentPadding only for content as it was suppose to? What is preventing this improvement, as it seems so easy to extend? This is preventing the development of so many custom dropdowns.
I did not understand that idea of the "stadiumInputBorder", is there any solution?
And I'm sorry if I'm being so much judgemental even if I'm a beginner but this is not making much sense to me :')
Hi @ruialves35, welcome to Flutter. I will try to answer some of your questions:
- In a nutshell, the code that draws a
TextField
's border is not simple, there are parts that are kinda more "low level", with pixel calculations (to draw the lines and curves) and all of the parameters to decide how to draw them, come from another "place". I am simplifying things here, just so you understand that's not a quick fix. - Stadium is the shape of a curved square, like a football stadium shape. The idea is to have a new and more customisable shape that we can plug-in there.
- The reason why this has not being done yet is probably because there are other higher priority tasks do be done.
- Be careful when asking questions regarding time estimates or why things are not done. They are not welcome in the community, they generate unnecessary friction with the Flutter team, as they have lots of stuff to do. This kind of question can get you banned, take a look at the community guidelines please.
Hi @ruialves35, welcome to Flutter. I will try to answer some of your questions:
- In a nutshell, the code that draws a
TextField
's border is not simple, there are parts that are kinda more "low level", with pixel calculations (to draw the lines and curves) and all of the parameters to decide how to draw them, come from another "place". I am simplifying things here, just so you understand that's not a quick fix.- Stadium is the shape of a curved square, like a football stadium shape. The idea is to have a new and more customisable shape that we can plug-in there.
- The reason why this has not being done yet is probably because there are other higher priority tasks do be done.
- Be careful when asking questions regarding time estimates or why things are not done. They are not welcome in the community, they generate unnecessary friction with the Flutter team, as they have lots of stuff to do. This kind of question can get you banned, take a look at the community guidelines please.
Thank you in advance, I completely understand your points. My question was more regarding the priority of this issue and if anyone is taking care of it, as nothing was said about that and there were already some attempts on fixing this, so we don't know if this will be done this year or it will be Open for the next 5 again... :( Furthermore, since you have the Center alignment and something related, it felt like extending that to customized pixels (and not only start and center) would be Nice
Came across this in old issue backlog review. The updates in #93933 made some improvements here. 👍
There is still room to improve though. Here is an updated code sample and clip of the behavior. Any more clarification on the desired behavior is welcome.
Code
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHome(),
);
}
}
class MyHome extends StatefulWidget {
@override
State<MyHome> createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[400],
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
const Spacer(flex: 2),
TextFormField(
initialValue: 'Wrong',
decoration: InputDecoration(
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.orange),
),
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(100)),
),
),
const Spacer(),
TextFormField(
initialValue: 'Better, but difficult',
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.orange),
borderRadius: BorderRadius.circular(100),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.white),
borderRadius: BorderRadius.circular(100),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(100),
),
),
),
const Spacer(flex: 2)
],
),
),
);
}
}
https://github.com/user-attachments/assets/36d3f15f-bdde-485e-869f-58374e6da798
Here is my implementation for StadiumInputBorder
incase anyone has urgent need for it, pending when one is merged into the framework
@immutable
class StadiumInputBorder extends InputBorder {
const StadiumInputBorder({
super.borderSide = const BorderSide(),
});
@override
bool get isOutline => true;
@override
StadiumInputBorder copyWith({BorderSide? borderSide}) {
return StadiumInputBorder(borderSide: borderSide ?? this.borderSide);
}
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.all(borderSide.width);
@override
ShapeBorder scale(double t) => StadiumBorder(side: borderSide.scale(t));
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
final Radius radius = Radius.circular(rect.shortestSide / 2.0);
final RRect borderRect = RRect.fromRectAndRadius(rect, radius);
final RRect adjustedRect = borderRect.deflate(borderSide.strokeInset);
return Path()..addRRect(adjustedRect);
}
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
final Radius radius = Radius.circular(rect.shortestSide / 2.0);
return Path()..addRRect(RRect.fromRectAndRadius(rect, radius));
}
@override
void paint(
Canvas canvas,
Rect rect, {
double? gapStart,
double gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection? textDirection,
}) {
switch (borderSide.style) {
case BorderStyle.none:
break;
case BorderStyle.solid:
final Radius radius = Radius.circular(rect.shortestSide / 2);
final RRect borderRect = RRect.fromRectAndRadius(rect, radius);
canvas.drawRRect(borderRect.inflate(borderSide.strokeOffset / 2), borderSide.toPaint());
}
}
}