Saltar al contenido

Flutter – PopupMenu en pulsación larga

Buscamos en distintos espacios para así darte la solución para tu dilema, si tienes alguna pregunta deja un comentario y respondemos sin falta, porque estamos para servirte.

Solución:

El OP y el Primer Respondedor evitaron el problema original usando PopupMenuButton, que funcionó bien en su caso. Pero creo que la pregunta más general de cómo posicionar el propio menú y cómo recibir la respuesta del usuario sin uso PopupMenuButton Vale la pena responder, porque a veces queremos un menú emergente en un widget personalizado, y queremos que aparezca en algunos gestos que no sean un simple toque (por ejemplo, la intención original del OP era mantener presionado).

Me propuse hacer una aplicación simple que demuestre lo siguiente:

  1. Utilizar una GestureDetector para capturar pulsación larga
  2. Usa la función showMenu() para mostrar un menú emergente y colóquelo cerca del toque del dedo
  3. Cómo recibir la selección del usuario
  4. (Bonificación) Cómo hacer un PopupMenuEntry que representa varios valores (el utilizado con frecuencia PopupMenuItem solo puede representar un valor único)

El resultado es que, cuando mantienes presionada una gran área amarilla, aparece un menú emergente en el que puedes seleccionar +1 o -1, y el número grande aumentaría o disminuiría en consecuencia:

Aplicación de uso de menú emergente

Vaya al final para todo el cuerpo del código. Los comentarios se esparcen allí para explicar lo que estoy haciendo. Aquí hay algunas cosas a tener en cuenta:

  1. showMenu()‘s position El parámetro requiere algo de esfuerzo para comprenderlo. Es un RelativeRect, que representa cómo se coloca un rectángulo más pequeño dentro de un rectángulo más grande. En nuestro caso, el rectángulo más grande es toda la pantalla, el rectángulo más pequeño es el área táctil. Flutter coloca el menú emergente de acuerdo con estas reglas (en inglés simple):

    • si el recto más pequeño se inclina hacia el izquierda mitad del rect más grande, el menú emergente se alinearía con el borde izquierdo de rect más pequeño

    • si el recto más pequeño se inclina hacia el Derecha mitad del rect más grande, el menú emergente se alinearía con el borde derecho más pequeño

    • si el rectángulo más pequeño está en el medio, qué borde gana depende de la dirección del texto del idioma. Borde izquierdo gana si usa inglés y otros idiomas de izquierda a derecha, el borde derecho gana en caso contrario.

Siempre es útil hacer referencia PopupMenuButtonimplementación oficial para ver cómo se usa showMenu() para mostrar el menú.

  1. showMenu() devuelve un Future. Usar Future.then() para registrar una devolución de llamada para manejar la selección del usuario. Otra opción es usar await.

  2. Recuérdalo PopupMenuEntry es una (subclase de) StatefulWidget. Puede diseñar cualquier número de sub-widgets dentro de él. Así es como se representan varios valores en una PopupMenuEntry. Si quieres que represente dos valores, haz que contenga dos botones, como quieras distribuirlos.

  3. Para cerrar el menú emergente, use Navigator.pop(). Flutter trata los menús emergentes como una “página” más pequeña. Cuando mostramos un menú emergente, en realidad estamos empujando una “página” a la pila del navegador. Para cerrar un menú emergente, lo sacamos de la pila, completando así el mencionado Future.

Aquí está el código completo:

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 
  var _count = 0;
  var _tapPosition;

  void _showCustomMenu() 
    final RenderBox overlay = Overlay.of(context).context.findRenderObject();

    showMenu(
      context: context,
      items: >[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((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: [
            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 
  @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 

class PlusMinusEntryState extends State 
  void _plus1() 
    // This is how you close the popup menu and return user selection.
    Navigator.pop(context, 1);
  

  void _minus1() 
    Navigator.pop(context, -1);
  

  @override
  Widget build(BuildContext context) 
    return Row(
      children: [
        Expanded(child: FlatButton(onPressed: _plus1, child: Text('+1'))),
        Expanded(child: FlatButton(onPressed: _minus1, child: Text('-1'))),
      ],
    );
  

Si va a usar un gridView o listview para diseñar las imágenes en la pantalla, puede envolver cada elemento con un detector de gestos, luego debe mantener sus imágenes en una lista en algún lugar, luego simplemente elimine la imagen de la lista y llame a setState ().

Algo parecido a lo siguiente. (Este código probablemente no se compilará, pero debería darle la idea)

    ListView.builder(
        itemCount: imageList.length,
        itemBuilder: (BuildContext context, int index) 
          return GestureDetector(
                onLongPress: () 
                  showMenu(
                    onSelected: () => setState(() => imageList.remove(index))
                    items: [
                      PopupMenuItem(
                        value: this._index,
                        child: Row(
                          children: [
                            Icon(Icons.delete),
                            Text("Delete"),
                          ],
                        ),
                      )
                    ],
                    context: context,
                  );
                ,
                child: imageList[index],
            );
          }
       )

Editar: también puede usar un menú emergente, como seguir

Container(
  margin: EdgeInsets.symmetric(vertical: 10),
  height: 100,
  width: 100,
  child: PopupMenuButton(
    child: FlutterLogo(),
    itemBuilder: (context) 
      return [new PopupMenuItem(child: Text('Delete'))];
    ,
  ),
),

La respuesta de Nick Lee se puede convertir en una mezcla con bastante facilidad, que luego se puede usar en cualquier lugar donde desee usar un menú emergente.

El mixin:

import 'package:flutter/material.dart' hide showMenu;
import 'package:flutter/material.dart' as material show showMenu;

/// A mixin to provide convenience methods to record a tap position and show a popup menu.
mixin CustomPopupMenu on State 
  Offset _tapPosition;

  /// Pass this method to an onTapDown parameter to record the tap position.
  void storePosition(TapDownDetails details) => _tapPosition = details.globalPosition;

  /// Use this method to show the menu.
  Future showMenu(
    @required BuildContext context,
    @required List> items,
    T initialValue,
    double elevation,
    String semanticLabel,
    ShapeBorder shape,
    Color color,
    bool captureInheritedThemes = true,
    bool useRootNavigator = false,
  ) 
    final RenderBox overlay = Overlay.of(context).context.findRenderObject();

    return material.showMenu(
      context: context,
      position: RelativeRect.fromLTRB(
        _tapPosition.dx,
        _tapPosition.dy,
        overlay.size.width - _tapPosition.dx,
        overlay.size.height - _tapPosition.dy,
      ),
      items: items,
      initialValue: initialValue,
      elevation: elevation,
      semanticLabel: semanticLabel,
      shape: shape,
      color: color,
      captureInheritedThemes: captureInheritedThemes,
      useRootNavigator: useRootNavigator,
    );
  

Y luego, para usarlo:

import 'package:flutter/material.dart';

import './custom_context_menu.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 with CustomPopupMenu 
  var _count = 0;

  void _showCustomMenu() 
    this.showMenu(
      context: context,
      items: >[PlusMinusEntry()],
    )
    // This is how you handle user selection
    .then((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  ... 
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            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  n == -1;

  @override
  PlusMinusEntryState createState() => PlusMinusEntryState();


class PlusMinusEntryState extends State 
  void _plus1() 
    // This is how you close the popup menu and return user selection.
    Navigator.pop(context, 1);
  

  void _minus1() 
    Navigator.pop(context, -1);
  

  @override
  Widget build(BuildContext context) 
    return Row(
      children: [
        Expanded(child: FlatButton(onPressed: _plus1, child: Text('+1'))),
        Expanded(child: FlatButton(onPressed: _minus1, child: Text('-1'))),
      ],
    );
  

Eres capaz de añadir valor a nuestro contenido informacional colaborando tu experiencia en los informes.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *