Home > Net >  How can I select Widgets by dragging over them but also clicking them individually in flutter?
How can I select Widgets by dragging over them but also clicking them individually in flutter?

Time:12-09

I want to create an Interface in which it is possible to drag your finger over several Areas. This changes the state of the areas to a selected state (See images).

What is the best way to approach this?

Start Position:
Star Position

Start Dragging:
Start Dragging

Select First Area: Select first area

Selected All Areas: Selected all areas

CodePudding user response:

The code needs some updates for current Flutter/Dart versions but this worked for me.

Updated code:


    import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Grid(),
    );
  }
}

class Grid extends StatefulWidget {
  @override
  GridState createState() {
    return new GridState();
  }
}

class GridState extends State<Grid> {
  final Set<int> selectedIndexes = Set<int>();
  final key = GlobalKey();
  final Set<_Foo> _trackTaped = Set<_Foo>();

  _detectTapedItem(PointerEvent event) {
    final RenderBox box = key.currentContext!.findAncestorRenderObjectOfType<RenderBox>()!;
    final result = BoxHitTestResult();
    Offset local = box.globalToLocal(event.position);
    if (box.hitTest(result, position: local)) {
      for (final hit in result.path) {
        /// temporary variable so that the [is] allows access of [index]
        final target = hit.target;
        if (target is _Foo && !_trackTaped.contains(target)) {
          _trackTaped.add(target);
          _selectIndex(target.index);
        }
      }
    }
  }

  _selectIndex(int index) {
    setState(() {
      selectedIndexes.add(index);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Listener(
      onPointerDown: _detectTapedItem,
      onPointerMove: _detectTapedItem,
      onPointerUp: _clearSelection,
      child: GridView.builder(
        key: key,
        itemCount: 6,
        physics: NeverScrollableScrollPhysics(),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          childAspectRatio: 1.0,
          crossAxisSpacing: 5.0,
          mainAxisSpacing: 5.0,
        ),
        itemBuilder: (context, index) {
          return Foo(
            index: index,
            child: Container(
              color: selectedIndexes.contains(index) ? Colors.red : Colors.blue,
            ),
          );
        },
      ),
    );
  }

  void _clearSelection(PointerUpEvent event) {
    _trackTaped.clear();
    setState(() {
      selectedIndexes.clear();
    });
  }
}

class Foo extends SingleChildRenderObjectWidget {
  final int index;

  Foo({required Widget child, required this.index, Key? key}) : super(child: child, key: key);

  @override
  _Foo createRenderObject(BuildContext context) {
    return _Foo(index);
  }

  @override
  void updateRenderObject(BuildContext context, _Foo renderObject) {
    renderObject..index = index;
  }
}

class _Foo extends RenderProxyBox {
  int index;
  _Foo(this.index);
}

CodePudding user response:

I use Rect class.

import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

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

  @override
  _StackOverflowState createState() => _StackOverflowState();
}

class _StackOverflowState extends State<StackOverflow> {
  late List<bool> isSelected;
  late List<GlobalKey> myGlobalKey;
  late List<Offset> offsetWidgets;
  late List<Size> sizeWidgets;
  late List<Rect> listRect;

  @override
  void initState() {
    super.initState();
    isSelected = List.generate(3, (index) => false);
    myGlobalKey = List.generate(3, (index) => GlobalKey());
    offsetWidgets = <Offset>[];
    sizeWidgets = <Size>[];
    listRect = <Rect>[];
    WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
      for (final key in myGlobalKey) {
        sizeWidgets
            .add((key.currentContext!.findRenderObject() as RenderBox).size);
        offsetWidgets.add((key.currentContext!.findRenderObject() as RenderBox)
            .localToGlobal(Offset.zero));
      }
      for (int i = 0; i < 3; i  ) {
        final dx = offsetWidgets[i].dx   sizeWidgets[i].width;
        final dy = offsetWidgets[i].dy   sizeWidgets[i].height;
        listRect.add(Rect.fromPoints(offsetWidgets[i], Offset(dx, dy)));
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Listener(
      onPointerMove: (PointerMoveEvent pointerMoveEvent) {

        if (listRect[0].contains(pointerMoveEvent.position)) {
          if (!isSelected[0]) {
            setState(() {
              isSelected[0] = true;
            });
          }
        } else if (listRect[1].contains(pointerMoveEvent.position)) {
          if (!isSelected[1]) {
            setState(() {
              isSelected[1] = true;
            });
          }
        } else if (listRect[2].contains(pointerMoveEvent.position)) {
          if (!isSelected[2]) {
            setState(() {
              isSelected[2] = true;
            });
          }
        }
      },

      child: Container(
        color: Colors.amber,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            RawMaterialButton(
              key: myGlobalKey[0],
              fillColor: isSelected[0] ? Colors.blueGrey : Colors.transparent,
              shape:
                  const CircleBorder(side: BorderSide(color: Colors.blueGrey)),
              onPressed: () {
                setState(() {
                  isSelected[0] = false;
                });
              },
            ),
            RawMaterialButton(
              key: myGlobalKey[1],
              fillColor: isSelected[1] ? Colors.blueGrey : Colors.transparent,
              shape:
                  const CircleBorder(side: BorderSide(color: Colors.blueGrey)),
              onPressed: () {
                setState(() {
                  isSelected[1] = false;
                });
              },
            ),
            RawMaterialButton(
              key: myGlobalKey[2],
              fillColor: isSelected[2] ? Colors.blueGrey : Colors.transparent,
              shape:
                  const CircleBorder(side: BorderSide(color: Colors.blueGrey)),
              onPressed: () {
                setState(() {
                  isSelected[2] = false;
                });
              },
            ),
          ],
        ),
      ),
    );
  }
}

  • Related