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 e7e8cec0ca0..6c985b6fe64 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 3fcd018e168..496d06100ae 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 e5a406ce00f..26708ea6c06 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 c357e36527f..caff060e0f6 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 fbcfde70ceb..21cd8aff7c1 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 e40cb4a4eb2..8992b0535f0 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 756082b16c5..c21d4bbee42 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,