Skip to content

[material/menu_anchor.dart] Add animations to MenuAnchor.#176494

Merged
davidhicks980 merged 67 commits intoflutter:masterfrom
davidhicks980:animated-menu-anchor
Jan 29, 2026
Merged

[material/menu_anchor.dart] Add animations to MenuAnchor.#176494
davidhicks980 merged 67 commits intoflutter:masterfrom
davidhicks980:animated-menu-anchor

Conversation

@davidhicks980
Copy link
Contributor

@davidhicks980 davidhicks980 commented Oct 3, 2025

  • Adds MD3 animations to MenuAnchor.
  • Adds a "hoverOpenDelay" parameter on SubmenuButton
  • Moves the layout algorithm from SingleChildLayoutDelegate to a custom render object. This was done because I needed to access the dry layout of the menu panel for the best effect. I'll demonstrate below. Turns out using dry layout significantly limits what children you can use with a widget. As a result, I'm calculating the final height using the inverse height factor.

Fixes #135025

I don't have access to the internal Google documentation, so I did my best to deduce the spec from https://github.com/material-components/material-web/blob/main/menu/internal/menu.ts.

This change is currently opt-in via an animated parameter on MenuAnchor and SubmenuButton. As a result, this is not a breaking change. Because the PR is already quite large, I chose to not add animation customization to this PR. I also didn't change any theming files or DropdownMenu.

The only other API addition is a hoverOpenDelay parameter on SubmenuButton. This parameter delays the start of a submenu opening by the specified duration. It is independent of the animated parameter.

When animated == false, the menu runs its forward and reverse animations with a duration of 0. I originally disposed of all animation assets when animated was false, but found that the code/testing complexity to be unwieldy.

Blocked by #176503

Examples

  • Basic example adapted from material-web
Screen.Recording.2026-01-14.at.11.07.31.PM.mov
  • Example showing scrollbar
Screen.Recording.2026-01-14.at.11.08.26.PM.mov
  • MenuBar usage
Screen.Recording.2026-01-14.at.11.09.27.PM.mov

Thanks to @QuncCccccc and @dkwingsmt for their work on this issue. Sorry for throwing a wrench into the original PR by introducing RawMenuAnchor...

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. f: material design flutter/packages/flutter/material repository. labels Oct 3, 2025
@guidezpl
Copy link
Member

guidezpl commented Oct 7, 2025

Looks good and seems consistent with https://m3.material.io/components/menus/guidelines#691d98ba-0375-4681-8c47-a5c3e7b58798. Only suggestion I would have is to not have a scrollbar appear temporarily during the animation, if there's not going to be one in the completed animated state.

@davidhicks980
Copy link
Contributor Author

@guidezpl I was thinking the same thing. Fix is pushed.

@github-actions github-actions bot added d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos labels Oct 10, 2025
@davidhicks980 davidhicks980 marked this pull request as ready for review October 10, 2025 05:31
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
…eDryLayout (flutter#176503)

_RenderDropdownMenuBody.computeDryLayout accesses this.constraints, but
it should only access the constraints parameter passed into
computeDryLayout. This PR removes this.constraints access.

<img width="621" height="73" alt="image"
src="proxy.php?url=https://github.com/user-attachments/assets/495573e3-3b91-4091-8a7c-76594c98e22f"
/>

Also, I removed a line in which the child's offset is being set in
computeDryLayout. This appears to be an error:
<img width="402" height="56" alt="image"
src="proxy.php?url=https://github.com/user-attachments/assets/0045761d-c958-451b-a6ec-cbdf0fe7bd09"
/>

Finally, I'm curious whether there is a reason why only the first child
is being used to set the height?

Resolves flutter#176494

Blocking flutter#176494

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

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](https://developers.google.com/gemini-code-assist/docs/review-github-code).
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.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
@dkwingsmt dkwingsmt requested a review from QuncCccccc December 17, 2025 19:15
Copy link
Contributor

@QuncCccccc QuncCccccc left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks a lot for implementing this feature!

/// Whether the menu should open and close with an animation.
///
/// Defaults to false.
final bool animated;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to add onAnimationStatusChanged to SubmenuButton since it can also be treated like a "anchor"? But it's fine to add it when we need it in the future😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done and added tests

@davidhicks980
Copy link
Contributor Author

davidhicks980 commented Jan 16, 2026

Okay, everything should be updated.

Added:

  • An AnimationStatus example to menu_anchor.0.dart with an associated test.
  • A snippet and additional documentation for onAnimationStatusChanged
  • An onAnimationStatusChanged property to SubmenuButton
  • A guard against closing a child while its parent is closing (I realized this could be an issue while testing the submenu animation status callback)

Changed:

  • The web curve is now used instead of the flutter curve for Easing.emphasized
  • Videos demonstrating the animation.
  • SizeTransition was swapped with Align + AnimationBuilder because SizeTransition was clipping the Material and Material already introduces a clip.

Everything should be good-to-go, but I wanted one last check since I changed a few lines. Lmk and I'll merge!

@dkwingsmt dkwingsmt requested a review from QuncCccccc January 21, 2026 19:13
Copy link
Contributor

@dkwingsmt dkwingsmt left a comment

Choose a reason for hiding this comment

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

LGTM. Thank you so much for the fantastic work! (I've asked Qun for final review.)

Comment on lines +395 to +399
/// Because the [MenuController.isOpen] property is true while the menu is
/// opening, opened, and closing, it cannot be used to determine whether the
/// menu is closing or opening. As such, this callback provides a way to
/// determine when the menu is opening or closing, and allows anchors to
/// toggle between the opening and closing states of the menu.
Copy link
Contributor

@dkwingsmt dkwingsmt Jan 21, 2026

Choose a reason for hiding this comment

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

I find the first sentence taking a bit effort to understand due to the change of subject and the "it". I propose the following rewrite (with the help of AI).

Suggested change
/// Because the [MenuController.isOpen] property is true while the menu is
/// opening, opened, and closing, it cannot be used to determine whether the
/// menu is closing or opening. As such, this callback provides a way to
/// determine when the menu is opening or closing, and allows anchors to
/// toggle between the opening and closing states of the menu.
/// This callback provides a way to determine when the menu is opening or
/// closing. This is necessary because the [MenuController.isOpen] property
/// remains true throughout the opening, opened, and closing phases, and
/// therefore cannot be used on its own to determine the current animation
/// direction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Copied verbatim

await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
expect(focusedMenu, equals('MenuItemButton(Text("Sub Menu 12"))'));
});
testWidgets('hoverOpenDelay delays when a SubmenuButton opens', (WidgetTester tester) async {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
testWidgets('hoverOpenDelay delays when a SubmenuButton opens', (WidgetTester tester) async {
testWidgets('hoverOpenDelay delays when a SubmenuButton opens', (WidgetTester tester) async {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

),
);
},
experimentalLeakTesting: LeakTesting.settings
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the convention, according to other tests, is to place this property before the test body.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Contributor

@QuncCccccc QuncCccccc left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks for your contribution:)!

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: We can also mention that this example also shows how to use [onAnimationStatusChanged] to track animation status and toggle the menu.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done -- copied verbatim

@davidhicks980 davidhicks980 added this pull request to the merge queue Jan 29, 2026
Merged via the queue into flutter:master with commit e1e029d Jan 29, 2026
69 of 70 checks passed
@davidhicks980 davidhicks980 deleted the animated-menu-anchor branch January 29, 2026 01:37
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Jan 29, 2026
Roll Flutter from dfd92b773219 to da72d5936d69 (39 revisions)

flutter/flutter@dfd92b7...da72d59

2026-01-29 98614782+auto-submit[bot]@users.noreply.github.com Reverts "[ Tool / Engine ] Cleanup x86 references (#181152)" (flutter/flutter#181643)
2026-01-29 [email protected] Roll Skia from 174db42b7300 to 89df65f8324c (2 revisions) (flutter/flutter#181636)
2026-01-29 [email protected] [material/menu_anchor.dart] Add animations to MenuAnchor. (flutter/flutter#176494)
2026-01-28 [email protected] Roll Skia from 8e09f8a82251 to 174db42b7300 (6 revisions) (flutter/flutter#181627)
2026-01-28 [email protected] Send statusBarTouch events via dedicated messages (flutter/flutter#179643)
2026-01-28 [email protected] test: Improve DropdownMenuFormField tests (flutter/flutter#181369)
2026-01-28 [email protected] Account for vec3 padding in Metal (flutter/flutter#181563)
2026-01-28 [email protected] [ Tool / Engine ] Cleanup x86 references (flutter/flutter#181152)
2026-01-28 [email protected] Fix the issue on macOS where, after a hot restart with multiple windows, unresponsive windows are left behind. (flutter/flutter#180287)
2026-01-28 [email protected] Roll Skia from 675444e94bc9 to 8e09f8a82251 (7 revisions) (flutter/flutter#181606)
2026-01-28 [email protected] Roll Packages from e37af11 to 1cb2148 (5 revisions) (flutter/flutter#181608)
2026-01-28 [email protected] fix: swap app and engine version in vk::ApplicationInfo (flutter/flutter#181432)
2026-01-28 [email protected] Adds impeller backend to skia gold client (flutter/flutter#181503)
2026-01-28 [email protected] Remove chrome_and_driver dependency where it's not needed (flutter/flutter#178174)
2026-01-28 [email protected] chore: Windows_mokey basic_material_app_win__compile !bringup (flutter/flutter#180985)
2026-01-28 [email protected] Fix generating both `settings.gradle` and `settings.gradle.kts` for plugins (flutter/flutter#181592)
2026-01-28 [email protected] Roll Skia from 6614f89de521 to 675444e94bc9 (3 revisions) (flutter/flutter#181587)
2026-01-28 [email protected] Fix `todayBorder` todayBorder color is incorrectly overridden by `todayForegroundColor` in CalendarDatePicker (flutter/flutter#178792)
2026-01-28 [email protected] Roll Skia from 8f1695a4b328 to 6614f89de521 (2 revisions) (flutter/flutter#181583)
2026-01-28 [email protected] feat: add RoundedSuperellipseInputBorder (flutter/flutter#177220)
2026-01-28 [email protected] Roll Dart SDK from 38e351498549 to f10dcbfca98f (2 revisions) (flutter/flutter#181582)
2026-01-28 [email protected] Roll Skia from f424d58e37a3 to 8f1695a4b328 (6 revisions) (flutter/flutter#181577)
2026-01-28 [email protected] Remove unused code paths  in `PlatformViewsController.java` (flutter/flutter#181393)
2026-01-28 [email protected] feat: add onEnd to AnimatedCrossFade (flutter/flutter#181455)
2026-01-28 [email protected] Don't pass bounds to saveLayer call when painting ImageFilter (flutter/flutter#181353)
2026-01-28 [email protected] test: Clarify failure messages on gestures/debug_test.dart (flutter/flutter#181109)
2026-01-27 [email protected] Roll Fuchsia Linux SDK from akraNGn2lw4T1msgZ... to adhoq9ouVRh0xzkm3... (flutter/flutter#181571)
2026-01-27 [email protected] Roll Dart SDK from 4c7cb0a1d07d to 38e351498549 (4 revisions) (flutter/flutter#181570)
2026-01-27 [email protected] Make sure that an AnimatedPadding doesn't crash in 0x0 environment (flutter/flutter#181235)
2026-01-27 [email protected] Make sure that an ImageFiltered doesn't crash at 0x0 environment (flutter/flutter#181067)
2026-01-27 [email protected] Marks firebase_release_smoke_test to be unflaky (flutter/flutter#181308)
2026-01-27 [email protected] Fix remove material import box decoration test (flutter/flutter#181253)
2026-01-27 [email protected] Roll Skia from c2754838b077 to f424d58e37a3 (8 revisions) (flutter/flutter#181564)
2026-01-27 [email protected] Make sure that an AnimatedAlign doesn't crash in 0x0 environment (flutter/flutter#181361)
2026-01-27 [email protected] Make sure that an ImageIcon doesn't crash in 0x0 environment (flutter/flutter#181099)
2026-01-27 [email protected] Make sure that an AnimatedContainer doesn't crash in 0x0 environment (flutter/flutter#181198)
2026-01-27 [email protected] Make sure that an AnimatedRotation doesn't crash in 0x0 environment (flutter/flutter#181486)
2026-01-27 [email protected] Make sure that an AnimatedPositionedDirectional doesn't crash in 0x0 … (flutter/flutter#181451)
2026-01-27 [email protected] Merge changelog for 3.38.8. (flutter/flutter#181558)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

...
flutter-zl pushed a commit to flutter-zl/flutter that referenced this pull request Feb 10, 2026
…6494)

* Adds MD3 animations to MenuAnchor. 
* Adds a "hoverOpenDelay" parameter on `SubmenuButton`
* Moves the layout algorithm from SingleChildLayoutDelegate to a custom
render object. <s>This was done because I needed to access the dry
layout of the menu panel for the best effect. I'll demonstrate
below.</s> Turns out using dry layout significantly limits what children
you can use with a widget. As a result, I'm calculating the final height
using the inverse height factor.

Fixes flutter#135025

I don't have access to the internal Google documentation, so I did my
best to deduce the spec from
https://github.com/material-components/material-web/blob/main/menu/internal/menu.ts.

This change is currently opt-in via an `animated` parameter on
`MenuAnchor` and `SubmenuButton`. As a result, this is not a breaking
change. Because the PR is already quite large, I chose to not add
animation customization to this PR. I also didn't change any theming
files or `DropdownMenu`.

The only other API addition is a `hoverOpenDelay` parameter on
`SubmenuButton`. This parameter delays the start of a submenu opening by
the specified duration. It is independent of the `animated` parameter.

When `animated == false`, the menu runs its forward and reverse
animations with a duration of 0. I originally disposed of all animation
assets when `animated` was false, but found that the code/testing
complexity to be unwieldy.

<s>Blocked by https://github.com/flutter/flutter/pull/176503</s>

## Examples

* Basic example adapted from material-web


https://github.com/user-attachments/assets/e6d02fab-e11c-4dce-ab82-2fb39c1111e0

* Example showing scrollbar 


https://github.com/user-attachments/assets/847054ba-7218-4e73-9835-019bfa7b5521

* MenuBar usage


https://github.com/user-attachments/assets/8231b1cd-c40c-4f18-af87-1b77b43ecb7b

Thanks to @QuncCccccc and @dkwingsmt for [their work on this
issue.](flutter#143416) Sorry for
throwing a wrench into the original PR by introducing `RawMenuAnchor`...

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

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](https://developers.google.com/gemini-code-assist/docs/review-github-code).
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.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add open and close animations for MenuAnchor

5 participants