Flutter: setState does not work during drag

Viewed 140

I have this code, which is an Icon. When the mouse hovers on the icon the cursor changes to SystemMouseCursors.grab, on click it changes to SystemMouseCursors.grabbing, and on move I want this to change to SystemMouseCursors.move.

ReorderableDragStartListener(
  index: index,
  child: InkWell(
    onTapDown: (_) => setState(() {
      grabStarted = true;
    }),
    onTapUp: (_) => setState(() {
      grabStarted = false;
    }),
    onTapCancel: () => setState(() {
      grabStarted = false;
    }),
    mouseCursor: grabStarted
        ? dragStarted
            ? SystemMouseCursors.move
            : SystemMouseCursors.grabbing
        : SystemMouseCursors.grab,
    child: Icon(...

and this section in the reorder part

ReorderableListView(
  ...
  onReorderStart: (index) {
    setState(() {
      dragStarted = true;
    });
  },
  onReorderEnd: (_) {
    setState(() {
      dragStarted = false;
    });
  },
  ...

Changes to the dragStarted flag do not change the cursor. It appears that setState is suspended during drag (which is probably correct, as widget rebuilding during drag can be messy).

Is this correct? And if so, how would I change the cursor to SystemMouseCursors.move?

1 Answers

First of all onTapCancel from inkWell is called before onReorderStart so logic for mouse cursor is not working properly.

I try to find out how ReorderableListView show item on top of list, and it use Overlay. I'm not 100% sure but I think that this item is unmounted from widget tree when it's dragged so that's reason why it's not rebuild. But I'm not sure.

The solution is to use proxyDecorator in ReorderableListView and here set cursor to SystemMouseCursor.move Here is full working web example, I tested it only on web

import 'dart:ui';
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(50, (int index) => index);
  bool dragStarted = false;
  bool grabStarted = false;
  Color colorStar = Colors.black;
  @override
  Widget build(BuildContext context) {
    return ReorderableListView(
      proxyDecorator: (child, index, animation) => MouseRegion(
        cursor: SystemMouseCursors.move,
        child: AnimatedBuilder(
          animation: animation,
          builder: (BuildContext context, Widget? child) {
            final double animValue =
                Curves.easeInOut.transform(animation.value);
            final double elevation = lerpDouble(0, 6, animValue)!;
            return Material(
              elevation: elevation,
              child: Icon(
                Icons.star,
                color: Color.lerp(
                  colorStar,
                  Colors.amber,
                  animValue,
                ),
              ),
            );
          },
        ),
      ),
      buildDefaultDragHandles: false,
      padding: const EdgeInsets.symmetric(horizontal: 40),
      children: [
        for (int index = 0; index < _items.length; index += 1)
          ReorderableDragStartListener(
            index: index,
            key: Key('$index'),
            child: InkWell(
              mouseCursor: grabStarted
                  ? SystemMouseCursors.grabbing
                  : SystemMouseCursors.grab,
              onTapDown: (_) => setState(() => grabStarted = true),
              onTapUp: (_) => setState(() => grabStarted = false),
              child: Icon(
                Icons.star,
                color: colorStar,
              ),
              // mouseCursor: mouseCursor,
            ),
          ),
      ],
      onReorder: (int oldIndex, int newIndex) {
        setState(() {
          if (oldIndex < newIndex) {
            newIndex -= 1;
          }
          final int item = _items.removeAt(oldIndex);
          _items.insert(newIndex, item);
        });
      },
      onReorderStart: (index) {},
      onReorderEnd: (_) => setState(() => grabStarted = false),
    );
  }
}

As you can see I make new widget in proxyDecorator that looks like item I choose to drag.

SetState is not suspended during dragging, only the item that is current in dragging state is not rebuild.

Related