How to swipe/drag 2 or more buttons in a grid of buttons using flutter How to swipe/drag 2 or more buttons in a grid of buttons using flutter dart dart

How to swipe/drag 2 or more buttons in a grid of buttons using flutter


You can manually hit test RenderBox and extract a specific RenderObject of your choice.

We could for example add the following renderobject above our buttons:

class Foo extends SingleChildRenderObjectWidget {  final int index;  Foo({Widget child, this.index, Key key}) : super(child: child, key: key);  @override  RenderObject createRenderObject(BuildContext context) {    return _Foo()..index = index;  }  @override  void updateRenderObject(BuildContext context, _Foo renderObject) {    renderObject..index = index;  }}class _Foo extends RenderProxyBox {  int index;}

Then use a Listener to extract all _Foo found under the pointer.

Here's a full application using this principle:

enter image description here

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.findRenderObject();    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({Widget child, this.index, Key key}) : super(child: child, key: key);  @override  _Foo createRenderObject(BuildContext context) {    return _Foo()..index = index;  }  @override  void updateRenderObject(BuildContext context, _Foo renderObject) {    renderObject..index = index;  }}class _Foo extends RenderProxyBox {  int index;}


I don't like this code at all, but it seems to be working

import 'package:flutter/material.dart';class TestScaffold extends StatefulWidget {  @override  State<StatefulWidget> createState() => _TestScaffoldState();}List<_SquareButton> _selectedList = [];class _TestScaffoldState extends State<TestScaffold> {  List<_SquareButton> buttons = [    _SquareButton('1'),    _SquareButton('2'),    _SquareButton('3'),    _SquareButton('4'),    _SquareButton('5'),    _SquareButton('6'),    _SquareButton('7'),    _SquareButton('8'),    _SquareButton('9'),    _SquareButton('10'),    _SquareButton('11'),    _SquareButton('12'),    _SquareButton('13'),    _SquareButton('14'),    _SquareButton('15'),    _SquareButton('16'),  ];  Map<Rect, _SquareButton> positions = {};  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(title: Text('Test'),),      body: GestureDetector(        onPanDown: (details) {          checkGesture(details.globalPosition);        },        onPanUpdate: (details) {          checkGesture(details.globalPosition);        },        child: GridView.count(crossAxisCount: 4,        physics: NeverScrollableScrollPhysics(),        children: buttons,),)    );  }  initPositions() {    if (positions.isNotEmpty) return;    buttons.forEach((btn) {      RenderBox box = btn.bKey.currentContext.findRenderObject();      Offset start = box.localToGlobal(Offset.zero);      Rect rect = Rect.fromLTWH(start.dx, start.dy, box.size.width, box.size.height);      positions.addAll({rect: btn});    });  }  checkGesture(Offset position) {    initPositions();    positions.forEach((rect, btn) {      if (rect.contains(position)) {        if (!_selectedList.contains(btn)) {            _selectedList.add(btn);        btn.state.setState((){});        }      }    });  }}class _SquareButton extends StatefulWidget {  _SquareButton(this.title);  final String title;  final GlobalKey bKey = GlobalKey();  State state;  @override  State<StatefulWidget> createState() {    state = _SquareButtonState();    return state;  }}class _SquareButtonState extends State<_SquareButton> {  @override  Widget build(BuildContext context) {    return Padding(key: widget.bKey, padding: EdgeInsets.all(4.0), child: Container(        color: _selectedList.contains(widget) ? Colors.tealAccent : Colors.teal,        child: Text(widget.title),        alignment: Alignment.center,      ),);  }}

There is a moment. If you enable scrolling - GestureDetector not always work on vertical movements