Kinda Code
Home/Flutter/Flutter: Showing a Context Menu on Long Press

Flutter: Showing a Context Menu on Long Press

Last updated: September 15, 2023

When developing apps with Flutter, there might be cases where you want to show a context menu when your user long presses on an element. The location of the context menu will be near the press location, as a reasonable thing. This practical article shows you the technique to get it done and then walks you through a complete example of applying that technique in practice.

What Is The Point?

To detect long press on an image or other kind of widget, wrap it with the GestureDetector widget, like so:

GestureDetector(
              // get tap location
              onTapDown: (details) => _getTapPosition(details),
              // show the context menu
              onLongPress: () => _showContextMenu(context),
              child: Image.network(/* ... */),
),

In the code snippet above, the _getTapPosition is used to retain the tap location (where the user’s finger hits the screen). In Flutter, we can obtain the tap position like so:

void _getTapPosition(TapDownDetails details) {
    final RenderBox referenceBox = context.findRenderObject() as RenderBox;
    final tapPosition = referenceBox.globalToLocal(details.globalPosition);
}

The _showContextMenu function, as its name self describes, is used to show the context menu. In Flutter, there is a built-in function named showMenu can help us do the job:

void _showContextMenu(BuildContext context) async {
    final result = await showMenu(
        context: context,

        // Position the context menu
        // It should be placed near the long press location
        position: /* ...*/,

        // set a list of choices for the context menu
        items: [/* ... */]);
}

For more clarity, see the working example below.

Example

App Overview

The sample we’re going to make presents a blue box and an image. When either the box or the image gets a long press, a context menu with 3 options will appear (Add to Favorites, Write Comment, and Hide). The selected option will be printed out (to the terminal). To dismiss the context menu, just tap somewhere outside of it.

If you look closely, you’ll notice that the context menu appears where the long press action takes place but never trespasses on the border of the screen. Here’s the demo:

The Code

The full source code in main.dart with explanations (useMaterial3 is enabled):

// main.dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // Remove the debug banner
      debugShowCheckedModeBanner: false,
      title: 'KindaCode.com',
      theme: ThemeData(useMaterial3: true),
      home: const KindaCodeDemo(),
    );
  }
}

class KindaCodeDemo extends StatefulWidget {
  const KindaCodeDemo({Key? key}) : super(key: key);

  @override
  State<KindaCodeDemo> createState() => _KindaCodeDemoState();
}

class _KindaCodeDemoState extends State<KindaCodeDemo> {
  // Tap location will be used use to position the context menu
  Offset _tapPosition = Offset.zero;
  void _getTapPosition(TapDownDetails details) {
    final RenderBox referenceBox = context.findRenderObject() as RenderBox;
    setState(() {
      _tapPosition = referenceBox.globalToLocal(details.globalPosition);
    });
  }

  // This function will be called when you long press on the blue box or the image
  void _showContextMenu(BuildContext context) async {
    final RenderObject? overlay =
        Overlay.of(context)?.context.findRenderObject();

    final result = await showMenu(
        context: context,

        // Show the context menu at the tap location
        position: RelativeRect.fromRect(
            Rect.fromLTWH(_tapPosition.dx, _tapPosition.dy, 30, 30),
            Rect.fromLTWH(0, 0, overlay!.paintBounds.size.width,
                overlay.paintBounds.size.height)),

        // set a list of choices for the context menu
        items: [
          const PopupMenuItem(
            value: 'favorites',
            child: Text('Add To Favorites'),
          ),
          const PopupMenuItem(
            value: 'comment',
            child: Text('Write Comment'),
          ),
          const PopupMenuItem(
            value: 'hide',
            child: Text('Hide'),
          ),
        ]);

    // Implement the logic for each choice here
    switch (result) {
      case 'favorites':
        debugPrint('Add To Favorites');
        break;
      case 'comment':
        debugPrint('Write Comment');
        break;
      case 'hide':
        debugPrint('Hide');
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('KindaCode.com')),
      body: Padding(
        padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
        child: Column(
          children: [
            GestureDetector(
              // get tap location
              onTapDown: (details) => _getTapPosition(details),
              // show the context menu
              onLongPress: () => _showContextMenu(context),
              child: Container(
                width: double.infinity,
                height: 200,
                color: Colors.blue,
                child: const Center(
                  child: Text(
                    'Hi There',
                    style: TextStyle(fontSize: 30, color: Colors.white),
                  ),
                ),
              ),
            ),
            const SizedBox(
              height: 50,
            ),
            GestureDetector(
              // get tap location
              onTapDown: (details) => _getTapPosition(details),
              // show the context menu
              onLongPress: () => _showContextMenu(context),
              child: Image.network(
                  'https://www.kindacode.com/wp-content/uploads/2022/07/plant-example.jpeg',
                  width: double.infinity,
                  height: 300,
                  fit: BoxFit.cover),
            ),
          ],
        ),
      ),
    );
  }
}

Conclusion

You’ve learned how to show a popup context menu when a widget takes a long press. You’ve also learned how to arrange the context menu in a sensible place. To explore more new and exciting stuff about modern Flutter, take a look at the following articles:

You can also tour around our Flutter topic page or Dart topic page for the most recent tutorials and examples.