|
| 1 | +import 'dart:developer'; |
| 2 | + |
| 3 | +import 'package:flutter/material.dart'; |
| 4 | +import 'package:flutter/rendering.dart'; |
| 5 | +import 'package:vector_math/vector_math.dart' as vmath; |
| 6 | + |
| 7 | +class ContinousDragGesturesDetection extends StatefulWidget { |
| 8 | + const ContinousDragGesturesDetection({super.key}); |
| 9 | + |
| 10 | + @override |
| 11 | + State<ContinousDragGesturesDetection> createState() => _ContinousDragGesturesDetectionState(); |
| 12 | +} |
| 13 | + |
| 14 | +class _ContinousDragGesturesDetectionState extends State<ContinousDragGesturesDetection> { |
| 15 | + final GlobalKey _parentKey = GlobalKey(); |
| 16 | + |
| 17 | + _detectTapedItem(PointerEvent event) { |
| 18 | + dragPoint = event.localPosition; |
| 19 | + |
| 20 | + hitTestItems(event); |
| 21 | + } |
| 22 | + |
| 23 | + void hitTestItems(PointerEvent event) { |
| 24 | + final RenderBox box = _parentKey.currentContext!.findRenderObject() as RenderBox; |
| 25 | + |
| 26 | + final result = BoxHitTestResult(); |
| 27 | + Offset local = box.globalToLocal(event.position); |
| 28 | + |
| 29 | + if (box.hitTest(result, position: local)) { |
| 30 | + for (final hit in result.path) { |
| 31 | + final target = hit.target; |
| 32 | + |
| 33 | + if (target is _RenderBoxItemDraggable) { |
| 34 | + setState(() { |
| 35 | + activeIndex = target.index; |
| 36 | + selectedIndex.add(target.index); |
| 37 | + log(selectedIndex.length.toString()); |
| 38 | + }); |
| 39 | + } |
| 40 | + } |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + int? activeIndex; |
| 45 | + |
| 46 | + Offset dragPoint = Offset.zero; |
| 47 | + |
| 48 | + // Calculates distance from center of the item to the drag point |
| 49 | + // and returns it as a value between 0 and 1 to use for various animations/effects |
| 50 | + double calculateDistanceValueFromDragPoint(GlobalKey key) { |
| 51 | + if (key.currentContext == null) { |
| 52 | + return 0; |
| 53 | + } |
| 54 | + if (_parentKey.currentContext == null) { |
| 55 | + return 0; |
| 56 | + } |
| 57 | + final RenderBox parent = _parentKey.currentContext!.findRenderObject() as RenderBox; |
| 58 | + final child = key.currentContext!.findRenderObject() as _RenderBoxItemDraggable; |
| 59 | + final childCenter = child.localToGlobal(Offset.zero) + Offset(child.size.width / 2, child.size.height / 2); |
| 60 | + final childPositionWithingParent = parent.globalToLocal(childCenter); |
| 61 | + |
| 62 | + final dragPostionWithinParent = dragPoint; |
| 63 | + |
| 64 | + final childPositionVector = vmath.Vector2(childPositionWithingParent.dx, childPositionWithingParent.dy); |
| 65 | + final dragPositionVector = vmath.Vector2(dragPostionWithinParent.dx, dragPostionWithinParent.dy); |
| 66 | + final childCenterToDragPointCircleRadius = childPositionVector.distanceTo(dragPositionVector); |
| 67 | + final childCoverageArea = child.size.width; |
| 68 | + final valueRatioToCenter = 1 - (childCenterToDragPointCircleRadius / childCoverageArea); |
| 69 | + if (child.index == 4) { |
| 70 | + log(valueRatioToCenter.toString()); |
| 71 | + } |
| 72 | + |
| 73 | + return valueRatioToCenter; |
| 74 | + } |
| 75 | + |
| 76 | + List<GlobalKey> keys = List.generate(9, (index) => GlobalKey()); |
| 77 | + final Set<int> selectedIndex = {}; |
| 78 | + |
| 79 | + @override |
| 80 | + Widget build(BuildContext context) { |
| 81 | + return Scaffold( |
| 82 | + backgroundColor: const Color(0xFF383838), |
| 83 | + body: Column( |
| 84 | + mainAxisAlignment: MainAxisAlignment.center, |
| 85 | + children: [ |
| 86 | + const Text( |
| 87 | + "Selected Index", |
| 88 | + style: TextStyle( |
| 89 | + fontSize: 24, |
| 90 | + color: Colors.white, |
| 91 | + ), |
| 92 | + ), |
| 93 | + Text( |
| 94 | + activeIndex == null ? "None" : activeIndex.toString(), |
| 95 | + style: const TextStyle( |
| 96 | + fontSize: 24, |
| 97 | + color: Colors.white, |
| 98 | + ), |
| 99 | + ), |
| 100 | + const SizedBox( |
| 101 | + height: 30, |
| 102 | + ), |
| 103 | + Listener( |
| 104 | + key: _parentKey, |
| 105 | + onPointerMove: _detectTapedItem, |
| 106 | + onPointerDown: _detectTapedItem, |
| 107 | + onPointerUp: _detectTapedItem, |
| 108 | + child: Padding( |
| 109 | + padding: const EdgeInsets.all(10), |
| 110 | + child: GridView.builder( |
| 111 | + shrinkWrap: true, |
| 112 | + itemCount: 9, |
| 113 | + padding: const EdgeInsets.all(0), |
| 114 | + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( |
| 115 | + crossAxisCount: 3, |
| 116 | + crossAxisSpacing: 10, |
| 117 | + mainAxisSpacing: 10, |
| 118 | + childAspectRatio: 1, |
| 119 | + ), |
| 120 | + physics: const NeverScrollableScrollPhysics(), |
| 121 | + itemBuilder: (context, index) { |
| 122 | + final isActive = activeIndex == index; |
| 123 | + final isSelected = selectedIndex.contains(index); |
| 124 | + final key = keys[index]; |
| 125 | + final value = calculateDistanceValueFromDragPoint(key); |
| 126 | + return ItemDraggable( |
| 127 | + index: index, |
| 128 | + key: key, |
| 129 | + child: SizedBox( |
| 130 | + height: 90, |
| 131 | + child: Stack( |
| 132 | + children: [ |
| 133 | + AnimatedContainer( |
| 134 | + duration: const Duration( |
| 135 | + milliseconds: 300, |
| 136 | + ), |
| 137 | + foregroundDecoration: BoxDecoration( |
| 138 | + gradient: RadialGradient( |
| 139 | + colors: [ |
| 140 | + Colors.transparent, |
| 141 | + const Color.fromARGB(255, 0, 0, 0).withOpacity(value.clamp(0, 1)), |
| 142 | + ], |
| 143 | + stops: const [ |
| 144 | + 0, |
| 145 | + 1.0, |
| 146 | + ], |
| 147 | + ), |
| 148 | + border: Border.all( |
| 149 | + color: const Color.fromARGB(255, 172, 230, 255), |
| 150 | + width: 2, |
| 151 | + ), |
| 152 | + borderRadius: const BorderRadius.all( |
| 153 | + Radius.circular( |
| 154 | + 8, |
| 155 | + ), |
| 156 | + ), |
| 157 | + ), |
| 158 | + decoration: BoxDecoration( |
| 159 | + gradient: RadialGradient( |
| 160 | + colors: [ |
| 161 | + isActive ? const Color(0xFFD3F2FF) : const Color.fromARGB(255, 64, 186, 239), |
| 162 | + const Color.fromARGB(255, 185, 227, 255), |
| 163 | + ], |
| 164 | + stops: const [ |
| 165 | + 0, |
| 166 | + 1, |
| 167 | + ], |
| 168 | + ), |
| 169 | + border: Border.all( |
| 170 | + color: const Color.fromARGB(255, 172, 230, 255), |
| 171 | + width: 2, |
| 172 | + ), |
| 173 | + borderRadius: const BorderRadius.all( |
| 174 | + Radius.circular( |
| 175 | + 10, |
| 176 | + ), |
| 177 | + ), |
| 178 | + ), |
| 179 | + ), |
| 180 | + ], |
| 181 | + ), |
| 182 | + ), |
| 183 | + ); |
| 184 | + }, |
| 185 | + ), |
| 186 | + ), |
| 187 | + ), |
| 188 | + ], |
| 189 | + ), |
| 190 | + ); |
| 191 | + } |
| 192 | +} |
| 193 | + |
| 194 | +class ItemDraggable extends SingleChildRenderObjectWidget { |
| 195 | + const ItemDraggable({ |
| 196 | + super.key, |
| 197 | + super.child, |
| 198 | + required this.index, |
| 199 | + }); |
| 200 | + final int index; |
| 201 | + |
| 202 | + @override |
| 203 | + RenderObject createRenderObject(BuildContext context) { |
| 204 | + return _RenderBoxItemDraggable()..index = index; |
| 205 | + } |
| 206 | + |
| 207 | + @override |
| 208 | + void updateRenderObject(BuildContext context, _RenderBoxItemDraggable renderObject) { |
| 209 | + renderObject.index = index; |
| 210 | + } |
| 211 | +} |
| 212 | + |
| 213 | +class _RenderBoxItemDraggable extends RenderProxyBox { |
| 214 | + late int index; |
| 215 | +} |
0 commit comments