Jaime Blasco

Merge branch 'curve'

@@ -8,6 +8,7 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; @@ -8,6 +8,7 @@ import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
8 class CupertinoSharePage extends StatelessWidget { 8 class CupertinoSharePage extends StatelessWidget {
9 @override 9 @override
10 Widget build(BuildContext context) { 10 Widget build(BuildContext context) {
  11 +
11 return Scaffold( 12 return Scaffold(
12 appBar: appBar(context), 13 appBar: appBar(context),
13 body: CupertinoPageScaffold( 14 body: CupertinoPageScaffold(
@@ -9,28 +9,29 @@ class NestedScrollModal extends StatelessWidget { @@ -9,28 +9,29 @@ class NestedScrollModal extends StatelessWidget {
9 @override 9 @override
10 Widget build(BuildContext context) { 10 Widget build(BuildContext context) {
11 return NestedScrollView( 11 return NestedScrollView(
12 -  
13 - physics: ScrollPhysics(parent: PageScrollPhysics()),  
14 - headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {  
15 - return <Widget>[  
16 - SliverList(  
17 - delegate: SliverChildListDelegate(  
18 - [  
19 - Container(height: 300, color: Colors.blue),  
20 - ],  
21 - ), 12 + controller: ScrollController(),
  13 + physics: ScrollPhysics(parent: PageScrollPhysics()),
  14 + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
  15 + return <Widget>[
  16 + SliverList(
  17 + delegate: SliverChildListDelegate(
  18 + [
  19 + Container(height: 300, color: Colors.blue),
  20 + ],
22 ), 21 ),
23 - ]; 22 + ),
  23 + ];
  24 + },
  25 + body: ListView.builder(
  26 + controller: scrollController,
  27 + itemBuilder: (context, index) {
  28 + return Container(
  29 + height: 100,
  30 + color: index.isOdd ? Colors.green : Colors.orange,
  31 + );
24 }, 32 },
25 - body: ListView.builder(  
26 - controller: scrollController,  
27 - itemBuilder: (context, index) {  
28 - return Container(  
29 - height: 100,  
30 - color: index.isOdd ? Colors.green : Colors.orange,  
31 - );  
32 - },  
33 - itemCount: 12,  
34 - )); 33 + itemCount: 12,
  34 + ),
  35 + );
35 } 36 }
36 } 37 }
@@ -12,6 +12,12 @@ import 'package:flutter/scheduler.dart'; @@ -12,6 +12,12 @@ import 'package:flutter/scheduler.dart';
12 import 'package:flutter/widgets.dart'; 12 import 'package:flutter/widgets.dart';
13 import 'package:modal_bottom_sheet/src/utils/primary_scroll_status_bar.dart'; 13 import 'package:modal_bottom_sheet/src/utils/primary_scroll_status_bar.dart';
14 14
  15 +import 'package:modal_bottom_sheet/src/utils/bottom_sheet_suspended_curve.dart';
  16 +import 'package:modal_bottom_sheet/src/utils/primary_scroll_status_bar.dart';
  17 +
  18 +
  19 +const Curve _decelerateEasing = Cubic(0.0, 0.0, 0.2, 1.0);
  20 +const Curve _modalBottomSheetCurve = _decelerateEasing;
15 const Duration _bottomSheetDuration = Duration(milliseconds: 400); 21 const Duration _bottomSheetDuration = Duration(milliseconds: 400);
16 const double _minFlingVelocity = 500.0; 22 const double _minFlingVelocity = 500.0;
17 const double _closeProgressThreshold = 0.6; 23 const double _closeProgressThreshold = 0.6;
@@ -62,7 +68,7 @@ class ModalBottomSheet extends StatefulWidget { @@ -62,7 +68,7 @@ class ModalBottomSheet extends StatefulWidget {
62 68
63 /// The curve used by the animation showing and dismissing the bottom sheet. 69 /// The curve used by the animation showing and dismissing the bottom sheet.
64 /// 70 ///
65 - /// If no curve is provided it falls back to `Curves.easeOutSine`. 71 + /// If no curve is provided it falls back to `decelerateEasing`.
66 final Curve animationCurve; 72 final Curve animationCurve;
67 73
68 /// Allows the bottom sheet to go beyond the top bound of the content, 74 /// Allows the bottom sheet to go beyond the top bound of the content,
@@ -177,7 +183,10 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -177,7 +183,10 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
177 return result; 183 return result;
178 } 184 }
179 185
  186 + ParametricCurve<double> animationCurve;
  187 +
180 void _handleDragUpdate(double primaryDelta) async { 188 void _handleDragUpdate(double primaryDelta) async {
  189 + animationCurve = Curves.linear;
181 assert(widget.enableDrag, 'Dragging is disabled'); 190 assert(widget.enableDrag, 'Dragging is disabled');
182 191
183 if (_dismissUnderway) return; 192 if (_dismissUnderway) return;
@@ -211,6 +220,11 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -211,6 +220,11 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
211 void _handleDragEnd(double velocity) async { 220 void _handleDragEnd(double velocity) async {
212 assert(widget.enableDrag, 'Dragging is disabled'); 221 assert(widget.enableDrag, 'Dragging is disabled');
213 222
  223 + animationCurve = BottomSheetSuspendedCurve(
  224 + widget.animationController.value,
  225 + curve: _defaultCurve,
  226 + );
  227 +
214 if (_dismissUnderway || !isDragging) return; 228 if (_dismissUnderway || !isDragging) return;
215 isDragging = false; 229 isDragging = false;
216 _bounceDragController.reverse(); 230 _bounceDragController.reverse();
@@ -303,8 +317,12 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -303,8 +317,12 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
303 } 317 }
304 } 318 }
305 319
  320 + ParametricCurve<double> get _defaultCurve =>
  321 + widget.animationCurve ?? _modalBottomSheetCurve;
  322 +
306 @override 323 @override
307 void initState() { 324 void initState() {
  325 + animationCurve = _defaultCurve;
308 _bounceDragController = 326 _bounceDragController =
309 AnimationController(vsync: this, duration: Duration(milliseconds: 300)); 327 AnimationController(vsync: this, duration: Duration(milliseconds: 300));
310 _scrollController = widget.scrollController ?? ScrollController(); 328 _scrollController = widget.scrollController ?? ScrollController();
@@ -329,49 +347,57 @@ class _ModalBottomSheetState extends State<ModalBottomSheet> @@ -329,49 +347,57 @@ class _ModalBottomSheetState extends State<ModalBottomSheet>
329 ); 347 );
330 } 348 }
331 349
332 - // Todo: Add curved Animation when push and pop without gesture  
333 - final Animation<double> containerAnimation = CurvedAnimation(  
334 - parent: widget.animationController,  
335 - curve: widget.animationCurve ?? Curves.linear,  
336 - );  
337 -  
338 - return PrimaryScrollStatusBarHandler(  
339 - scrollController: _scrollController,  
340 - child: AnimatedBuilder(  
341 - animation: widget.animationController,  
342 - builder: (context, _) => ClipRect(  
343 - child: CustomSingleChildLayout(  
344 - delegate: _ModalBottomSheetLayout(  
345 - containerAnimation.value, widget.expanded),  
346 - child: !widget.enableDrag  
347 - ? child  
348 - : KeyedSubtree(  
349 - key: _childKey,  
350 - child: AnimatedBuilder(  
351 - animation: bounceAnimation,  
352 - builder: (context, _) => CustomSingleChildLayout(  
353 - delegate:  
354 - _CustomBottomSheetLayout(bounceAnimation.value),  
355 - child: GestureDetector(  
356 - onVerticalDragUpdate: (details) =>  
357 - _handleDragUpdate(details.primaryDelta),  
358 - onVerticalDragEnd: (details) =>  
359 - _handleDragEnd(details.primaryVelocity),  
360 - child: NotificationListener<ScrollNotification>(  
361 - onNotification:  
362 - (ScrollNotification notification) {  
363 - _handleScrollUpdate(notification);  
364 - return false;  
365 - },  
366 - child: RepaintBoundary(child: child),  
367 - )), 350 + final mediaQuery = MediaQuery.of(context);
  351 +
  352 + child = AnimatedBuilder(
  353 + animation: widget.animationController,
  354 + builder: (context, child) {
  355 + final animationValue = animationCurve.transform(
  356 + mediaQuery.accessibleNavigation
  357 + ? 1.0
  358 + : widget.animationController.value);
  359 +
  360 + final draggableChild = !widget.enableDrag
  361 + ? child
  362 + : KeyedSubtree(
  363 + key: _childKey,
  364 + child: AnimatedBuilder(
  365 + animation: bounceAnimation,
  366 + builder: (context, _) => CustomSingleChildLayout(
  367 + delegate: _CustomBottomSheetLayout(bounceAnimation.value),
  368 + child: GestureDetector(
  369 + onVerticalDragUpdate: (details) {
  370 + _handleDragUpdate(details.primaryDelta);
  371 + },
  372 + onVerticalDragEnd: (details) {
  373 + _handleDragEnd(details.primaryVelocity);
  374 + },
  375 + child: NotificationListener<ScrollNotification>(
  376 + onNotification: (ScrollNotification notification) {
  377 + _handleScrollUpdate(notification);
  378 + return false;
  379 + },
  380 + child: child,
368 ), 381 ),
369 ), 382 ),
370 ), 383 ),
  384 + ),
  385 + );
  386 + return ClipRect(
  387 + child: CustomSingleChildLayout(
  388 + delegate: _ModalBottomSheetLayout(
  389 + animationValue,
  390 + widget.expanded,
  391 + ),
  392 + child: draggableChild,
371 ), 393 ),
372 - ),  
373 - ), 394 + );
  395 + },
  396 + child: RepaintBoundary(child: child),
374 ); 397 );
  398 +
  399 + return PrimaryScrollStatusBarHandler(
  400 + scrollController: _scrollController, child: child);
375 } 401 }
376 } 402 }
377 403
  1 +import 'dart:ui';
  2 +
  3 +import 'package:flutter/animation.dart';
  4 +import 'package:flutter/foundation.dart';
  5 +
  6 +// Copied from bottom_sheet.dart as is a private class
  7 +// https://github.com/flutter/flutter/issues/51627
  8 +
  9 +// TODO(guidezpl): Look into making this public. A copy of this class is in
  10 +// scaffold.dart, for now, https://github.com/flutter/flutter/issues/51627
  11 +
  12 +/// A curve that progresses linearly until a specified [startingPoint], at which
  13 +/// point [curve] will begin. Unlike [Interval], [curve] will not start at zero,
  14 +/// but will use [startingPoint] as the Y position.
  15 +///
  16 +/// For example, if [startingPoint] is set to `0.5`, and [curve] is set to
  17 +/// [Curves.easeOut], then the bottom-left quarter of the curve will be a
  18 +/// straight line, and the top-right quarter will contain the entire contents of
  19 +/// [Curves.easeOut].
  20 +///
  21 +/// This is useful in situations where a widget must track the user's finger
  22 +/// (which requires a linear animation), and afterwards can be flung using a
  23 +/// curve specified with the [curve] argument, after the finger is released. In
  24 +/// such a case, the value of [startingPoint] would be the progress of the
  25 +/// animation at the time when the finger was released.
  26 +///
  27 +/// The [startingPoint] and [curve] arguments must not be null.
  28 +class BottomSheetSuspendedCurve extends ParametricCurve<double> {
  29 + /// Creates a suspended curve.
  30 + const BottomSheetSuspendedCurve(
  31 + this.startingPoint, {
  32 + this.curve = Curves.easeOutCubic,
  33 + }) : assert(startingPoint != null),
  34 + assert(curve != null);
  35 +
  36 + /// The progress value at which [curve] should begin.
  37 + ///
  38 + /// This defaults to [Curves.easeOutCubic].
  39 + final double startingPoint;
  40 +
  41 + /// The curve to use when [startingPoint] is reached.
  42 + final Curve curve;
  43 +
  44 + @override
  45 + double transform(double t) {
  46 + assert(t >= 0.0 && t <= 1.0);
  47 + assert(startingPoint >= 0.0 && startingPoint <= 1.0);
  48 +
  49 + if (t < startingPoint) {
  50 + return t;
  51 + }
  52 +
  53 + if (t == 1.0) {
  54 + return t;
  55 + }
  56 +
  57 + final double curveProgress = (t - startingPoint) / (1 - startingPoint);
  58 + final double transformed = curve.transform(curveProgress);
  59 + return lerpDouble(startingPoint, 1, transformed);
  60 + }
  61 +
  62 + @override
  63 + String toString() {
  64 + return '${describeIdentity(this)}($startingPoint, $curve)';
  65 + }
  66 +}