diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index b0b722976d..e627b3ea91 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -4708,6 +4708,128 @@ class Wrap extends MultiChildRenderObjectWidget { /// * [CustomMultiChildLayout], which uses a delegate to position multiple /// children. /// * The [catalog of layout widgets](https://flutter.dev/widgets/layout/). +/// +/// {@tool snippet --template=freeform} +/// +/// This example uses the [Flow] widget to create a menu that opens and closes +/// as it is interacted with. The color of the button in the menu changes to +/// indicate which one has been selected. +/// +/// {@animation 450 100 https://flutter.github.io/assets-for-api-docs/assets/widgets/flow_menu.mp4} +/// +/// ```dart main +/// import 'package:flutter/material.dart'; +/// +/// void main() => runApp(FlowApp()); +/// +/// class FlowApp extends StatelessWidget { +/// @override +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: Scaffold( +/// appBar: AppBar( +/// title: const Text('Flow Example'), +/// ), +/// body: FlowMenu(), +/// ), +/// ); +/// } +/// } +/// +/// class FlowMenu extends StatefulWidget { +/// @override +/// _FlowMenuState createState() => _FlowMenuState(); +/// } +/// +/// class _FlowMenuState extends State with SingleTickerProviderStateMixin { +/// AnimationController menuAnimation; +/// IconData lastTapped = Icons.notifications; +/// final List menuItems = [ +/// Icons.home, +/// Icons.new_releases, +/// Icons.notifications, +/// Icons.settings, +/// Icons.menu, +/// ]; +/// +/// void _updateMenu(IconData icon) { +/// if (icon != Icons.menu) +/// setState(() => lastTapped = icon); +/// } +/// +/// @override +/// void initState() { +/// super.initState(); +/// menuAnimation = AnimationController( +/// duration: const Duration(milliseconds: 250), +/// vsync: this, +/// ); +/// } +/// +/// Widget flowMenuItem(IconData icon) { +/// final double buttonDiameter = MediaQuery.of(context).size.width / menuItems.length; +/// return Padding( +/// padding: const EdgeInsets.symmetric(vertical: 8.0), +/// child: RawMaterialButton( +/// fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue, +/// splashColor: Colors.amber[100], +/// shape: CircleBorder(), +/// constraints: BoxConstraints.tight(Size(buttonDiameter, buttonDiameter)), +/// onPressed: () { +/// _updateMenu(icon); +/// menuAnimation.status == AnimationStatus.completed +/// ? menuAnimation.reverse() +/// : menuAnimation.forward(); +/// }, +/// child: Icon( +/// icon, +/// color: Colors.white, +/// size: 45.0, +/// ), +/// ), +/// ); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Container( +/// child: Flow( +/// delegate: FlowMenuDelegate(menuAnimation: menuAnimation), +/// children: menuItems.map((IconData icon) => flowMenuItem(icon)).toList(), +/// ), +/// ); +/// } +/// } +/// +/// class FlowMenuDelegate extends FlowDelegate { +/// FlowMenuDelegate({this.menuAnimation}) : super(repaint: menuAnimation); +/// +/// final Animation menuAnimation; +/// +/// @override +/// bool shouldRepaint(FlowMenuDelegate oldDelegate) { +/// return menuAnimation != oldDelegate.menuAnimation; +/// } +/// +/// @override +/// void paintChildren(FlowPaintingContext context) { +/// double dx = 0.0; +/// for (int i = 0; i < context.childCount; ++i) { +/// dx = context.getChildSize(i).width * i; +/// context.paintChild( +/// i, +/// transform: Matrix4.translationValues( +/// dx * menuAnimation.value, +/// 0, +/// 0, +/// ), +/// ); +/// } +/// } +/// } +/// ``` +/// {@end-tool} +/// class Flow extends MultiChildRenderObjectWidget { /// Creates a flow layout. ///