From 7ab122e557b28c868d646099ddc35cd615cb880d Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Wed, 6 Apr 2016 13:28:09 -0700 Subject: [PATCH] PopupMenuButton should lazily build menu items Previously, the client of PopupMenuButton needed to build all the menu times when building the PopupMenuButton. This can get expensive if, for example, each item in a scrollable list has a popup menu associated with it. Now the client passes a builder function to the PopupMenuButton that gets invoked only when its time to show the menu items. --- dev/benchmarks/complex_layout/lib/main.dart | 2 +- .../lib/demo/flexible_space_demo.dart | 2 +- .../lib/demo/leave_behind_demo.dart | 2 +- examples/material_gallery/lib/demo/menu_demo.dart | 10 +++++----- .../lib/demo/scrollable_tabs_demo.dart | 2 +- examples/stocks/lib/stock_home.dart | 2 +- packages/flutter/lib/src/material/popup_menu.dart | 14 ++++++++++---- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/dev/benchmarks/complex_layout/lib/main.dart b/dev/benchmarks/complex_layout/lib/main.dart index e7e8cec0ca..6c985b6fe6 100644 --- a/dev/benchmarks/complex_layout/lib/main.dart +++ b/dev/benchmarks/complex_layout/lib/main.dart @@ -101,7 +101,7 @@ class TopBarMenu extends StatelessWidget { Widget build(BuildContext context) { return new PopupMenuButton( onSelected: (String value) { print("Selected: $value"); }, - items: >[ + itemBuilder: (BuildContext context) => >[ new PopupMenuItem( value: "Friends", child: new MenuItemWithIcon(Icons.people, "Friends", "5 new") diff --git a/examples/material_gallery/lib/demo/flexible_space_demo.dart b/examples/material_gallery/lib/demo/flexible_space_demo.dart index 3fcd018e16..496d06100a 100644 --- a/examples/material_gallery/lib/demo/flexible_space_demo.dart +++ b/examples/material_gallery/lib/demo/flexible_space_demo.dart @@ -110,7 +110,7 @@ class FlexibleSpaceDemoState extends State { _appBarBehavior = value; }); }, - items: >[ + itemBuilder: (BuildContext context) => >[ new PopupMenuItem( value: AppBarBehavior.scroll, child: new Text('AppBar scrolls away') diff --git a/examples/material_gallery/lib/demo/leave_behind_demo.dart b/examples/material_gallery/lib/demo/leave_behind_demo.dart index e5a406ce00..26708ea6c0 100644 --- a/examples/material_gallery/lib/demo/leave_behind_demo.dart +++ b/examples/material_gallery/lib/demo/leave_behind_demo.dart @@ -134,7 +134,7 @@ class LeaveBehindDemoState extends State { actions: [ new PopupMenuButton( onSelected: handleDemoAction, - items: >[ + itemBuilder: (BuildContext context) => >[ new PopupMenuItem( value: LeaveBehindDemoAction.reset, child: new Text('Reset the list') diff --git a/examples/material_gallery/lib/demo/menu_demo.dart b/examples/material_gallery/lib/demo/menu_demo.dart index c357e36527..caff060e0f 100644 --- a/examples/material_gallery/lib/demo/menu_demo.dart +++ b/examples/material_gallery/lib/demo/menu_demo.dart @@ -64,7 +64,7 @@ class MenuDemoState extends State { actions: [ new PopupMenuButton( onSelected: showMenuSelection, - items: >[ + itemBuilder: (BuildContext context) => >[ new PopupMenuItem( value: 'AppBar Menu', child: new Text('AppBar Menu') @@ -91,7 +91,7 @@ class MenuDemoState extends State { title: new Text('An item with a context menu button'), trailing: new PopupMenuButton( onSelected: showMenuSelection, - items: >[ + itemBuilder: (BuildContext context) => >[ new PopupMenuItem( value: _simpleValue1, child: new Text('Context menu item one') @@ -114,7 +114,7 @@ class MenuDemoState extends State { title: new Text('An item with a sectioned menu'), trailing: new PopupMenuButton( onSelected: showMenuSelection, - items: >[ + itemBuilder: (BuildContext context) => >[ new PopupMenuItem( value: 'Preview', child: new ListItem( @@ -157,7 +157,7 @@ class MenuDemoState extends State { title: new Text('An item with a simple menu'), subtitle: new Text(_simpleValue) ), - items: >[ + itemBuilder: (BuildContext context) => >[ new PopupMenuItem( value: _simpleValue1, child: new Text(_simpleValue1) @@ -178,7 +178,7 @@ class MenuDemoState extends State { title: new Text('An item with a checklist menu'), trailing: new PopupMenuButton( onSelected: showCheckedMenuSelections, - items: >[ + itemBuilder: (BuildContext context) => >[ new CheckedPopupMenuItem( value: _checkedValue1, checked: isChecked(_checkedValue1), diff --git a/examples/material_gallery/lib/demo/scrollable_tabs_demo.dart b/examples/material_gallery/lib/demo/scrollable_tabs_demo.dart index fbcfde70ce..21cd8aff7c 100644 --- a/examples/material_gallery/lib/demo/scrollable_tabs_demo.dart +++ b/examples/material_gallery/lib/demo/scrollable_tabs_demo.dart @@ -53,7 +53,7 @@ class ScrollableTabsDemoState extends State { actions: [ new PopupMenuButton( onSelected: changeDemoStyle, - items: >[ + itemBuilder: (BuildContext context) => >[ new PopupMenuItem( value: TabsDemoStyle.iconsAndText, child: new Text('Icons and Text') diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart index e40cb4a4eb..8992b0535f 100644 --- a/examples/stocks/lib/stock_home.dart +++ b/examples/stocks/lib/stock_home.dart @@ -220,7 +220,7 @@ class StockHomeState extends State { ), new PopupMenuButton<_StockMenuItem>( onSelected: (_StockMenuItem value) { _handleStockMenu(context, value); }, - items: >[ + itemBuilder: (BuildContext context) => >[ new CheckedPopupMenuItem<_StockMenuItem>( value: _StockMenuItem.autorefresh, checked: _autorefresh, diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index 756082b16c..c21d4bbee4 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -380,6 +380,9 @@ Future showMenu/**/({ /// its menu to be dismissed. typedef void PopupMenuItemSelected(T value); +/// Signature used by [PopupMenuButton] to lazily construct the items shown when the button is pressed. +typedef List> PopupMenuItemBuilder(BuildContext context); + /// Displays a menu when pressed and calls [onSelected] when the menu is dismissed /// because an item was selected. The value passed to [onSelected] is the value of /// the selected menu item. If child is null then a standard 'navigation/more_vert' @@ -387,15 +390,18 @@ typedef void PopupMenuItemSelected(T value); class PopupMenuButton extends StatefulWidget { PopupMenuButton({ Key key, - this.items, + this.itemBuilder, this.initialValue, this.onSelected, this.tooltip: 'Show menu', this.elevation: 8, this.child - }) : super(key: key); + }) : super(key: key) { + assert(itemBuilder != null); + } - final List> items; + /// Called when the button is pressed to create the items to show in the menu. + final PopupMenuItemBuilder itemBuilder; final T initialValue; @@ -420,7 +426,7 @@ class _PopupMenuButtonState extends State> { showMenu/**/( context: context, elevation: config.elevation, - items: config.items, + items: config.itemBuilder(context), initialValue: config.initialValue, position: new ModalPosition( left: topLeft.x,