draggable_fab.dart 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // ignore_for_file: library_private_types_in_public_api
  2. library draggable_fab;
  3. import 'dart:math';
  4. import 'package:flutter/material.dart';
  5. /// Draggable FAB widget which is always aligned to
  6. /// the edge of the screen - be it left,top, right,bottom
  7. class DraggableFab extends StatefulWidget {
  8. final Widget child;
  9. final Offset? initPosition;
  10. final double securityBottom;
  11. const DraggableFab(
  12. {Key? key, required this.child, this.initPosition, this.securityBottom: 0})
  13. : super(key: key);
  14. @override
  15. _DraggableFabState createState() => _DraggableFabState();
  16. }
  17. class _DraggableFabState extends State<DraggableFab> {
  18. late Size _widgetSize;
  19. double? _left, _top;
  20. double _screenWidth = 0.0, _screenHeight = 0.0;
  21. double? _screenWidthMid, _screenHeightMid;
  22. @override
  23. void initState() {
  24. super.initState();
  25. WidgetsBinding.instance
  26. .addPostFrameCallback((_) => _getWidgetSize(context));
  27. }
  28. void _getWidgetSize(BuildContext context) {
  29. _widgetSize = context.size!;
  30. if (widget.initPosition != null) {
  31. _calculatePosition(widget.initPosition!);
  32. }
  33. }
  34. @override
  35. Widget build(BuildContext context) {
  36. return Stack(children: [
  37. Positioned(
  38. left: this._left,
  39. top: this._top,
  40. child: Draggable(
  41. child: widget.child,
  42. feedback: widget.child,
  43. onDragEnd: _handleDragEnded,
  44. childWhenDragging: Container(
  45. width: 0.0,
  46. height: 0.0,
  47. ),
  48. ),
  49. )
  50. ]);
  51. }
  52. void _handleDragEnded(DraggableDetails draggableDetails) {
  53. this._calculatePosition(draggableDetails.offset);
  54. }
  55. void _calculatePosition(Offset targetOffset) {
  56. if (_screenWidthMid == null || _screenHeightMid == null) {
  57. Size screenSize = MediaQuery.of(context).size;
  58. _screenWidth = screenSize.width;
  59. _screenHeight = screenSize.height;
  60. _screenWidthMid = _screenWidth / 2;
  61. _screenHeightMid = _screenHeight / 2;
  62. }
  63. switch (_getAnchor(targetOffset)) {
  64. case Anchor.LEFT_FIRST:
  65. this._left = _widgetSize.width / 4;
  66. this._top = max(_widgetSize.height / 4, targetOffset.dy);
  67. break;
  68. case Anchor.TOP_FIRST:
  69. this._left = max(_widgetSize.width / 4, targetOffset.dx);
  70. this._top = _widgetSize.height / 4;
  71. break;
  72. case Anchor.RIGHT_SECOND:
  73. this._left = _screenWidth - _widgetSize.width;
  74. this._top = max(_widgetSize.height, targetOffset.dy);
  75. break;
  76. case Anchor.TOP_SECOND:
  77. this._left = min(_screenWidth - _widgetSize.width, targetOffset.dx);
  78. this._top = _widgetSize.height / 2;
  79. break;
  80. case Anchor.LEFT_THIRD:
  81. this._left = _widgetSize.width / 4;
  82. this._top = min(
  83. _screenHeight - _widgetSize.height - widget.securityBottom,
  84. targetOffset.dy);
  85. break;
  86. case Anchor.BOTTOM_THIRD:
  87. this._left = _widgetSize.width / 2;
  88. this._top = _screenHeight - _widgetSize.height - widget.securityBottom;
  89. break;
  90. case Anchor.RIGHT_FOURTH:
  91. this._left = _screenWidth - _widgetSize.width;
  92. this._top = min(
  93. _screenHeight - _widgetSize.height - widget.securityBottom,
  94. targetOffset.dy);
  95. break;
  96. case Anchor.BOTTOM_FOURTH:
  97. this._left = _screenWidth - _widgetSize.width;
  98. this._top = _screenHeight - _widgetSize.height - widget.securityBottom;
  99. break;
  100. }
  101. setState(() {});
  102. }
  103. /// Computes the appropriate anchor screen edge for the widget
  104. Anchor _getAnchor(Offset position) {
  105. if (position.dx < _screenWidthMid! && position.dy < _screenHeightMid!) {
  106. return position.dx < position.dy ? Anchor.LEFT_FIRST : Anchor.TOP_FIRST;
  107. } else if (position.dx >= _screenWidthMid! &&
  108. position.dy < _screenHeightMid!) {
  109. return _screenWidth - position.dx < position.dy
  110. ? Anchor.RIGHT_SECOND
  111. : Anchor.TOP_SECOND;
  112. } else if (position.dx < _screenWidthMid! &&
  113. position.dy >= _screenHeightMid!) {
  114. return position.dx < _screenHeight - position.dy
  115. ? Anchor.LEFT_THIRD
  116. : Anchor.BOTTOM_THIRD;
  117. } else {
  118. return _screenWidth - position.dx < _screenHeight - position.dy
  119. ? Anchor.RIGHT_FOURTH
  120. : Anchor.BOTTOM_FOURTH;
  121. }
  122. }
  123. }
  124. /// #######################################
  125. /// # | # | #
  126. /// # TOP_FIRST # TOP_SECOND #
  127. /// # - LEFT_FIRST # RIGHT_SECOND - #
  128. /// #######################################
  129. /// # - LEFT_THIRD # RIGHT_FOURTH - #
  130. /// # BOTTOM_THIRD # BOTTOM_FOURTH #
  131. /// # | # | #
  132. /// #######################################
  133. enum Anchor {
  134. LEFT_FIRST,
  135. TOP_FIRST,
  136. RIGHT_SECOND,
  137. TOP_SECOND,
  138. LEFT_THIRD,
  139. BOTTOM_THIRD,
  140. RIGHT_FOURTH,
  141. BOTTOM_FOURTH
  142. }