draggable_scrollbar.dart 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import 'dart:async';
  2. import 'package:flutter/foundation.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:flutter/services.dart';
  5. import 'package:photos/ui/huge_listview/scroll_bar_thumb.dart';
  6. class DraggableScrollbar extends StatefulWidget {
  7. final Widget child;
  8. final Color backgroundColor;
  9. final Color drawColor;
  10. final double heightScrollThumb;
  11. final EdgeInsetsGeometry padding;
  12. final int totalCount;
  13. final int initialScrollIndex;
  14. final int currentFirstIndex;
  15. final ValueChanged<double> onChange;
  16. final String Function(int) labelTextBuilder;
  17. final bool isEnabled;
  18. const DraggableScrollbar({
  19. Key key,
  20. @required this.child,
  21. this.backgroundColor = Colors.white,
  22. this.drawColor = Colors.grey,
  23. this.heightScrollThumb = 80.0,
  24. this.padding,
  25. this.totalCount = 1,
  26. this.initialScrollIndex = 0,
  27. this.currentFirstIndex = 0,
  28. @required this.labelTextBuilder,
  29. this.onChange,
  30. this.isEnabled = true,
  31. }) : super(key: key);
  32. @override
  33. DraggableScrollbarState createState() => DraggableScrollbarState();
  34. }
  35. class DraggableScrollbarState extends State<DraggableScrollbar>
  36. with TickerProviderStateMixin {
  37. static const thumbAnimationDuration = Duration(milliseconds: 1000);
  38. static const labelAnimationDuration = Duration(milliseconds: 1000);
  39. double thumbOffset = 0.0;
  40. bool isDragging = false;
  41. int currentFirstIndex;
  42. double get thumbMin => 0.0;
  43. double get thumbMax => context.size.height - widget.heightScrollThumb;
  44. AnimationController _thumbAnimationController;
  45. Animation<double> _thumbAnimation;
  46. AnimationController _labelAnimationController;
  47. Animation<double> _labelAnimation;
  48. Timer _fadeoutTimer;
  49. @override
  50. void initState() {
  51. super.initState();
  52. currentFirstIndex = widget.currentFirstIndex;
  53. if (widget.initialScrollIndex > 0 && widget.totalCount > 1) {
  54. WidgetsBinding.instance?.addPostFrameCallback((_) {
  55. setState(
  56. () => thumbOffset = (widget.initialScrollIndex / widget.totalCount) *
  57. (thumbMax - thumbMin),
  58. );
  59. });
  60. }
  61. _thumbAnimationController = AnimationController(
  62. vsync: this,
  63. duration: thumbAnimationDuration,
  64. );
  65. _thumbAnimation = CurvedAnimation(
  66. parent: _thumbAnimationController,
  67. curve: Curves.fastOutSlowIn,
  68. );
  69. _labelAnimationController = AnimationController(
  70. vsync: this,
  71. duration: labelAnimationDuration,
  72. );
  73. _labelAnimation = CurvedAnimation(
  74. parent: _labelAnimationController,
  75. curve: Curves.fastOutSlowIn,
  76. );
  77. }
  78. @override
  79. void dispose() {
  80. _thumbAnimationController.dispose();
  81. _labelAnimationController.dispose();
  82. _fadeoutTimer?.cancel();
  83. super.dispose();
  84. }
  85. @override
  86. Widget build(BuildContext context) {
  87. if (widget.isEnabled) {
  88. return Stack(
  89. children: [
  90. RepaintBoundary(child: widget.child),
  91. RepaintBoundary(child: buildThumb()),
  92. ],
  93. );
  94. } else {
  95. return widget.child;
  96. }
  97. }
  98. Widget buildKeyboard() {
  99. if (defaultTargetPlatform == TargetPlatform.windows) {
  100. return RawKeyboardListener(
  101. focusNode: FocusNode(),
  102. onKey: keyHandler,
  103. child: buildThumb(),
  104. );
  105. } else {
  106. return buildThumb();
  107. }
  108. }
  109. Widget buildThumb() => Padding(
  110. padding: widget.padding,
  111. child: Container(
  112. alignment: Alignment.topRight,
  113. margin: EdgeInsets.only(top: thumbOffset),
  114. child: ScrollBarThumb(
  115. widget.backgroundColor,
  116. widget.drawColor,
  117. widget.heightScrollThumb,
  118. widget.labelTextBuilder.call(currentFirstIndex),
  119. _labelAnimation,
  120. _thumbAnimation,
  121. onDragStart,
  122. onDragUpdate,
  123. onDragEnd,
  124. ),
  125. ),
  126. );
  127. void setPosition(double position, int currentFirstIndex) {
  128. setState(() {
  129. this.currentFirstIndex = currentFirstIndex;
  130. thumbOffset = position * (thumbMax - thumbMin);
  131. if (_thumbAnimationController.status != AnimationStatus.forward) {
  132. _thumbAnimationController.forward();
  133. }
  134. _fadeoutTimer?.cancel();
  135. _fadeoutTimer = Timer(thumbAnimationDuration, () {
  136. _thumbAnimationController.reverse();
  137. _labelAnimationController.reverse();
  138. _fadeoutTimer = null;
  139. });
  140. });
  141. }
  142. void onDragStart(DragStartDetails details) {
  143. setState(() {
  144. isDragging = true;
  145. _labelAnimationController.forward();
  146. _fadeoutTimer?.cancel();
  147. });
  148. }
  149. void onDragUpdate(DragUpdateDetails details) {
  150. setState(() {
  151. if (_thumbAnimationController.status != AnimationStatus.forward) {
  152. _thumbAnimationController.forward();
  153. }
  154. if (isDragging && details.delta.dy != 0) {
  155. thumbOffset += details.delta.dy;
  156. thumbOffset = thumbOffset.clamp(thumbMin, thumbMax);
  157. final double position = thumbOffset / (thumbMax - thumbMin);
  158. widget.onChange?.call(position);
  159. }
  160. });
  161. }
  162. void onDragEnd(DragEndDetails details) {
  163. _fadeoutTimer = Timer(thumbAnimationDuration, () {
  164. _thumbAnimationController.reverse();
  165. _labelAnimationController.reverse();
  166. _fadeoutTimer = null;
  167. });
  168. setState(() => isDragging = false);
  169. }
  170. void keyHandler(RawKeyEvent value) {
  171. if (value.runtimeType == RawKeyDownEvent) {
  172. if (value.logicalKey == LogicalKeyboardKey.arrowDown) {
  173. onDragUpdate(
  174. DragUpdateDetails(
  175. globalPosition: Offset.zero,
  176. delta: const Offset(0, 2),
  177. ),
  178. );
  179. } else if (value.logicalKey == LogicalKeyboardKey.arrowUp) {
  180. onDragUpdate(
  181. DragUpdateDetails(
  182. globalPosition: Offset.zero,
  183. delta: const Offset(0, -2),
  184. ),
  185. );
  186. } else if (value.logicalKey == LogicalKeyboardKey.pageDown) {
  187. onDragUpdate(
  188. DragUpdateDetails(
  189. globalPosition: Offset.zero,
  190. delta: const Offset(0, 25),
  191. ),
  192. );
  193. } else if (value.logicalKey == LogicalKeyboardKey.pageUp) {
  194. onDragUpdate(
  195. DragUpdateDetails(
  196. globalPosition: Offset.zero,
  197. delta: const Offset(0, -25),
  198. ),
  199. );
  200. }
  201. }
  202. }
  203. }