flutter icon indicating copy to clipboard operation
flutter copied to clipboard

TextFormField doesn't render its border as said in the docs.

Open feinstein opened this issue 5 years ago • 10 comments

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).

feinstein avatar Mar 05 '19 00:03 feinstein

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.

border

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.

HansMuller avatar Mar 05 '19 17:03 HansMuller

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.

feinstein avatar Mar 05 '19 18:03 feinstein

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.

zekiegitimcom avatar May 15 '19 16:05 zekiegitimcom

Hi @feinstein Do you think this issue still valid? Thank you

TahaTesser avatar Apr 24 '20 14:04 TahaTesser

Yes, I would still like to see a StadiumInputBorder.

feinstein avatar Apr 24 '20 15:04 feinstein

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.

mugilan-viewzen avatar May 17 '22 10:05 mugilan-viewzen

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.

justinmc avatar Jul 14 '22 20:07 justinmc

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 :')

ruialves35 avatar Apr 29 '24 13:04 ruialves35

Hi @ruialves35, welcome to Flutter. I will try to answer some of your questions:

  1. 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.
  2. 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.
  3. The reason why this has not being done yet is probably because there are other higher priority tasks do be done.
  4. 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.

feinstein avatar May 01 '24 06:05 feinstein

Hi @ruialves35, welcome to Flutter. I will try to answer some of your questions:

  1. 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.
  2. 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.
  3. The reason why this has not being done yet is probably because there are other higher priority tasks do be done.
  4. 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

ruialves35 avatar May 01 '24 07:05 ruialves35

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

Piinks avatar Aug 22 '24 19:08 Piinks

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());
    }
  }
}

davidnwaneri avatar Aug 26 '24 08:08 davidnwaneri