Skip to content
This repository has been archived by the owner on Nov 24, 2022. It is now read-only.

Commit

Permalink
#27 feature: Option that snapping sheet adapt to a scrollable view (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamJonsson authored Mar 14, 2021
1 parent 21fffcb commit bc0f325
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 50 deletions.
5 changes: 0 additions & 5 deletions lib/src/on_drag_wrapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ import 'package:flutter/material.dart';
class OnDragWrapper extends StatelessWidget {
final Widget child;
final Function(double) dragUpdate;
final VoidCallback dragStart;
final VoidCallback dragEnd;

OnDragWrapper(
{Key? key,
required this.dragStart,
required this.dragEnd,
required this.child,
required this.dragUpdate})
Expand All @@ -17,9 +15,6 @@ class OnDragWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onVerticalDragStart: (_) {
this.dragStart();
},
onVerticalDragEnd: (_) {
this.dragEnd();
},
Expand Down
121 changes: 121 additions & 0 deletions lib/src/scroll_controller_override.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import 'package:flutter/widgets.dart';
import 'package:snapping_sheet/snapping_sheet.dart';
import 'package:snapping_sheet/src/sheet_size_calculator.dart';
import 'package:snapping_sheet/src/snapping_calculator.dart';

class ScrollControllerOverride extends StatefulWidget {
final SheetSizeCalculator sizeCalculator;
final ScrollController scrollController;
final SheetLocation sheetLocation;
final Widget child;

final Function(double) dragUpdate;
final VoidCallback dragEnd;
final double currentPosition;
final SnappingCalculator snappingCalculator;

ScrollControllerOverride({
required this.sizeCalculator,
required this.scrollController,
required this.dragUpdate,
required this.dragEnd,
required this.currentPosition,
required this.snappingCalculator,
required this.child,
required this.sheetLocation,
});

@override
_ScrollControllerOverrideState createState() =>
_ScrollControllerOverrideState();
}

class _ScrollControllerOverrideState extends State<ScrollControllerOverride> {
DragDirection? _currentDragDirection;

@override
void initState() {
super.initState();
widget.scrollController.removeListener(_onScrollUpdate);
widget.scrollController.addListener(_onScrollUpdate);
}

@override
void dispose() {
widget.scrollController.removeListener(_onScrollUpdate);
super.dispose();
}

void _onScrollUpdate() {
if (!_allowScrolling) _lockScrollPosition(widget.scrollController);
}

void _overrideScroll(double dragAmount) {
if (!_allowScrolling) widget.dragUpdate(dragAmount);
}

bool get _allowScrolling {
if (widget.sheetLocation == SheetLocation.below) {
if (_currentDragDirection == DragDirection.up) {
if (widget.currentPosition >= _biggestSnapPos)
return true;
else
return false;
}
if (_currentDragDirection == DragDirection.down) {
if (widget.scrollController.position.pixels > 0) return true;
if (widget.currentPosition <= _smallestSnapPos)
return true;
else
return false;
}
}

if (widget.sheetLocation == SheetLocation.above) {
if (_currentDragDirection == DragDirection.down) {
if (widget.currentPosition <= _smallestSnapPos) {
return true;
} else
return false;
}
if (_currentDragDirection == DragDirection.up) {
if (widget.scrollController.position.pixels > 0) return true;
if (widget.currentPosition >= _biggestSnapPos)
return true;
else
return false;
}
}

return false;
}

double get _biggestSnapPos =>
widget.snappingCalculator.getBiggestPositionPixels();
double get _smallestSnapPos =>
widget.snappingCalculator.getSmallestPositionPixels();

void _lockScrollPosition(ScrollController controller) {
controller.position.setPixels(0);
}

void _setDragDirection(double dragAmount) {
this._currentDragDirection =
dragAmount > 0 ? DragDirection.down : DragDirection.up;
print(this._currentDragDirection);
}

@override
Widget build(BuildContext context) {
return Listener(
onPointerMove: (dragEvent) {
_setDragDirection(dragEvent.delta.dy);
_overrideScroll(dragEvent.delta.dy);
},
onPointerUp: (_) {
widget.dragEnd();
},
child: widget.child,
);
}
}
55 changes: 44 additions & 11 deletions lib/src/sheet_content_wrapper.dart
Original file line number Diff line number Diff line change
@@ -1,38 +1,71 @@
import 'package:flutter/widgets.dart';
import 'package:snapping_sheet/src/on_drag_wrapper.dart';
import 'package:snapping_sheet/src/scroll_controller_override.dart';
import 'package:snapping_sheet/src/sheet_size_calculator.dart';
import 'package:snapping_sheet/src/snapping_calculator.dart';
import 'package:snapping_sheet/src/snapping_sheet_content.dart';

class SheetContentWrapper extends StatelessWidget {
class SheetContentWrapper extends StatefulWidget {
final SheetSizeCalculator sizeCalculator;
final SnappingSheetContent? sheetData;

final Function(double) dragUpdate;
final VoidCallback dragStart;
final VoidCallback dragEnd;
final double currentPosition;
final SnappingCalculator snappingCalculator;

const SheetContentWrapper(
{Key? key,
required this.sheetData,
required this.sizeCalculator,
required this.currentPosition,
required this.snappingCalculator,
required this.dragUpdate,
required this.dragStart,
required this.dragEnd})
: super(key: key);

Widget _wrapSheetDataWithDraggable() {
if (!sheetData!.draggable) return sheetData!;
@override
_SheetContentWrapperState createState() => _SheetContentWrapperState();
}

class _SheetContentWrapperState extends State<SheetContentWrapper> {
Widget _wrapWithDragWrapper(Widget child) {
return OnDragWrapper(
dragStart: dragStart,
dragEnd: dragEnd,
child: sheetData!,
dragUpdate: dragUpdate,
dragEnd: widget.dragEnd,
child: child,
dragUpdate: widget.dragUpdate,
);
}

Widget _wrapWithScrollControllerOverride(Widget child) {
return ScrollControllerOverride(
sizeCalculator: widget.sizeCalculator,
scrollController: widget.sheetData!.childScrollController!,
dragUpdate: widget.dragUpdate,
dragEnd: widget.dragEnd,
currentPosition: widget.currentPosition,
snappingCalculator: widget.snappingCalculator,
sheetLocation: widget.sheetData!.location,
child: child,
);
}

Widget _wrapWithNecessaryWidgets(Widget child) {
Widget wrappedChild = child;
if (widget.sheetData!.draggable) {
wrappedChild = _wrapWithDragWrapper(wrappedChild);
}
if (widget.sheetData!.childScrollController != null) {
wrappedChild = _wrapWithScrollControllerOverride(wrappedChild);
}
return wrappedChild;
}

@override
Widget build(BuildContext context) {
if (sheetData == null) return SizedBox();
return sizeCalculator.positionWidget(child: _wrapSheetDataWithDraggable());
if (widget.sheetData == null) return SizedBox();
return widget.sizeCalculator.positionWidget(
child: _wrapWithNecessaryWidgets(widget.sheetData!.child),
);
}
}
3 changes: 3 additions & 0 deletions lib/src/snapping_calculator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ class SnappingCalculator {
this.grabbingHeight,
);

// We have a perfect match. Often happens when overflow drag.
if (posPixels == currentPosition) return true;

bool isAbove = posPixels > currentPosition;

if (isAbove && dragDirection == DragDirection.down) return false;
Expand Down
6 changes: 6 additions & 0 deletions lib/src/snapping_position.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,10 @@ class SnappingPosition {
if (this._positionPixel != null) return this._positionPixel!;
return this._positionFactor! * maxHeight;
}

bool operator ==(other) =>
other is SnappingPosition &&
other._positionFactor == this._positionFactor &&
other._positionPixel == this._positionPixel;
int get hashCode => _positionFactor.hashCode ^ _positionPixel.hashCode;
}
37 changes: 23 additions & 14 deletions lib/src/snapping_sheet_content.dart
Original file line number Diff line number Diff line change
@@ -1,36 +1,45 @@
import 'package:flutter/widgets.dart';
import 'sheet_size_behaviors.dart';

enum SnappingSheetContentSize {
// The size of the sheet content changes to the available height
dynamicSize,

/// The size is static and do not change when the sheet is dragged
staticSize,
enum SheetLocation {
above,
below,
unknown,
}

class SnappingSheetContent extends StatelessWidget {
class SnappingSheetContent {
/// The size behavior of the sheet. Can either be [SheetSizeStatic] or
/// [SheetSizeDynamic].
final SheetSizeBehavior sizeBehavior;
final Widget child;

/// When given a scroll controller that is attached to scrollable view, e.g
/// [ListView] or a [SingleChildScrollView], the sheet will naturally grow
/// and shrink according to the current scroll position of that view.
///
/// OBS, the scrollable view needs to have the [reverse] parameter set to
/// false if located in the below sheet and true if located in the above
/// sheet. Otherwise, the logic wont behave as intended.
final ScrollController? childScrollController;
final bool draggable;
Widget _child;
SheetLocation location = SheetLocation.unknown;

SnappingSheetContent({
Key? key,
required this.child,
required Widget child,
this.draggable = false,
this.sizeBehavior = const SheetSizeDynamic(),
}) : super(key: key);
this.childScrollController,
}) : this._child = child;

double? _getHeight() {
var sizeBehavior = this.sizeBehavior;
if (sizeBehavior is SheetSizeStatic) return sizeBehavior.height;
}

@override
Widget build(BuildContext context) {
Widget get child {
return SizedBox(
height: _getHeight(),
child: this.child,
child: this._child,
);
}
}
Loading

0 comments on commit bc0f325

Please sign in to comment.