From 82dd096f00f3fc1bc7c1d66632595a3cfd8097b5 Mon Sep 17 00:00:00 2001 From: ottodaempfle Date: Thu, 12 Dec 2024 16:41:29 +0100 Subject: [PATCH 1/2] updated horizontal scrolling inside bs --- example/lib/main.dart | 34 +++++++-- lib/scrollable_bottom_sheet.dart | 126 ++++++++++++++++--------------- 2 files changed, 94 insertions(+), 66 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index c92e610..3bf782b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -31,14 +31,34 @@ class MainApp extends StatelessWidget { maxHeight: maxHeight, minHeight: 100, builder: (context, scrollController) { - return ListView.builder( + return SingleChildScrollView( controller: scrollController, - itemCount: 100, - itemBuilder: (context, index) { - return ListTile( - title: Text('Element $index'), - ); - }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text("Horizontal Scroll"), + const SizedBox(height: 8), + SizedBox( + height: 100, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Container( + color: Colors.red, + width: 100, + child: Text(index.toString()), + ), + ); + }, + ), + ), + Container( + height: 200, + ), + ], + ), ); }, borderRadiusTop: 15, diff --git a/lib/scrollable_bottom_sheet.dart b/lib/scrollable_bottom_sheet.dart index 27791be..a82c5a8 100644 --- a/lib/scrollable_bottom_sheet.dart +++ b/lib/scrollable_bottom_sheet.dart @@ -68,8 +68,7 @@ class ScrollableBottomSheet extends StatefulWidget { } } -class ScrollableBottomSheetState extends State - with SingleTickerProviderStateMixin { +class ScrollableBottomSheetState extends State with SingleTickerProviderStateMixin { final _scrollController = ScrollController(); late final AnimationController _animationController; var _isScrollingEnabled = false; @@ -80,11 +79,12 @@ class ScrollableBottomSheetState extends State ScrollHoldController? _hold; - Tween get _sizeTween => - Tween(begin: widget.minHeight, end: widget.maxHeight); + Tween get _sizeTween => Tween(begin: widget.minHeight, end: widget.maxHeight); bool get _isPanelOpen => _animationController.value == 1.0; + bool _isHorizontalScrolling = false; + double _pixelToValue(double pixels) { return (pixels - widget.minHeight) / (widget.maxHeight - widget.minHeight); } @@ -96,9 +96,7 @@ class ScrollableBottomSheetState extends State _animationController = AnimationController( vsync: this, duration: widget.animationDuration, - value: widget.initialPosition == null - ? 0.0 - : _pixelToValue(widget.initialPosition!), + value: widget.initialPosition == null ? 0.0 : _pixelToValue(widget.initialPosition!), )..addListener(_notifyScrollListeners); } @@ -114,8 +112,7 @@ class ScrollableBottomSheetState extends State super.didUpdateWidget(oldWidget); if (!widget.maintainPositionOnConstraintChange) return; - if (oldWidget.maxHeight == widget.maxHeight && - oldWidget.minHeight == widget.minHeight) return; + if (oldWidget.maxHeight == widget.maxHeight && oldWidget.minHeight == widget.minHeight) return; final previousPositionPixels = Tween( begin: oldWidget.minHeight, @@ -127,44 +124,61 @@ class ScrollableBottomSheetState extends State @override Widget build(BuildContext context) { - final borderRadius = - BorderRadius.vertical(top: Radius.circular(widget.borderRadiusTop)); - - return GestureListener( - canDrag: widget.canDrag, - onVerticalDragUpdate: (details) => _onDragUpdate(details), - onVerticalDragEnd: (details) => _onDragEnd(details), - onVerticalDragCancel: () => _handleDragCancel(), - child: MediaQuery.removePadding( - context: context, - removeTop: true, - child: Container( - decoration: BoxDecoration( - boxShadow: widget.shadows, - borderRadius: borderRadius, - color: widget.backgroundColor, - ), - // Use a foreground decoration to make sure we don't allocate more height. - foregroundDecoration: ShapeDecoration( - shape: NonUniformBorder( - topWidth: 2, - color: widget.borderColor, + final borderRadius = BorderRadius.vertical(top: Radius.circular(widget.borderRadiusTop)); + + return NotificationListener( + onNotification: (scrollNotification) { + if (scrollNotification is ScrollStartNotification && scrollNotification.metrics.axis == Axis.horizontal) { + _isHorizontalScrolling = true; + } + + if (scrollNotification is ScrollUpdateNotification && scrollNotification.metrics.axis == Axis.horizontal) { + if (scrollNotification.dragDetails == null) { + _isHorizontalScrolling = false; + } + } + + if (scrollNotification is ScrollEndNotification && scrollNotification.metrics.axis == Axis.horizontal) { + _isHorizontalScrolling = false; + } + + return false; + }, + child: GestureListener( + canDrag: widget.canDrag, + onVerticalDragUpdate: (details) => _onDragUpdate(details), + onVerticalDragEnd: (details) => _onDragEnd(details), + onVerticalDragCancel: () => _handleDragCancel(), + child: MediaQuery.removePadding( + context: context, + removeTop: true, + child: Container( + decoration: BoxDecoration( + boxShadow: widget.shadows, borderRadius: borderRadius, + color: widget.backgroundColor, ), - ), - child: ClipRRect( - borderRadius: borderRadius, - child: AnimatedBuilder( - animation: _animationController, - builder: (context, child) { - return SizedBox( - height: _sizeTween.transform(_animationController.value), - child: child, - ); - }, - child: Builder( - builder: (context) => - widget.builder(context, _scrollController), + // Use a foreground decoration to make sure we don't allocate more height. + foregroundDecoration: ShapeDecoration( + shape: NonUniformBorder( + topWidth: 2, + color: widget.borderColor, + borderRadius: borderRadius, + ), + ), + child: ClipRRect( + borderRadius: borderRadius, + child: AnimatedBuilder( + animation: _animationController, + builder: (context, child) { + return SizedBox( + height: _sizeTween.transform(_animationController.value), + child: child, + ); + }, + child: Builder( + builder: (context) => widget.builder(context, _scrollController), + ), ), ), ), @@ -180,13 +194,13 @@ class ScrollableBottomSheetState extends State final primaryDelta = delta.dy; _didStartScrolling = true; + if (_isHorizontalScrolling) return; + if (_isScrollingEnabled && _isPanelOpen) { // _drag might be null if the drag activity ended and called _disposeDrag. assert(_hold == null || _drag == null); _drag?.update(details); - if (_scrollController.hasClients && - _scrollController.position.pixels <= 0 && - details.primaryDelta! > 0) { + if (_scrollController.hasClients && _scrollController.position.pixels <= 0 && details.primaryDelta! > 0) { setState(() => _isScrollingEnabled = false); _handleDragCancel(); if (_scrollController.position.pixels != 0.0) { @@ -198,16 +212,13 @@ class ScrollableBottomSheetState extends State // only slide the panel if scrolling is not enabled if (!_isScrollingEnabled && !_isScrollingBlocked) { - _animationController.value -= - primaryDelta / (widget.maxHeight - widget.minHeight); + _animationController.value -= primaryDelta / (widget.maxHeight - widget.minHeight); } // if the panel is open and the user hasn't scrolled, we need to determine // whether to enable scrolling if the user swipes up, or disable closing and // begin to close the panel if the user swipes down - if (_isPanelOpen && - _scrollController.hasClients && - _scrollController.offset <= 0) { + if (_isPanelOpen && _scrollController.hasClients && _scrollController.offset <= 0) { final scrollingEnabled = primaryDelta < 0; setState(() => _isScrollingEnabled = scrollingEnabled); @@ -257,11 +268,9 @@ class ScrollableBottomSheetState extends State } final scrollPixelPerSeconds = details.velocity.pixelsPerSecond.dy; - final flingVelocity = - -scrollPixelPerSeconds / (widget.maxHeight - widget.minHeight); + final flingVelocity = -scrollPixelPerSeconds / (widget.maxHeight - widget.minHeight); - final nearestSnapPoint = - _findNearestRelativeSnapPoint(target: _animationController.value); + final nearestSnapPoint = _findNearestRelativeSnapPoint(target: _animationController.value); if (scrollPixelPerSeconds > widget.completeFlingVelocity) { if (flingVelocity.isNegative) { @@ -322,8 +331,7 @@ class ScrollableBottomSheetState extends State } Future animateToNearestSnapPoint() { - final newPosition = - _findNearestRelativeSnapPoint(target: _animationController.value); + final newPosition = _findNearestRelativeSnapPoint(target: _animationController.value); return animateTo( pixels: _sizeTween.transform(newPosition), duration: widget.animationDuration, From ca602e0717093ed611cfca85a97da3da3e190614 Mon Sep 17 00:00:00 2001 From: ottodaempfle Date: Thu, 12 Dec 2024 16:55:51 +0100 Subject: [PATCH 2/2] updated formatting --- lib/scrollable_bottom_sheet.dart | 48 ++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/lib/scrollable_bottom_sheet.dart b/lib/scrollable_bottom_sheet.dart index 790e266..271627f 100644 --- a/lib/scrollable_bottom_sheet.dart +++ b/lib/scrollable_bottom_sheet.dart @@ -68,7 +68,8 @@ class ScrollableBottomSheet extends StatefulWidget { } } -class ScrollableBottomSheetState extends State with SingleTickerProviderStateMixin { +class ScrollableBottomSheetState extends State + with SingleTickerProviderStateMixin { final _scrollController = ScrollController(); late final AnimationController _animationController; var _isScrollingEnabled = false; @@ -79,7 +80,8 @@ class ScrollableBottomSheetState extends State with Singl ScrollHoldController? _hold; - Tween get _sizeTween => Tween(begin: widget.minHeight, end: widget.maxHeight); + Tween get _sizeTween => + Tween(begin: widget.minHeight, end: widget.maxHeight); bool get _isPanelOpen => _animationController.value == 1.0; @@ -96,7 +98,9 @@ class ScrollableBottomSheetState extends State with Singl _animationController = AnimationController( vsync: this, duration: widget.animationDuration, - value: widget.initialPosition == null ? 0.0 : _pixelToValue(widget.initialPosition!), + value: widget.initialPosition == null + ? 0.0 + : _pixelToValue(widget.initialPosition!), )..addListener(_notifyScrollListeners); } @@ -112,7 +116,8 @@ class ScrollableBottomSheetState extends State with Singl super.didUpdateWidget(oldWidget); if (!widget.maintainPositionOnConstraintChange) return; - if (oldWidget.maxHeight == widget.maxHeight && oldWidget.minHeight == widget.minHeight) return; + if (oldWidget.maxHeight == widget.maxHeight && + oldWidget.minHeight == widget.minHeight) return; final previousPositionPixels = Tween( begin: oldWidget.minHeight, @@ -124,21 +129,25 @@ class ScrollableBottomSheetState extends State with Singl @override Widget build(BuildContext context) { - final borderRadius = BorderRadius.vertical(top: Radius.circular(widget.borderRadiusTop)); + final borderRadius = + BorderRadius.vertical(top: Radius.circular(widget.borderRadiusTop)); return NotificationListener( onNotification: (scrollNotification) { - if (scrollNotification is ScrollStartNotification && scrollNotification.metrics.axis == Axis.horizontal) { + if (scrollNotification is ScrollStartNotification && + scrollNotification.metrics.axis == Axis.horizontal) { _isHorizontalScrolling = true; } - if (scrollNotification is ScrollUpdateNotification && scrollNotification.metrics.axis == Axis.horizontal) { + if (scrollNotification is ScrollUpdateNotification && + scrollNotification.metrics.axis == Axis.horizontal) { if (scrollNotification.dragDetails == null) { _isHorizontalScrolling = false; } } - if (scrollNotification is ScrollEndNotification && scrollNotification.metrics.axis == Axis.horizontal) { + if (scrollNotification is ScrollEndNotification && + scrollNotification.metrics.axis == Axis.horizontal) { _isHorizontalScrolling = false; } @@ -177,7 +186,8 @@ class ScrollableBottomSheetState extends State with Singl ); }, child: Builder( - builder: (context) => widget.builder(context, _scrollController), + builder: (context) => + widget.builder(context, _scrollController), ), ), ), @@ -200,7 +210,9 @@ class ScrollableBottomSheetState extends State with Singl // _drag might be null if the drag activity ended and called _disposeDrag. assert(_hold == null || _drag == null); _drag?.update(details); - if (_scrollController.hasClients && _scrollController.position.pixels <= 0 && details.primaryDelta! > 0) { + if (_scrollController.hasClients && + _scrollController.position.pixels <= 0 && + details.primaryDelta! > 0) { setState(() => _isScrollingEnabled = false); _handleDragCancel(); if (_scrollController.position.pixels != 0.0) { @@ -212,13 +224,16 @@ class ScrollableBottomSheetState extends State with Singl // only slide the panel if scrolling is not enabled if (!_isScrollingEnabled && !_isScrollingBlocked) { - _animationController.value -= primaryDelta / (widget.maxHeight - widget.minHeight); + _animationController.value -= + primaryDelta / (widget.maxHeight - widget.minHeight); } // if the panel is open and the user hasn't scrolled, we need to determine // whether to enable scrolling if the user swipes up, or disable closing and // begin to close the panel if the user swipes down - if (_isPanelOpen && _scrollController.hasClients && _scrollController.offset <= 0) { + if (_isPanelOpen && + _scrollController.hasClients && + _scrollController.offset <= 0) { final scrollingEnabled = primaryDelta < 0; setState(() => _isScrollingEnabled = scrollingEnabled); @@ -270,9 +285,11 @@ class ScrollableBottomSheetState extends State with Singl } final scrollPixelPerSeconds = details.velocity.pixelsPerSecond.dy; - final flingVelocity = -scrollPixelPerSeconds / (widget.maxHeight - widget.minHeight); + final flingVelocity = + -scrollPixelPerSeconds / (widget.maxHeight - widget.minHeight); - final nearestSnapPoint = _findNearestRelativeSnapPoint(target: _animationController.value); + final nearestSnapPoint = + _findNearestRelativeSnapPoint(target: _animationController.value); if (scrollPixelPerSeconds > widget.completeFlingVelocity) { if (flingVelocity.isNegative) { @@ -336,7 +353,8 @@ class ScrollableBottomSheetState extends State with Singl } Future animateToNearestSnapPoint() { - final newPosition = _findNearestRelativeSnapPoint(target: _animationController.value); + final newPosition = + _findNearestRelativeSnapPoint(target: _animationController.value); return animateTo( pixels: _sizeTween.transform(newPosition), duration: widget.animationDuration,