I'm making an image gallery and I need the user to be able to long-press an image to show a popup menu which will let him delete the image.
My code, so far:
return GestureDetector( onLongPress: () { showMenu( items: <PopupMenuEntry>[ PopupMenuItem( value: this._index, child: Row( children: <Widget>[ Icon(Icons.delete), Text("Delete"), ], ), ) ], context: context, ); }, child: Image.memory( this._asset.thumbData.buffer.asUint8List(), fit: BoxFit.cover, gaplessPlayback: true, ), );
Which produces:
But also, I couldn't find out how to completely remove the image's widget when the longPress function is called. How to do so?
Flutter Button on Long Press To execute a set of instructions for long-press action, use onLongPress property. We shall use RaisedButton for a button. The working should be same even for other types of Buttons.
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 we focus on an Application, We can see in every Application there is a Pop Up Menu button that will do some work.
To show a popup menu, use the showMenu function. To create a button that shows a popup menu, consider using PopupMenuButton. To show a checkmark next to a popup menu item, consider using CheckedPopupMenuItem. Typically the child of a PopupMenuItem is a Text widget.
The OP and the First Answerer bypassed the original problem using PopupMenuButton
, which worked fine in their case. But I think the more general question of how to position one's own menu and how to receive the user's response without using PopupMenuButton
is worth answering, because sometimes we want a popup menu on a custom widget, and we want it to appear on some gestures other than a simple tap (e.g. the OP's original intention was to long-press).
I set out to make a simple app demonstrating the following:
GestureDetector
to capture long-pressshowMenu()
to display a popup menu, and position it near the finger's touchPopupMenuEntry
that represents multiple values (the oft-used PopupMenuItem
can only represent a single value)The result is, when you long-press on a big yellow area, a popup menu appears on which you can select +1
or -1
, and the big number would increment or decrement accordingly:
Skip to the end for the entire body of code. Comments are sprinkled in there to explain what I am doing. Here are a few things to note:
showMenu()
's position
parameter takes some effort to understand. It's a RelativeRect
, which represents how a smaller rect is positioned inside a bigger rect. In our case, the bigger rect is the entire screen, the smaller rect is the area of touch. Flutter positions the popup menu according to these rules (in plain English):
if the smaller rect leans toward the left half of the bigger rect, the popup menu would align with the smaller rect's left edge
if the smaller rect leans toward the right half of the bigger rect, the popup menu would align with the smaller rect's right edge
if the smaller rect is in the middle, which edge wins depends on the language's text direction. Left edge wins if using English and other left-to-right languages, right edge wins otherwise.
It's always useful to reference PopupMenuButton
's official implementation to see how it uses showMenu()
to display the menu.
showMenu()
returns a Future
. Use Future.then()
to register a callback to handle user selection. Another option is to use await
.
Remember that PopupMenuEntry
is a (subclass of) StatefulWidget
. You can layout any number of sub-widgets inside it. This is how you represent multiple values in a PopupMenuEntry
. If you want it to represent two values, just make it contain two buttons, however you want to lay them out.
To close the popup menu, use Navigator.pop()
. Flutter treats popup menus like a smaller "page". When we display a popup menu, we are actually pushing a "page" to the navigator's stack. To close a popup menu, we pop it from the stack, thus completing the aforementioned Future
.
Here is the full code:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Popup Menu Usage', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Popup Menu Usage'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { var _count = 0; var _tapPosition; void _showCustomMenu() { final RenderBox overlay = Overlay.of(context).context.findRenderObject(); showMenu( context: context, items: <PopupMenuEntry<int>>[PlusMinusEntry()], position: RelativeRect.fromRect( _tapPosition & const Size(40, 40), // smaller rect, the touch area Offset.zero & overlay.size // Bigger rect, the entire screen ) ) // This is how you handle user selection .then<void>((int delta) { // delta would be null if user taps on outside the popup menu // (causing it to close without making selection) if (delta == null) return; setState(() { _count = _count + delta; }); }); // Another option: // // final delta = await showMenu(...); // // Then process `delta` however you want. // Remember to make the surrounding function `async`, that is: // // void _showCustomMenu() async { ... } } void _storePosition(TapDownDetails details) { _tapPosition = details.globalPosition; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ GestureDetector( // This does not give the tap position ... onLongPress: _showCustomMenu, // Have to remember it on tap-down. onTapDown: _storePosition, child: Container( color: Colors.amberAccent, padding: const EdgeInsets.all(100.0), child: Text( '$_count', style: const TextStyle( fontSize: 100, fontWeight: FontWeight.bold), ), ), ), ], ), ), ); } } class PlusMinusEntry extends PopupMenuEntry<int> { @override double height = 100; // height doesn't matter, as long as we are not giving // initialValue to showMenu(). @override bool represents(int n) => n == 1 || n == -1; @override PlusMinusEntryState createState() => PlusMinusEntryState(); } class PlusMinusEntryState extends State<PlusMinusEntry> { void _plus1() { // This is how you close the popup menu and return user selection. Navigator.pop<int>(context, 1); } void _minus1() { Navigator.pop<int>(context, -1); } @override Widget build(BuildContext context) { return Row( children: <Widget>[ Expanded(child: FlatButton(onPressed: _plus1, child: Text('+1'))), Expanded(child: FlatButton(onPressed: _minus1, child: Text('-1'))), ], ); } }
If you are going to use a gridView or listview for laying out the images on the screen, you can wrap each item with a gesture detector then you should keep your images in a list somewhere, then simply remove the image from the list and call setState().
Something like the following. (This code will probably won't compile but it should give you the idea)
ListView.builder( itemCount: imageList.length, itemBuilder: (BuildContext context, int index) { return GestureDetector( onLongPress: () { showMenu( onSelected: () => setState(() => imageList.remove(index))} items: <PopupMenuEntry>[ PopupMenuItem( value: this._index, child: Row( children: <Widget>[ Icon(Icons.delete), Text("Delete"), ], ), ) ], context: context, ); }, child: imageList[index], ); } )
Edit: You can use a popup menu too, like following
Container( margin: EdgeInsets.symmetric(vertical: 10), height: 100, width: 100, child: PopupMenuButton( child: FlutterLogo(), itemBuilder: (context) { return <PopupMenuItem>[new PopupMenuItem(child: Text('Delete'))]; }, ), ),
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With