3

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

Maciej Pszczolinski
  • 1,623
  • 1
  • 19
  • 38
  • In web the scroll is taken over by the mouse wheel. But in a mobile device when you drag it first tries to scroll. if you should be able to reorder it immediately and not scroll. On which gesture do you expect the scroll to happen on mobile devices? – Kaushik Chandru Jul 17 '22 at 09:52
  • Scroll would be on the any other element except tapping in drag element (green square in that case). It works like that when reordering "watch later" list in YouTube app. And it has nothing to do with mouse scroll – Maciej Pszczolinski Jul 17 '22 at 09:57

2 Answers2

5

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
    ),
  ),
)
Maciej Pszczolinski
  • 1,623
  • 1
  • 19
  • 38
  • This worked perfectly. Words cannot describe how thankful I am. I have spent hours on this before finding this. One has to search for very specific terms for it to show up on google. – bingo Feb 26 '23 at 00:52
0

Try this

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: Scaffold(
        appBar: AppBar(title: const Text(_title)),
        body: const MyStatefulWidget(),
      ),
    );
  }
}

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

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  final List<int> _items = List<int>.generate(20, (int index) => index);

  @override
  Widget build(BuildContext context) {
    final ColorScheme colorScheme = Theme.of(context).colorScheme;
    final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
    final Color evenItemColor = colorScheme.primary.withOpacity(0.15);

    return ReorderableListView(
      buildDefaultDragHandles: false,
      children: <Widget>[
        for (int index = 0; index < _items.length; index++)
          Container(
            key: Key('$index'),
            color: _items[index].isOdd ? oddItemColor : evenItemColor,
            child: Row(
              children: <Widget>[
                Container(
                  width: 64,
                  height: 64,
                  padding: const EdgeInsets.all(8),
                  child: ReorderableDragStartListener(
                    index: index,
                    child: Card(
                      color: colorScheme.primary,
                      elevation: 2,
                    ),
                  ),
                ),
                Text('Item ${_items[index]}'),
              ],
            ),
          ),
      ],
      onReorder: (int oldIndex, int newIndex) {
        setState(() {
          if (oldIndex < newIndex) {
            newIndex -= 1;
          }
          final int item = _items.removeAt(oldIndex);
          _items.insert(newIndex, item);
        });
      },
    );
  }
}

Kaushik Chandru
  • 15,510
  • 2
  • 12
  • 30