From 93a53dc76469e0faa31caf97d2a843143f8a6347 Mon Sep 17 00:00:00 2001 From: Harshvardhan Joshi Date: Sat, 28 Sep 2019 06:35:10 +0530 Subject: [PATCH] changes: - add horizontal scrollbar support - add horizontal scrollbar tab in example app --- example/lib/main.dart | 79 +++++-- example/pubspec.lock | 268 ++------------------- lib/draggable_scrollbar.dart | 440 ++++++++++++++++++++++------------- pubspec.lock | 270 ++------------------- 4 files changed, 385 insertions(+), 672 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 01aa7c2..63589fb 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -38,12 +38,13 @@ class _MyHomePageState extends State { ScrollController _semicircleController = ScrollController(); ScrollController _arrowsController = ScrollController(); ScrollController _rrectController = ScrollController(); + ScrollController _horizontalController = ScrollController(); ScrollController _customController = ScrollController(); @override Widget build(BuildContext context) { return DefaultTabController( - length: 4, + length: 5, child: Scaffold( appBar: AppBar( title: Text(widget.title), @@ -51,6 +52,7 @@ class _MyHomePageState extends State { Tab(text: 'Semicircle'), Tab(text: 'Arrows'), Tab(text: 'RRect'), + Tab(text: 'Horizontal'), Tab(text: 'Custom'), ]), ), @@ -58,6 +60,7 @@ class _MyHomePageState extends State { SemicircleDemo(controller: _semicircleController), ArrowsDemo(controller: _arrowsController), RRectDemo(controller: _rrectController), + HorizontalScrollbarDemo(controller: _horizontalController), CustomDemo(controller: _customController), ]), ), @@ -81,9 +84,9 @@ class SemicircleDemo extends StatelessWidget { labelTextBuilder: (offset) { final int currentItem = controller.hasClients ? (controller.offset / - controller.position.maxScrollExtent * - numItems) - .floor() + controller.position.maxScrollExtent * + numItems) + .floor() : 0; return Text("$currentItem"); @@ -158,7 +161,7 @@ class RRectDemo extends StatelessWidget { Widget build(BuildContext context) { return DraggableScrollbar.rrect( controller: controller, - labelTextBuilder: (offset) => Text("${offset.floor()}"), + labelTextBuilder: (offset) => Text("${offset ~/ 100}"), child: ListView.builder( controller: controller, itemCount: 1000, @@ -208,21 +211,27 @@ class CustomDemo extends StatelessWidget { ); }, ), - heightScrollThumb: 48.0, + sizeScrollThumb: 48.0, backgroundColor: Colors.blue, - scrollThumbBuilder: ( - Color backgroundColor, - Animation thumbAnimation, - Animation labelAnimation, - double height, { - Text labelText, - BoxConstraints labelConstraints, - }) { + scrollThumbBuilder: (Color backgroundColor, + Animation thumbAnimation, + Animation labelAnimation, + double height, { + Text labelText, + BoxConstraints labelConstraints, + Axis scrollDirection, + }) { + var width = 20.0; + if (!isVertical(scrollDirection)) { + width = width + height; + height = width - height; + width = width - height; + } return FadeTransition( opacity: thumbAnimation, child: Container( height: height, - width: 20.0, + width: width, color: backgroundColor, ), ); @@ -230,3 +239,43 @@ class CustomDemo extends StatelessWidget { ); } } + +/// Horizontal Scrollbar Demo +class HorizontalScrollbarDemo extends StatefulWidget { + final ScrollController controller; + + const HorizontalScrollbarDemo({Key key, this.controller}) : super(key: key); + + @override + _HorizontalScrollbarDemoState createState() => + _HorizontalScrollbarDemoState(); +} + +class _HorizontalScrollbarDemoState extends State { + @override + Widget build(BuildContext context) { + return DraggableScrollbar.rrect( + controller: widget.controller, + labelTextBuilder: (offset) => Text("${offset ~/ 100}"), + child: ListView.builder( + scrollDirection: Axis.horizontal, + controller: widget.controller, + itemCount: 1000, + itemExtent: 100.0, + itemBuilder: (context, index) { + return Container( + padding: EdgeInsets.all(8.0), + child: Material( + elevation: 4.0, + borderRadius: BorderRadius.circular(4.0), + color: Colors.cyan[index % 9 * 100], + child: Center( + child: Text(index.toString()), + ), + ), + ); + }, + ), + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 70aa1ce..7d44722 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,27 +1,13 @@ # Generated by pub -# See https://www.dartlang.org/tools/pub/glossary#lockfile +# See https://dart.dev/tools/pub/glossary#lockfile packages: - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.32.4" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.2.0" boolean_selector: dependency: transitive description: @@ -43,27 +29,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.2" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.5" cupertino_icons: dependency: "direct main" description: @@ -77,7 +42,7 @@ packages: path: ".." relative: true source: path - version: "0.0.3" + version: "0.0.4" flutter: dependency: "direct main" description: flutter @@ -88,90 +53,13 @@ packages: description: flutter source: sdk version: "0.0.0" - front_end: - dependency: transitive - description: - name: front_end - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.4" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.7" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.3+3" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+17" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.5" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.3" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1+1" - json_rpc_2: - dependency: transitive - description: - name: json_rpc_2 - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.9" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.4" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+2" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.3+1" + version: "0.12.5" meta: dependency: transitive description: @@ -179,41 +67,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.6" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6+2" - multi_server_socket: - dependency: transitive - description: - name: multi_server_socket - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - node_preamble: - dependency: transitive - description: - name: node_preamble - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.4" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" path: dependency: transitive description: @@ -221,88 +74,32 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.6.2" - plugin: + pedantic: dependency: transitive description: - name: plugin + name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "0.2.0+3" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.6" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.2" + version: "1.7.0" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.0+1" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.3+3" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - shelf_static: - dependency: transitive - description: - name: shelf_static - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.8" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.2+4" + version: "2.0.3" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.7" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.4.1" + version: "1.5.5" stack_trace: dependency: transitive description: @@ -316,7 +113,7 @@ packages: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "1.6.8" + version: "2.0.0" string_scanner: dependency: transitive description: @@ -330,14 +127,14 @@ packages: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" - test: + version: "1.1.0" + test_api: dependency: transitive description: - name: test + name: test_api url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "0.2.5" typed_data: dependency: transitive description: @@ -345,13 +142,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.6" - utf: - dependency: transitive - description: - name: utf - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.0+5" vector_math: dependency: transitive description: @@ -359,33 +149,5 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" - vm_service_client: - dependency: transitive - description: - name: vm_service_client - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.6" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+10" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.9" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.15" sdks: - dart: ">=2.0.0-dev.68.0 <3.0.0" + dart: ">=2.2.2 <3.0.0" diff --git a/lib/draggable_scrollbar.dart b/lib/draggable_scrollbar.dart index 9573486..308e59b 100755 --- a/lib/draggable_scrollbar.dart +++ b/lib/draggable_scrollbar.dart @@ -4,17 +4,17 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Build the Scroll Thumb and label using the current configuration -typedef Widget ScrollThumbBuilder( - Color backgroundColor, - Animation thumbAnimation, - Animation labelAnimation, - double height, { - Text labelText, - BoxConstraints labelConstraints, -}); +typedef Widget ScrollThumbBuilder(Color backgroundColor, + Animation thumbAnimation, + Animation labelAnimation, + double size, { + Text labelText, + BoxConstraints labelConstraints, + @required Axis scrollDirection, + }); /// Build a Text widget using the current scroll offset -typedef Text LabelTextBuilder(double offsetY); +typedef Text LabelTextBuilder(double positionOffset); /// A widget that will display a BoxScrollView with a ScrollThumb that can be dragged /// for quick navigation of the BoxScrollView. @@ -25,8 +25,8 @@ class DraggableScrollbar extends StatefulWidget { /// A function that builds a thumb using the current configuration final ScrollThumbBuilder scrollThumbBuilder; - /// The height of the scroll thumb - final double heightScrollThumb; + /// The height/length of the scroll thumb + final double sizeScrollThumb; /// The background color of the label and thumb final Color backgroundColor; @@ -55,7 +55,7 @@ class DraggableScrollbar extends StatefulWidget { DraggableScrollbar({ Key key, this.alwaysVisibleScrollThumb = false, - @required this.heightScrollThumb, + @required this.sizeScrollThumb, @required this.backgroundColor, @required this.scrollThumbBuilder, @required this.child, @@ -67,7 +67,6 @@ class DraggableScrollbar extends StatefulWidget { this.labelConstraints, }) : assert(controller != null), assert(scrollThumbBuilder != null), - assert(child.scrollDirection == Axis.vertical), super(key: key); DraggableScrollbar.rrect({ @@ -76,16 +75,16 @@ class DraggableScrollbar extends StatefulWidget { this.alwaysVisibleScrollThumb = false, @required this.child, @required this.controller, - this.heightScrollThumb = 48.0, + this.sizeScrollThumb = 48.0, this.backgroundColor = Colors.white, this.padding, this.scrollbarAnimationDuration = const Duration(milliseconds: 300), this.scrollbarTimeToFade = const Duration(milliseconds: 600), this.labelTextBuilder, this.labelConstraints, - }) : assert(child.scrollDirection == Axis.vertical), - scrollThumbBuilder = - _thumbRRectBuilder(scrollThumbKey, alwaysVisibleScrollThumb), + }) + : scrollThumbBuilder = + _thumbRRectBuilder(scrollThumbKey, alwaysVisibleScrollThumb), super(key: key); DraggableScrollbar.arrows({ @@ -94,16 +93,16 @@ class DraggableScrollbar extends StatefulWidget { this.alwaysVisibleScrollThumb = false, @required this.child, @required this.controller, - this.heightScrollThumb = 48.0, + this.sizeScrollThumb = 48.0, this.backgroundColor = Colors.white, this.padding, this.scrollbarAnimationDuration = const Duration(milliseconds: 300), this.scrollbarTimeToFade = const Duration(milliseconds: 600), this.labelTextBuilder, this.labelConstraints, - }) : assert(child.scrollDirection == Axis.vertical), - scrollThumbBuilder = - _thumbArrowBuilder(scrollThumbKey, alwaysVisibleScrollThumb), + }) + : scrollThumbBuilder = + _thumbArrowBuilder(scrollThumbKey, alwaysVisibleScrollThumb), super(key: key); DraggableScrollbar.semicircle({ @@ -112,44 +111,53 @@ class DraggableScrollbar extends StatefulWidget { this.alwaysVisibleScrollThumb = false, @required this.child, @required this.controller, - this.heightScrollThumb = 48.0, + this.sizeScrollThumb = 48.0, this.backgroundColor = Colors.white, this.padding, this.scrollbarAnimationDuration = const Duration(milliseconds: 300), this.scrollbarTimeToFade = const Duration(milliseconds: 600), this.labelTextBuilder, this.labelConstraints, - }) : assert(child.scrollDirection == Axis.vertical), - scrollThumbBuilder = _thumbSemicircleBuilder( - heightScrollThumb * 0.6, scrollThumbKey, alwaysVisibleScrollThumb), + }) + : scrollThumbBuilder = _thumbSemicircleBuilder( + sizeScrollThumb * 0.6, scrollThumbKey, alwaysVisibleScrollThumb), super(key: key); @override _DraggableScrollbarState createState() => _DraggableScrollbarState(); - static buildScrollThumbAndLabel( - {@required Widget scrollThumb, - @required Color backgroundColor, - @required Animation thumbAnimation, - @required Animation labelAnimation, - @required Text labelText, - @required BoxConstraints labelConstraints, - @required bool alwaysVisibleScrollThumb}) { + static buildScrollThumbAndLabel({ + @required Widget scrollThumb, + @required Color backgroundColor, + @required Animation thumbAnimation, + @required Animation labelAnimation, + @required Text labelText, + @required BoxConstraints labelConstraints, + @required bool alwaysVisibleScrollThumb, + @required bool isVertical, + }) { + var children = [ + ScrollLabel( + animation: labelAnimation, + child: labelText, + backgroundColor: backgroundColor, + constraints: labelConstraints, + ), + scrollThumb, + ]; var scrollThumbAndLabel = labelText == null ? scrollThumb - : Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - ScrollLabel( - animation: labelAnimation, - child: labelText, - backgroundColor: backgroundColor, - constraints: labelConstraints, - ), - scrollThumb, - ], - ); + : (isVertical + ? Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: children, + ) + : Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: children, + )); if (alwaysVisibleScrollThumb) { return scrollThumbAndLabel; @@ -162,14 +170,20 @@ class DraggableScrollbar extends StatefulWidget { static ScrollThumbBuilder _thumbSemicircleBuilder( double width, Key scrollThumbKey, bool alwaysVisibleScrollThumb) { - return ( - Color backgroundColor, - Animation thumbAnimation, - Animation labelAnimation, - double height, { - Text labelText, - BoxConstraints labelConstraints, - }) { + return (Color backgroundColor, + Animation thumbAnimation, + Animation labelAnimation, + double height, { + Text labelText, + BoxConstraints labelConstraints, + Axis scrollDirection, + }) { + var isVerticalAxis = isVertical(scrollDirection); + if (!isVerticalAxis) { + width = width + height; + height = width - height; + width = width - height; + } final scrollThumb = CustomPaint( key: scrollThumbKey, foregroundPainter: ArrowCustomPainter(Colors.grey), @@ -196,31 +210,46 @@ class DraggableScrollbar extends StatefulWidget { labelText: labelText, labelConstraints: labelConstraints, alwaysVisibleScrollThumb: alwaysVisibleScrollThumb, + isVertical: isVerticalAxis, ); }; } static ScrollThumbBuilder _thumbArrowBuilder( Key scrollThumbKey, bool alwaysVisibleScrollThumb) { - return ( - Color backgroundColor, - Animation thumbAnimation, - Animation labelAnimation, - double height, { - Text labelText, - BoxConstraints labelConstraints, - }) { - final scrollThumb = ClipPath( - child: Container( - height: height, - width: 20.0, + return (Color backgroundColor, + Animation thumbAnimation, + Animation labelAnimation, + double size, { + Text labelText, + BoxConstraints labelConstraints, + Axis scrollDirection, + }) { + var container = Container( + height: size, + width: 20.0, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.all( + Radius.circular(12.0), + ), + ), + ); + var isVerticalAxis = isVertical(scrollDirection); + if (!isVerticalAxis) { + container = Container( + height: 20.0, + width: size, decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.all( Radius.circular(12.0), ), ), - ), + ); + } + final scrollThumb = ClipPath( + child: container, clipper: ArrowClipper(), ); @@ -232,25 +261,33 @@ class DraggableScrollbar extends StatefulWidget { labelText: labelText, labelConstraints: labelConstraints, alwaysVisibleScrollThumb: alwaysVisibleScrollThumb, + isVertical: isVerticalAxis, ); }; } static ScrollThumbBuilder _thumbRRectBuilder( Key scrollThumbKey, bool alwaysVisibleScrollThumb) { - return ( - Color backgroundColor, - Animation thumbAnimation, - Animation labelAnimation, - double height, { - Text labelText, - BoxConstraints labelConstraints, - }) { + return (Color backgroundColor, + Animation thumbAnimation, + Animation labelAnimation, + double height, { + Text labelText, + BoxConstraints labelConstraints, + Axis scrollDirection, + }) { + var width = 16.0; + var isVerticalAxis = isVertical(scrollDirection); + if (!isVerticalAxis) { + width = width + height; + height = width - height; + width = width - height; + } final scrollThumb = Material( elevation: 4.0, child: Container( constraints: BoxConstraints.tight( - Size(16.0, height), + Size(width, height), ), ), color: backgroundColor, @@ -265,49 +302,12 @@ class DraggableScrollbar extends StatefulWidget { labelText: labelText, labelConstraints: labelConstraints, alwaysVisibleScrollThumb: alwaysVisibleScrollThumb, + isVertical: isVerticalAxis, ); }; } } -class ScrollLabel extends StatelessWidget { - final Animation animation; - final Color backgroundColor; - final Text child; - - final BoxConstraints constraints; - static const BoxConstraints _defaultConstraints = - BoxConstraints.tightFor(width: 72.0, height: 28.0); - - const ScrollLabel({ - Key key, - @required this.child, - @required this.animation, - @required this.backgroundColor, - this.constraints = _defaultConstraints, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return FadeTransition( - opacity: animation, - child: Container( - margin: EdgeInsets.only(right: 12.0), - child: Material( - elevation: 4.0, - color: backgroundColor, - borderRadius: BorderRadius.all(Radius.circular(16.0)), - child: Container( - constraints: constraints ?? _defaultConstraints, - alignment: Alignment.center, - child: child, - ), - ), - ), - ); - } -} - class _DraggableScrollbarState extends State with TickerProviderStateMixin { double _barOffset; @@ -355,8 +355,13 @@ class _DraggableScrollbarState extends State super.dispose(); } + /// Getter for scroll direction of the child + Axis get scrollDirection => widget.child.scrollDirection; + double get barMaxScrollExtent => - context.size.height - widget.heightScrollThumb; + isVertical(scrollDirection) + ? (context.size.height - widget.sizeScrollThumb) + : (context.size.width - widget.sizeScrollThumb); double get barMinScrollExtent => 0.0; @@ -369,46 +374,73 @@ class _DraggableScrollbarState extends State Widget labelText; if (widget.labelTextBuilder != null && _isDragInProcess) { labelText = widget.labelTextBuilder( - _viewOffset + _barOffset + widget.heightScrollThumb / 2, + _viewOffset + _barOffset + widget.sizeScrollThumb / 2, ); } return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - //print("LayoutBuilder constraints=$constraints"); - - return NotificationListener( - onNotification: (ScrollNotification notification) { - changePosition(notification); - }, - child: Stack( - children: [ - RepaintBoundary( - child: widget.child, - ), - RepaintBoundary( - child: GestureDetector( - onVerticalDragStart: _onVerticalDragStart, - onVerticalDragUpdate: _onVerticalDragUpdate, - onVerticalDragEnd: _onVerticalDragEnd, - child: Container( - alignment: Alignment.topRight, - margin: EdgeInsets.only(top: _barOffset), - padding: widget.padding, - child: widget.scrollThumbBuilder( - widget.backgroundColor, - _thumbAnimation, - _labelAnimation, - widget.heightScrollThumb, + var thumbView = Container( + alignment: Alignment.topRight, + margin: EdgeInsets.only(top: _barOffset), + padding: widget.padding, + child: widget.scrollThumbBuilder(widget.backgroundColor, + _thumbAnimation, _labelAnimation, widget.sizeScrollThumb, + labelText: labelText, + labelConstraints: widget.labelConstraints, + scrollDirection: scrollDirection), + ); + if (!isVertical(scrollDirection)) { + thumbView = Container( + alignment: Alignment.bottomLeft, + margin: EdgeInsets.only( + left: _barOffset, + ), + padding: widget.padding, + child: widget.scrollThumbBuilder(widget.backgroundColor, + _thumbAnimation, _labelAnimation, widget.sizeScrollThumb, labelText: labelText, labelConstraints: widget.labelConstraints, + scrollDirection: scrollDirection), + ); + } + var detectorWidget = GestureDetector( + onVerticalDragStart: _onVerticalDragStart, + onVerticalDragUpdate: _onVerticalDragUpdate, + onVerticalDragEnd: _onVerticalDragEnd, + child: thumbView, + ); + + //if the scrollDirection is not vertical then listen for + // horizontal drag events + if (!isVertical(scrollDirection)) { + detectorWidget = GestureDetector( + onHorizontalDragStart: _onHorizontalDragStart, + onHorizontalDragUpdate: _onHorizontalDragUpdate, + onHorizontalDragEnd: _onHorizontalDragEnd, + child: thumbView, + ); + } + + Widget result = RepaintBoundary( + child: detectorWidget, + ); + + return NotificationListener( + onNotification: (ScrollNotification notification) { + changePosition(notification); + return true; + }, + child: Stack( + children: [ + RepaintBoundary( + child: widget.child, ), - ), - )), - ], - ), - ); - }); + result, + ], + ), + ); + }); } //scroll bar has received notification that it's view was scrolled @@ -459,19 +491,15 @@ class _DraggableScrollbarState extends State }); } - double getBarDelta( - double scrollViewDelta, - double barMaxScrollExtent, - double viewMaxScrollExtent, - ) { + double getBarDelta(double scrollViewDelta, + double barMaxScrollExtent, + double viewMaxScrollExtent,) { return scrollViewDelta * barMaxScrollExtent / viewMaxScrollExtent; } - double getScrollViewDelta( - double barDelta, - double barMaxScrollExtent, - double viewMaxScrollExtent, - ) { + double getScrollViewDelta(double barDelta, + double barMaxScrollExtent, + double viewMaxScrollExtent,) { return barDelta * viewMaxScrollExtent / barMaxScrollExtent; } @@ -523,6 +551,98 @@ class _DraggableScrollbarState extends State _isDragInProcess = false; }); } + + void _onHorizontalDragStart(DragStartDetails details) { + setState(() { + _isDragInProcess = true; + _labelAnimationController.forward(); + _fadeoutTimer?.cancel(); + }); + } + + void _onHorizontalDragUpdate(DragUpdateDetails details) { + setState(() { + if (_thumbAnimationController.status != AnimationStatus.forward) { + _thumbAnimationController.forward(); + } + if (_isDragInProcess) { + _barOffset += details.delta.dx; + + if (_barOffset < barMinScrollExtent) { + _barOffset = barMinScrollExtent; + } + if (_barOffset > barMaxScrollExtent) { + _barOffset = barMaxScrollExtent; + } + + double viewDelta = getScrollViewDelta( + details.delta.dx, barMaxScrollExtent, viewMaxScrollExtent); + + _viewOffset = widget.controller.position.pixels + viewDelta; + if (_viewOffset < widget.controller.position.minScrollExtent) { + _viewOffset = widget.controller.position.minScrollExtent; + } + if (_viewOffset > viewMaxScrollExtent) { + _viewOffset = viewMaxScrollExtent; + } + widget.controller.jumpTo(_viewOffset); + } + }); + } + + void _onHorizontalDragEnd(DragEndDetails details) { + _fadeoutTimer = Timer(widget.scrollbarTimeToFade, () { + _thumbAnimationController.reverse(); + _labelAnimationController.reverse(); + _fadeoutTimer = null; + }); + setState(() { + _isDragInProcess = false; + }); + } +} + +class ScrollLabel extends StatelessWidget { + final Animation animation; + final Color backgroundColor; + final Text child; + + final BoxConstraints constraints; + static const width = 72.0; + static const height = 28.0; + + static const BoxConstraints _defaultConstraints = + BoxConstraints.tightFor(width: width, height: height); + + const ScrollLabel({ + Key key, + @required this.child, + @required this.animation, + @required this.backgroundColor, + this.constraints = _defaultConstraints, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + var margin = EdgeInsets.all(8.0); + + return FadeTransition( + opacity: animation, + child: Container( + margin: margin, + child: Material( + elevation: 4.0, + color: backgroundColor, + borderRadius: BorderRadius.all(Radius.circular(16.0)), + child: Container( + constraints: constraints ?? _defaultConstraints, + alignment: Alignment.center, + child: child, + ), + ), + ), + ); + } } /// Draws 2 triangles like arrow up and arrow down @@ -629,3 +749,9 @@ class SlideFadeTransition extends StatelessWidget { ); } } + +/// Check if the provided scroll direction is +/// equals to [Axis.vertical] or not +bool isVertical(Axis axisType) { + return axisType == Axis.vertical; +} diff --git a/pubspec.lock b/pubspec.lock index 26be4a0..7642229 100755 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,69 +1,34 @@ # Generated by pub -# See https://www.dartlang.org/tools/pub/glossary#lockfile +# See https://dart.dev/tools/pub/glossary#lockfile packages: - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "0.31.2-alpha.2" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.3" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.0.7" + version: "2.2.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.4" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.6" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.3" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.14.4" + version: "1.14.11" flutter: dependency: "direct main" description: flutter @@ -74,290 +39,101 @@ packages: description: flutter source: sdk version: "0.0.0" - front_end: - dependency: transitive - description: - name: front_end - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.0-alpha.12" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.5" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.3" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+16" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.2" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.2+1" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.1" - kernel: - dependency: transitive - description: - name: kernel - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0-alpha.12" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "0.11.3+1" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.2+1" + version: "0.12.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.5" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.6" - multi_server_socket: - dependency: transitive - description: - name: multi_server_socket - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - node_preamble: - dependency: transitive - description: - name: node_preamble - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.1" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" + version: "1.1.6" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.5.1" - plugin: + version: "1.6.2" + pedantic: dependency: transitive description: - name: plugin + name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "0.2.0+2" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.4" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "1.4.1" + version: "1.7.0" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "0.29.0+1" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.3" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - shelf_static: - dependency: transitive - description: - name: shelf_static - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.7" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.2" + version: "2.0.3" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.4" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.5" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "1.5.5" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.2" + version: "1.9.3" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "1.6.6" + version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "1.0.4" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" - test: + version: "1.1.0" + test_api: dependency: transitive description: - name: test + name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.12.37" + version: "0.2.5" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.5" - utf: - dependency: transitive - description: - name: utf - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.0+4" + version: "1.1.6" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.7+7" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.7" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.13" + version: "2.0.8" sdks: - dart: ">=2.0.0-dev.52.0 <=2.0.0-dev.58.0.flutter-f981f09760" + dart: ">=2.2.2 <3.0.0"