androidflutterflutter-material

Flutter ReorderableDragStartListener issue detecting gesture on mobile device (but works fine in web browser)


I want to have reorderable list in flutter with custom drag handle (that works immediately, without long press first).

To achieve that I did:

buildDefaultDragHandles: false,

and I used ReorderableDragStartListener.

code:

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(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<int> _items = List<int>.generate(50, (int index) => index);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: ReorderableListView(
          buildDefaultDragHandles: false,
          children: <Widget>[
            for (int index = 0; index < _items.length; index++)
              Container(
                key: Key('$index'),
                color: _items[index].isOdd ? Colors.blue[100] : Colors.red[100],
                child: Row(
                  children: <Widget>[
                    Container(
                      width: 64,
                      height: 64,
                      padding: const EdgeInsets.all(8),
                      child: ReorderableDragStartListener(
                        index: index,
                        child: Card(
                          color: Colors.green,
                          elevation: 2,
                        ),
                      ),
                    ),
                    Text('Item ${_items[index]}'),
                  ],
                ),
              ),
          ],
          onReorder: (int oldIndex, int newIndex) {
            print('oldIndex $oldIndex, newIndex $newIndex');
          },
        ),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

On desktop (e.g. when run in Edge) it works as expected, drag handle is clicked (mouse down) and dragged up or down to change order.

The problem is on mobile device. When I tap down, and I move finger up or down - the scroll is performed. When however I tap down, and move finger little left or right, and then up/down -> then reordering happens. (tested in android emulator and real android device).

Question is - why on mobile I need to do this little annoying additional left/right move before chaining order? How to fix it?

How it works on desktop (Edge):

enter image description here

How it work on Android (bug!):

enter image description here


Solution

  • I solved it using custom ReorderableDragStartListener, when I set tap delay to 1ms. Since this approach does not require moving finger left/right before dragging, and 1ms is low time, it works like a charm.

    code:

    import 'package:flutter/gestures.dart';
    import 'package:flutter/widgets.dart';
    
    class CustomReorderableDelayedDragStartListener extends ReorderableDragStartListener {
      final Duration delay;
    
      const CustomReorderableDelayedDragStartListener({
        this.delay = kLongPressTimeout,
        Key? key,
        required Widget child,
        required int index,
        bool enabled = true,
      }) : super(key: key, child: child, index: index, enabled: enabled);
    
      @override
      MultiDragGestureRecognizer createRecognizer() {
        return DelayedMultiDragGestureRecognizer(delay: delay, debugOwner: this);
      }
    }
    

    usage:

    CustomReorderableDelayedDragStartListener(
      delay: const Duration(milliseconds: 1), // or any other duration that fits you
      index: widget.index, // passed from parent
      child: Container(
        child: const Icon( // or any other graphical element
          Icons.drag_handle
        ),
      ),
    )