Skip to content

OutlineInputBorder: Fix label animation and gap rendering - #182910

Draft
shivanuj13 wants to merge 9 commits into
flutter:masterfrom
shivanuj13:master
Draft

shivanuj13 wants to merge 9 commits into
flutter:masterfrom
shivanuj13:master

Conversation

@shivanuj13

Copy link
Copy Markdown
Contributor

Fixes the outline border appearing straight instead of rounded during the floating label animation by using gapPercentage in the corner arc sweep calculations.

Fixes #153387

Before the changes:

before.mp4

After the changes:

after.mp4

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

@github-actions github-actions Bot added framework flutter/packages/flutter repository. See also f: labels. p: material_ui material_ui package in flutter/packages labels Feb 25, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request fixes a rendering issue with OutlineInputBorder where the corners of the gap for the floating label would appear straight during animation. The changes correctly incorporate gapPercentage into the path calculations for the border gap, ensuring the corners are rounded throughout the animation. The fix looks mostly correct, but I've found a potential issue in the arc sweep calculation that might lead to incorrect animation behavior. Additionally, new tests have been added to cover this fix.

Comment on lines 478 to 480
final double tlCornerArcSweep = math.acos(
clampDouble(1 - start / scaledRRect.tlRadiusX, 0.0, 1.0),
clampDouble((1 - start / tlRadiusX) * gapPercentage, 0.0, 1.0),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The calculation for tlCornerArcSweep seems to have an extra multiplication by gapPercentage. The start argument to this method is already scaled by gapPercentage, so applying it again here might result in a non-linear and incorrect animation for the corner arc. The argument to acos should likely be clampDouble(1 - start / tlRadiusX, 0.0, 1.0). This would make the sweep angle animate smoothly from 0 to its final value as gapPercentage goes from 0 to 1.

      final double tlCornerArcSweep = math.acos(
        clampDouble(1 - start / tlRadiusX, 0.0, 1.0),
      );

path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep);
} else if (start + extent < scaledRRect.width) {
final double dx = scaledRRect.width - (start + extent);
final double sweep = math.asin(clampDouble((1 - dx / trRadiusX) * gapPercentage, 0.0, 1.0));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to the comment on tlCornerArcSweep, this calculation for sweep also seems to have an extra multiplication by gapPercentage. The values start and extent used in dx are already dependent on gapPercentage. This might cause an incorrect animation curve for the corner. I believe removing the multiplication would produce the intended smooth animation.

      final double sweep = math.asin(clampDouble(1 - dx / trRadiusX, 0.0, 1.0));

@flutter-dashboard

Copy link
Copy Markdown

This pull request executed golden file tests, but it has not been updated in a while (20+ days). Test results from Gold expire after as many days, so this pull request will need to be updated with a fresh commit in order to get results from Gold.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@dkwingsmt dkwingsmt left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for working on this! The logic change in this PR is really delicate and I appreciate it.

However, I noticed that this implementation actually changes the current behavior by introducing a horizontal "sliding" of the gap: By multiplying start by gapPercentage, the entire gap appears to slide along the X-axis as it animates, rather than expanding from a fixed starting point. Typically, the gap should stay anchored at its intended position (gapStart - gapPadding) and grow to the right as the extent increases.

The reason we see a straight line before your fix is that when the gap is still very small (and ends inside the corner radius), the original code jumps straight to scaledRRect.top to start the next segment. Instead of shifting X coordinates, we should calculate the exact point on the arc where the gap ends and "resume" the arc from that specific angle.

Instead of modifying start or adding offsets to moveTo, I recommend keeping the original start and extent logic but refining how we "resume" the path. If gapEnd falls within a radius, we can calculate the specific resumeAngle and use addArc to draw the remaining part of that corner. This would keep the gap fixed in place and avoid the jumpy behavior.

@shivanuj13

Copy link
Copy Markdown
Contributor Author

Thanks a lot for the review.

I’ve updated the fix so that the gap now stays anchored at its original start position, and only the gap width (extent) animates, as intended.

VID_20260402_163731.mp4

@shivanuj13
shivanuj13 requested a review from dkwingsmt April 6, 2026 09:55
@dkwingsmt dkwingsmt added the CICD Run CI/CD label Apr 8, 2026
@github-actions github-actions Bot removed the CICD Run CI/CD label Apr 24, 2026
@dkwingsmt dkwingsmt added the CICD Run CI/CD label Apr 24, 2026

@dkwingsmt dkwingsmt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM except for the unit test failures.

final double sweep = math.asin(clampDouble(1 - dx / scaledRRect.trRadiusX, 0.0, 1.0));
path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep);
} else if (gapEnd < scaledRRect.width) {
final double dx = scaledRRect.width - gapEnd;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't replace outerWidth with anything related to scaledRRect, because scaledRRect comes from inflating outer by borderSide.strokeOffset / 2. This is also the cause to the unit test failures.

justinmc
justinmc previously approved these changes Apr 28, 2026

@justinmc justinmc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, kudos @shivanuj13. LGTM if CI passes.

@dkwingsmt What are you using as a reference for how this animation should look? I was looking at the web implementation and it looks very slightly different than what I see in this PR in the last comment. The web one appears to expand the gap from a central point, whereas this PR expands the gap from left to right. Not a big deal though.

@dkwingsmt

dkwingsmt commented Apr 28, 2026 •

Copy link
Copy Markdown
Contributor

@justinmc Oh! Thank you so much for checking for me. I was just thinking about what the original issue says. I really should have checked the M3 website. Well this PR is still an improvement.

@shivanuj13

Copy link
Copy Markdown
Contributor Author

I was comparing gapEnd to scaledRRect.width, but scaledRRect is built off outer.inflate(borderSide.strokeOffset / 2), so its width isn’t the same horizontal ruler as gapStart / start / extent from InputDecorator.

Now I pass outer.width into _gapBorderPath and only use it for the gapEnd checks and dx

I also hadn’t checked the web version earlier. Please let me know if we should open a separate issue for this behavior difference. This PR is mainly addressing the original problem where, during label animation, the outline border appears straight instead of staying rounded throughout the animation.

@dkwingsmt
dkwingsmt self-requested a review May 6, 2026 18:20
@dkwingsmt dkwingsmt added the CICD Run CI/CD label May 7, 2026
@dkwingsmt

Copy link
Copy Markdown
Contributor

This PR is definitely a great improvement as it fixes an obvious bug. We should land this PR as it is (after the code freeze is lifted). Thank you very much for the contribution.

The issue mentioned by @justinmc should be filed as a separate issue, where we might want to discuss whether to apply the change by default or add a property for backward compatibility.

@shivanuj13 You're not required to file the issue, and definitely not required to address it. But doing so will definitely be appreciated. :)

@dkwingsmt

Copy link
Copy Markdown
Contributor

Analyser issue:


[2026-05-07 15:17:44.861156] [STDOUT] stdout:    info • Omit the type annotation on a local variable when the type is obvious. Try removing the type annotation • packages/flutter/test/material/input_decorator_test.dart:1815:13 • omit_obvious_local_variable_types
[2026-05-07 15:17:44.861281] [STDOUT] stdout:    info • Omit the type annotation on a local variable when the type is obvious. Try removing the type annotation • packages/flutter/test/material/input_decorator_test.dart:1816:13 • omit_obvious_local_variable_types
[2026-05-07 15:17:44.861306] [STDOUT] stdout:    info • The value of the argument is redundant because it matches the default value. Try removing the argument • packages/flutter/test/material/input_decorator_test.dart:2145:21 • avoid_redundant_argument_values
[2026-05-07 15:17:44.861387] [STDOUT] stdout:    info • The value of the argument is redundant because it matches the default value. Try removing the argument • packages/flutter/test/material/input_decorator_test.dart:2145:39 • avoid_redundant_argument_values

https://ci.chromium.org/ui/p/flutter/builders/try/Linux%20analyzer_benchmark/26008/overview

@github-actions github-actions Bot removed the CICD Run CI/CD label May 10, 2026
@shivanuj13

Copy link
Copy Markdown
Contributor Author

Thank you @dkwingsmt for guiding me throughout this PR. I have fixed the analyser issue.

@dkwingsmt dkwingsmt added the CICD Run CI/CD label May 13, 2026
..path(style: PaintingStyle.stroke)
..restore(),
);
}, skip: isBrowser);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

analyzer error:

/b/s/w/ir/x/w/flutter/packages/flutter/test/material/input_decorator_test.dart:1831: skip test without a justification comment.

@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 1, 2026
@dkwingsmt
dkwingsmt self-requested a review June 3, 2026 18:34
@dkwingsmt dkwingsmt added the CICD Run CI/CD label Jun 3, 2026

@dkwingsmt dkwingsmt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-LGTM for the record.

@dkwingsmt

Copy link
Copy Markdown
Contributor

Thanks for your contribution! This PR has reached completion, however we're not able to merge this PR for the moment due to the Material/Cupertino code freeze (#184093). As soon as the decoupling is complete, we will post a way to port this PR to the new packages to land. In the meantime, I'll convert this PR to a draft and apply a label so that we can quickly find it when the freeze is lifted.

@dkwingsmt
dkwingsmt marked this pull request as draft June 10, 2026 17:51
@dkwingsmt dkwingsmt added the waiting for code freeze This PR is waiting for a code freeze to resolve. label Jun 10, 2026
@Piinks Piinks added the Decoupling: Not ready to port yet Instructions will be provided when this is ready to move to flutter/packages. label Jun 24, 2026
@Piinks

Piinks commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

I've marked this PR as not ready to port to flutter/packages yet.
We'll provide instructions to move this change over to material_ui/cupertino_ui once ready to receive PRs. Thank you!

@Piinks

Piinks commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

This PR is now ready to port over to flutter/packages! 🥳
See guidance and instructions in #188444

@Piinks Piinks added Decoupling: Port to flutter/packages This PR is ready to be ported to flutter/packages. We will provide instructions to do so. and removed waiting for code freeze This PR is waiting for a code freeze to resolve. Decoupling: Not ready to port yet Instructions will be provided when this is ready to move to flutter/packages. labels Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD Decoupling: Port to flutter/packages This PR is ready to be ported to flutter/packages. We will provide instructions to do so. framework flutter/packages/flutter repository. See also f: labels. p: material_ui material_ui package in flutter/packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Material UI: Visual bug with decorated TextFields and label animations

5 participants