Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter - PopupMenu on long press

Tags:

flutter

dart

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?

like image 601
avi12 Avatar asked Jan 22 '19 01:01

avi12


People also ask

How do you do a long press in flutter?

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.

How do you use Popmenubutton in flutter?

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.

How do you use PopupMenuItem in flutter?

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.


2 Answers

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:

  1. Use a GestureDetector to capture long-press
  2. Use the function showMenu() to display a popup menu, and position it near the finger's touch
  3. How to receive the user's selection
  4. (Bonus) How to make a PopupMenuEntry 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:

Popup Menu Usage App

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:

  1. 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.

  1. showMenu() returns a Future. Use Future.then() to register a callback to handle user selection. Another option is to use await.

  2. 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.

  3. 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'))),       ],     );   } } 
like image 104
Nick Lee Avatar answered Sep 16 '22 11:09

Nick Lee


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'))];     },   ), ), 
like image 41
Mertus Avatar answered Sep 16 '22 11:09

Mertus