dart - Flutter - 长按弹出菜单

我正在制作一个图片库,我需要用户能够长按图片以显示一个弹出菜单,让他可以删除图片。

我的代码,到目前为止:

  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,
    ),
  );

产生:

但是,当调用 longPress 函数时,我不知道如何完全删除图像的小部件。该怎么做?

最佳答案

OP 和 First Answerer 使用 PopupMenuButton 绕过了原始问题,这在他们的情况下运行良好。但我认为如何定位自己的菜单以及如何在不使用 PopupMenuButton 的情况下接收用户响应的更一般的问题值得回答,因为有时我们希望在一个自定义小部件,我们希望它出现在一些手势上,而不是简单的点击(例如,OP 的初衷是长按)。

我开始制作一个简单的应用程序来演示以下内容:

  1. 使用 GestureDetector 捕捉长按
  2. 使用函数showMenu()显示弹出菜单,并将其定位在手指触摸的附近
  3. 如何接收用户的选择
  4. (Bonus) 如何制作一个代表多个值的PopupMenuEntry(常用的PopupMenuItem只能代表一个值)

结果是,当你长按一个大的黄色区域时,会出现一个弹出菜单,您可以在其中选择 +1-1,大number 会相应地增加或减少:

整个代码体跳到最后。评论被洒在那里解释我在做什么。这里有几点需要注意:

  1. showMenu()position 参数需要一些努力才能理解。它是一个 RelativeRect ,它表示较小的矩形如何定位在较大的矩形内。在我们的例子中,较大的矩形是整个屏幕,较小的矩形是触摸区域。 Flutter 根据这些规则定位弹出菜单(用简单的英语):

    • 如果较小的矩形向较大矩形的一半倾斜,弹出菜单将与较小矩形的左边缘对齐

    • 如果较小的矩形向较大矩形的一半倾斜,弹出菜单将与较小矩形的右边缘对齐

    • 如果较小的矩形在中间,则哪个边获胜取决于语言的文本方向。如果使用英语和其他从左到右的语言,左边缘获胜,否则右边缘获胜。

引用 PopupMenuButton's official implementation 来了解它如何使用 showMenu() 来显示菜单总是很有用的。

  1. showMenu() 返回一个 Future。使用 Future.then() 注册回调以处理用户选择。另一种选择是使用 await

  2. 请记住,PopupMenuEntryStatefulWidget 的(子类)。您可以在其中布局任意数量的子小部件。这就是您在 PopupMenuEntry 中表示多个值的方式。如果你想让它代表两个值,只要让它包含两个按钮,不管你想布局它们。

  3. 要关闭弹出菜单,请使用 Navigator.pop() 。 Flutter 将弹出菜单视为较小的“页面”。当我们显示一个弹出菜单时,我们实际上是在将一个“页面”推送到导航器的堆栈中。要关闭一个弹出菜单,我们将它从堆栈中弹出,从而完成前面提到的Future

这里是完整的代码:

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'))),
      ],
    );
  }
}

https://stackoverflow.com/questions/54300081/

相关文章:

listview - 如何通过 Flutter 中的 RaisedButton 传递/绑定(bind

flutter - 控制和禁用 flutter 中的下拉按钮?

sqlite - 如何在 Flutter 中使用 SQFlite 进行数据库查询

dart - 在 Dart 中向服务器发出多个独立请求的最佳方式

image - Flutter CircleAvatar backgroundImage没有填满圆圈

firebase - 如何在 Flutter 中从 Firebase Auth 获取用户 ID?

dart - 这个 Dart 错误处理程序有什么问题?

android - 如何设置 Flutter 应用的构建和版本号

flutter - 流生成器。错误状态 : Stream has already been list

build - 如何在 Flutter 中修复 "Gradle build failed to pr