Jaime Blasco

Add custom curve when not dragging

... ... @@ -8,6 +8,7 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
class CupertinoSharePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBar(context),
body: CupertinoPageScaffold(
... ...
... ... @@ -162,6 +162,7 @@ class _MyHomePageState extends State<MyHomePage> {
title: Text('Cupertino Modal fit'),
onTap: () => showCupertinoModalBottomSheet(
expand: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
... ... @@ -173,6 +174,7 @@ class _MyHomePageState extends State<MyHomePage> {
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
animationCurve: Curves.easeInOutCubic,
backgroundColor: Colors.transparent,
builder: (context, scrollController) =>
ModalFit(scrollController: scrollController),
... ...
... ... @@ -9,7 +9,7 @@ class NestedScrollModal extends StatelessWidget {
@override
Widget build(BuildContext context) {
return NestedScrollView(
controller: ScrollController(),
physics: ScrollPhysics(parent: PageScrollPhysics()),
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
... ... @@ -31,6 +31,7 @@ class NestedScrollModal extends StatelessWidget {
);
},
itemCount: 12,
));
),
);
}
}
... ...
... ... @@ -11,6 +11,12 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:modal_bottom_sheet/src/utils/bottom_sheet_suspended_curve.dart';
import 'package:modal_bottom_sheet/src/utils/primary_scroll_status_bar.dart';
const Curve _decelerateEasing = Cubic(0.0, 0.0, 0.2, 1.0);
const Curve _modalBottomSheetCurve = _decelerateEasing;
const Duration _bottomSheetDuration = Duration(milliseconds: 400);
const double _minFlingVelocity = 500.0;
const double _closeProgressThreshold = 0.6;
... ... @@ -61,7 +67,7 @@ class ModalBottomSheet extends StatefulWidget {
/// The curve used by the animation showing and dismissing the bottom sheet.
///
/// If no curve is provided it falls back to `Curves.easeOutSine`.
/// If no curve is provided it falls back to `decelerateEasing`.
final Curve animationCurve;
/// Allows the bottom sheet to go beyond the top bound of the content,
... ... @@ -176,7 +182,10 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
return result;
}
ParametricCurve<double> animationCurve;
void _handleDragUpdate(double primaryDelta) async {
animationCurve = Curves.linear;
assert(widget.enableDrag, 'Dragging is disabled');
if (_dismissUnderway) return;
... ... @@ -210,6 +219,11 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
void _handleDragEnd(double velocity) async {
assert(widget.enableDrag, 'Dragging is disabled');
animationCurve = BottomSheetSuspendedCurve(
widget.animationController.value,
curve: _defaultCurve,
);
if (_dismissUnderway || !isDragging) return;
isDragging = false;
_bounceDragController.reverse();
... ... @@ -302,8 +316,12 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
}
}
ParametricCurve<double> get _defaultCurve =>
widget.animationCurve ?? _modalBottomSheetCurve;
@override
void initState() {
animationCurve = _defaultCurve;
_bounceDragController =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_scrollController = widget.scrollController ?? ScrollController();
... ... @@ -328,19 +346,17 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
);
}
// Todo: Add curved Animation when push and pop without gesture
final Animation<double> containerAnimation = CurvedAnimation(
parent: widget.animationController,
curve: widget.animationCurve ?? Curves.linear,
);
final mediaQuery = MediaQuery.of(context);
return AnimatedBuilder(
child = AnimatedBuilder(
animation: widget.animationController,
builder: (context, _) => ClipRect(
child: CustomSingleChildLayout(
delegate: _ModalBottomSheetLayout(
containerAnimation.value, widget.expanded),
child: !widget.enableDrag
builder: (context, child) {
final animationValue = animationCurve.transform(
mediaQuery.accessibleNavigation
? 1.0
: widget.animationController.value);
final draggableChild = !widget.enableDrag
? child
: KeyedSubtree(
key: _childKey,
... ... @@ -349,10 +365,12 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
builder: (context, _) => CustomSingleChildLayout(
delegate: _CustomBottomSheetLayout(bounceAnimation.value),
child: GestureDetector(
onVerticalDragUpdate: (details) =>
_handleDragUpdate(details.primaryDelta),
onVerticalDragEnd: (details) =>
_handleDragEnd(details.primaryVelocity),
onVerticalDragUpdate: (details) {
_handleDragUpdate(details.primaryDelta);
},
onVerticalDragEnd: (details) {
_handleDragEnd(details.primaryVelocity);
},
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
_handleScrollUpdate(notification);
... ... @@ -363,10 +381,22 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
),
),
),
);
return ClipRect(
child: CustomSingleChildLayout(
delegate: _ModalBottomSheetLayout(
animationValue,
widget.expanded,
),
child: draggableChild,
),
),
);
},
child: RepaintBoundary(child: child),
);
return PrimaryScrollStatusBarHandler(
scrollController: _scrollController, child: child);
}
}
... ...
import 'dart:ui';
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart';
// Copied from bottom_sheet.dart as is a private class
// https://github.com/flutter/flutter/issues/51627
// TODO(guidezpl): Look into making this public. A copy of this class is in
// scaffold.dart, for now, https://github.com/flutter/flutter/issues/51627
/// A curve that progresses linearly until a specified [startingPoint], at which
/// point [curve] will begin. Unlike [Interval], [curve] will not start at zero,
/// but will use [startingPoint] as the Y position.
///
/// For example, if [startingPoint] is set to `0.5`, and [curve] is set to
/// [Curves.easeOut], then the bottom-left quarter of the curve will be a
/// straight line, and the top-right quarter will contain the entire contents of
/// [Curves.easeOut].
///
/// This is useful in situations where a widget must track the user's finger
/// (which requires a linear animation), and afterwards can be flung using a
/// curve specified with the [curve] argument, after the finger is released. In
/// such a case, the value of [startingPoint] would be the progress of the
/// animation at the time when the finger was released.
///
/// The [startingPoint] and [curve] arguments must not be null.
class BottomSheetSuspendedCurve extends ParametricCurve<double> {
/// Creates a suspended curve.
const BottomSheetSuspendedCurve(
this.startingPoint, {
this.curve = Curves.easeOutCubic,
}) : assert(startingPoint != null),
assert(curve != null);
/// The progress value at which [curve] should begin.
///
/// This defaults to [Curves.easeOutCubic].
final double startingPoint;
/// The curve to use when [startingPoint] is reached.
final Curve curve;
@override
double transform(double t) {
assert(t >= 0.0 && t <= 1.0);
assert(startingPoint >= 0.0 && startingPoint <= 1.0);
if (t < startingPoint) {
return t;
}
if (t == 1.0) {
return t;
}
final double curveProgress = (t - startingPoint) / (1 - startingPoint);
final double transformed = curve.transform(curveProgress);
return lerpDouble(startingPoint, 1, transformed);
}
@override
String toString() {
return '${describeIdentity(this)}($startingPoint, $curve)';
}
}
... ...