Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
e7df5ec5
Unverified
Commit
e7df5ec5
authored
Sep 15, 2021
by
Casey Rogers
Committed by
GitHub
Sep 15, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add Snapping Behavior to DraggableScrollableSheet (#84394)
parent
daa051c3
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
438 additions
and
32 deletions
+438
-32
draggable_scrollable_sheet.dart
...s/flutter/lib/src/widgets/draggable_scrollable_sheet.dart
+207
-14
draggable_scrollable_sheet_test.dart
...flutter/test/widgets/draggable_scrollable_sheet_test.dart
+231
-18
No files found.
packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart
View file @
e7df5ec5
...
@@ -2,6 +2,8 @@
...
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
import
'dart:math'
as
math
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/gestures.dart'
;
...
@@ -51,6 +53,12 @@ typedef ScrollableWidgetBuilder = Widget Function(
...
@@ -51,6 +53,12 @@ typedef ScrollableWidgetBuilder = Widget Function(
/// [ScrollableWidgetBuilder] does not use the provided [ScrollController], the
/// [ScrollableWidgetBuilder] does not use the provided [ScrollController], the
/// sheet will remain at the initialChildSize.
/// sheet will remain at the initialChildSize.
///
///
/// By default, the widget will stay at whatever size the user drags it to. To
/// make the widget snap to specific sizes whenever they lift their finger
/// during a drag, set [snap] to `true`. The sheet will snap between
/// [minChildSize] and [maxChildSize]. Use [snapSizes] to add more sizes for
/// the sheet to snap between.
///
/// By default, the widget will expand its non-occupied area to fill available
/// By default, the widget will expand its non-occupied area to fill available
/// space in the parent. If this is not desired, e.g. because the parent wants
/// space in the parent. If this is not desired, e.g. because the parent wants
/// to position sheet based on the space it is taking, the [expand] property
/// to position sheet based on the space it is taking, the [expand] property
...
@@ -107,6 +115,8 @@ class DraggableScrollableSheet extends StatefulWidget {
...
@@ -107,6 +115,8 @@ class DraggableScrollableSheet extends StatefulWidget {
this
.
minChildSize
=
0.25
,
this
.
minChildSize
=
0.25
,
this
.
maxChildSize
=
1.0
,
this
.
maxChildSize
=
1.0
,
this
.
expand
=
true
,
this
.
expand
=
true
,
this
.
snap
=
false
,
this
.
snapSizes
,
required
this
.
builder
,
required
this
.
builder
,
})
:
assert
(
initialChildSize
!=
null
),
})
:
assert
(
initialChildSize
!=
null
),
assert
(
minChildSize
!=
null
),
assert
(
minChildSize
!=
null
),
...
@@ -147,6 +157,26 @@ class DraggableScrollableSheet extends StatefulWidget {
...
@@ -147,6 +157,26 @@ class DraggableScrollableSheet extends StatefulWidget {
/// The default value is true.
/// The default value is true.
final
bool
expand
;
final
bool
expand
;
/// Whether the widget should snap between [snapSizes] when the user lifts
/// their finger during a drag.
///
/// If the user's finger was still moving when they lifted it, the widget will
/// snap to the next snap size (see [snapSizes]) in the direction of the drag.
/// If their finger was still, the widget will snap to the nearest snap size.
final
bool
snap
;
/// A list of target sizes that the widget should snap to.
///
/// Snap sizes are fractional values of the parent container's height. They
/// must be listed in increasing order and be between [minChildSize] and
/// [maxChildSize].
///
/// The [minChildSize] and [maxChildSize] are implicitly included in snap
/// sizes and do not need to be specified here. For example, `snapSizes = [.5]`
/// will result in a sheet that snaps between [minChildSize], `.5`, and
/// [maxChildSize].
final
List
<
double
>?
snapSizes
;
/// The builder that creates a child to display in this widget, which will
/// The builder that creates a child to display in this widget, which will
/// use the provided [ScrollController] to enable dragging and scrolling
/// use the provided [ScrollController] to enable dragging and scrolling
/// of the contents.
/// of the contents.
...
@@ -241,6 +271,8 @@ class _DraggableSheetExtent {
...
@@ -241,6 +271,8 @@ class _DraggableSheetExtent {
_DraggableSheetExtent
({
_DraggableSheetExtent
({
required
this
.
minExtent
,
required
this
.
minExtent
,
required
this
.
maxExtent
,
required
this
.
maxExtent
,
required
this
.
snap
,
required
this
.
snapSizes
,
required
this
.
initialExtent
,
required
this
.
initialExtent
,
required
VoidCallback
listener
,
required
VoidCallback
listener
,
})
:
assert
(
minExtent
!=
null
),
})
:
assert
(
minExtent
!=
null
),
...
@@ -255,21 +287,30 @@ class _DraggableSheetExtent {
...
@@ -255,21 +287,30 @@ class _DraggableSheetExtent {
final
double
minExtent
;
final
double
minExtent
;
final
double
maxExtent
;
final
double
maxExtent
;
final
bool
snap
;
final
List
<
double
>
snapSizes
;
final
double
initialExtent
;
final
double
initialExtent
;
final
ValueNotifier
<
double
>
_currentExtent
;
final
ValueNotifier
<
double
>
_currentExtent
;
double
availablePixels
;
double
availablePixels
;
// Used to disable snapping until the extent has changed. We do this because
// we don't want to snap away from the initial extent.
bool
hasChanged
=
false
;
bool
get
isAtMin
=>
minExtent
>=
_currentExtent
.
value
;
bool
get
isAtMin
=>
minExtent
>=
_currentExtent
.
value
;
bool
get
isAtMax
=>
maxExtent
<=
_currentExtent
.
value
;
bool
get
isAtMax
=>
maxExtent
<=
_currentExtent
.
value
;
set
currentExtent
(
double
value
)
{
set
currentExtent
(
double
value
)
{
assert
(
value
!=
null
);
assert
(
value
!=
null
);
hasChanged
=
true
;
_currentExtent
.
value
=
value
.
clamp
(
minExtent
,
maxExtent
);
_currentExtent
.
value
=
value
.
clamp
(
minExtent
,
maxExtent
);
}
}
double
get
currentExtent
=>
_currentExtent
.
value
;
double
get
currentExtent
=>
_currentExtent
.
value
;
double
get
currentPixels
=>
extentToPixels
(
_currentExtent
.
value
);
double
get
additionalMinExtent
=>
isAtMin
?
0.0
:
1.0
;
double
get
additionalMinExtent
=>
isAtMin
?
0.0
:
1.0
;
double
get
additionalMaxExtent
=>
isAtMax
?
0.0
:
1.0
;
double
get
additionalMaxExtent
=>
isAtMax
?
0.0
:
1.0
;
List
<
double
>
get
pixelSnapSizes
=>
snapSizes
.
map
(
extentToPixels
).
toList
();
/// The scroll position gets inputs in terms of pixels, but the extent is
/// The scroll position gets inputs in terms of pixels, but the extent is
/// expected to be expressed as a number between 0..1.
/// expected to be expressed as a number between 0..1.
...
@@ -277,7 +318,13 @@ class _DraggableSheetExtent {
...
@@ -277,7 +318,13 @@ class _DraggableSheetExtent {
if
(
availablePixels
==
0
)
{
if
(
availablePixels
==
0
)
{
return
;
return
;
}
}
currentExtent
+=
delta
/
availablePixels
*
maxExtent
;
updateExtent
(
currentExtent
+
pixelsToExtent
(
delta
),
context
);
}
/// Set the extent to the new value. [newExtent] should be a number between
/// 0..1.
void
updateExtent
(
double
newExtent
,
BuildContext
context
)
{
currentExtent
=
newExtent
;
DraggableScrollableNotification
(
DraggableScrollableNotification
(
minExtent:
minExtent
,
minExtent:
minExtent
,
maxExtent:
maxExtent
,
maxExtent:
maxExtent
,
...
@@ -286,6 +333,14 @@ class _DraggableSheetExtent {
...
@@ -286,6 +333,14 @@ class _DraggableSheetExtent {
context:
context
,
context:
context
,
).
dispatch
(
context
);
).
dispatch
(
context
);
}
}
double
pixelsToExtent
(
double
pixels
)
{
return
pixels
/
availablePixels
*
maxExtent
;
}
double
extentToPixels
(
double
extent
)
{
return
extent
/
maxExtent
*
availablePixels
;
}
}
}
class
_DraggableScrollableSheetState
extends
State
<
DraggableScrollableSheet
>
{
class
_DraggableScrollableSheetState
extends
State
<
DraggableScrollableSheet
>
{
...
@@ -298,12 +353,38 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
...
@@ -298,12 +353,38 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
_extent
=
_DraggableSheetExtent
(
_extent
=
_DraggableSheetExtent
(
minExtent:
widget
.
minChildSize
,
minExtent:
widget
.
minChildSize
,
maxExtent:
widget
.
maxChildSize
,
maxExtent:
widget
.
maxChildSize
,
snap:
widget
.
snap
,
snapSizes:
_impliedSnapSizes
(),
initialExtent:
widget
.
initialChildSize
,
initialExtent:
widget
.
initialChildSize
,
listener:
_setExtent
,
listener:
_setExtent
,
);
);
_scrollController
=
_DraggableScrollableSheetScrollController
(
extent:
_extent
);
_scrollController
=
_DraggableScrollableSheetScrollController
(
extent:
_extent
);
}
}
List
<
double
>
_impliedSnapSizes
()
{
for
(
int
index
=
0
;
index
<
(
widget
.
snapSizes
?.
length
??
0
);
index
+=
1
)
{
final
double
snapSize
=
widget
.
snapSizes
![
index
];
assert
(
snapSize
>=
widget
.
minChildSize
&&
snapSize
<=
widget
.
maxChildSize
,
'
${_snapSizeErrorMessage(index)}
\n
Snap sizes must be between `minChildSize` and `maxChildSize`. '
);
assert
(
index
==
0
||
snapSize
>
widget
.
snapSizes
![
index
-
1
],
'
${_snapSizeErrorMessage(index)}
\n
Snap sizes must be in ascending order. '
);
}
widget
.
snapSizes
?.
asMap
().
forEach
((
int
index
,
double
snapSize
)
{
});
// Ensure the snap sizes start and end with the min and max child sizes.
if
(
widget
.
snapSizes
==
null
||
widget
.
snapSizes
!.
isEmpty
)
{
return
<
double
>[
widget
.
minChildSize
,
widget
.
maxChildSize
,
];
}
return
<
double
>[
if
(
widget
.
snapSizes
!.
first
!=
widget
.
minChildSize
)
widget
.
minChildSize
,
...
widget
.
snapSizes
!,
if
(
widget
.
snapSizes
!.
last
!=
widget
.
maxChildSize
)
widget
.
maxChildSize
,
];
}
@override
@override
void
didChangeDependencies
()
{
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
super
.
didChangeDependencies
();
...
@@ -318,6 +399,7 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
...
@@ -318,6 +399,7 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
curve:
Curves
.
linear
,
curve:
Curves
.
linear
,
);
);
}
}
_extent
.
hasChanged
=
false
;
_extent
.
_currentExtent
.
value
=
_extent
.
initialExtent
;
_extent
.
_currentExtent
.
value
=
_extent
.
initialExtent
;
}
}
}
}
...
@@ -349,6 +431,20 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
...
@@ -349,6 +431,20 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
_scrollController
.
dispose
();
_scrollController
.
dispose
();
super
.
dispose
();
super
.
dispose
();
}
}
String
_snapSizeErrorMessage
(
int
invalidIndex
)
{
final
List
<
String
>
snapSizesWithIndicator
=
widget
.
snapSizes
!.
asMap
().
keys
.
map
(
(
int
index
)
{
final
String
snapSizeString
=
widget
.
snapSizes
![
index
].
toString
();
if
(
index
==
invalidIndex
)
{
return
'>>>
$snapSizeString
<<<'
;
}
return
snapSizeString
;
},
).
toList
();
return
"Invalid snapSize '
${widget.snapSizes![invalidIndex]}
' at index
$invalidIndex
of:
\n
"
'
$snapSizesWithIndicator
'
;
}
}
}
/// A [ScrollController] suitable for use in a [ScrollableWidgetBuilder] created
/// A [ScrollController] suitable for use in a [ScrollableWidgetBuilder] created
...
@@ -466,6 +562,15 @@ class _DraggableScrollableSheetScrollPosition
...
@@ -466,6 +562,15 @@ class _DraggableScrollableSheetScrollPosition
}
}
}
}
bool
get
_isAtSnapSize
{
return
extent
.
snapSizes
.
any
(
(
double
snapSize
)
{
return
(
extent
.
currentExtent
-
snapSize
).
abs
()
<=
extent
.
pixelsToExtent
(
physics
.
tolerance
.
distance
);
},
);
}
bool
get
_shouldSnap
=>
extent
.
snap
&&
extent
.
hasChanged
&&
!
_isAtSnapSize
;
@override
@override
void
dispose
()
{
void
dispose
()
{
// Stop the animation before dispose.
// Stop the animation before dispose.
...
@@ -475,7 +580,7 @@ class _DraggableScrollableSheetScrollPosition
...
@@ -475,7 +580,7 @@ class _DraggableScrollableSheetScrollPosition
@override
@override
void
goBallistic
(
double
velocity
)
{
void
goBallistic
(
double
velocity
)
{
if
(
velocity
==
0.0
||
if
(
(
velocity
==
0.0
&&
!
_shouldSnap
)
||
(
velocity
<
0.0
&&
listShouldScroll
)
||
(
velocity
<
0.0
&&
listShouldScroll
)
||
(
velocity
>
0.0
&&
extent
.
isAtMax
))
{
(
velocity
>
0.0
&&
extent
.
isAtMax
))
{
super
.
goBallistic
(
velocity
);
super
.
goBallistic
(
velocity
);
...
@@ -485,13 +590,24 @@ class _DraggableScrollableSheetScrollPosition
...
@@ -485,13 +590,24 @@ class _DraggableScrollableSheetScrollPosition
_dragCancelCallback
?.
call
();
_dragCancelCallback
?.
call
();
_dragCancelCallback
=
null
;
_dragCancelCallback
=
null
;
late
final
Simulation
simulation
;
if
(
extent
.
snap
)
{
// Snap is enabled, simulate snapping instead of clamping scroll.
simulation
=
_SnappingSimulation
(
position:
extent
.
currentPixels
,
initialVelocity:
velocity
,
pixelSnapSize:
extent
.
pixelSnapSizes
,
tolerance:
physics
.
tolerance
);
}
else
{
// The iOS bouncing simulation just isn't right here - once we delegate
// The iOS bouncing simulation just isn't right here - once we delegate
// the ballistic back to the ScrollView, it will use the right simulation.
// the ballistic back to the ScrollView, it will use the right simulation.
final
Simulation
simulation
=
ClampingScrollSimulation
(
simulation
=
ClampingScrollSimulation
(
position:
extent
.
currentExtent
,
// Run the simulation in terms of pixels, not extent.
position:
extent
.
currentPixels
,
velocity:
velocity
,
velocity:
velocity
,
tolerance:
physics
.
tolerance
,
tolerance:
physics
.
tolerance
,
);
);
}
final
AnimationController
ballisticController
=
AnimationController
.
unbounded
(
final
AnimationController
ballisticController
=
AnimationController
.
unbounded
(
debugLabel:
objectRuntimeType
(
this
,
'_DraggableScrollableSheetPosition'
),
debugLabel:
objectRuntimeType
(
this
,
'_DraggableScrollableSheetPosition'
),
...
@@ -500,10 +616,10 @@ class _DraggableScrollableSheetScrollPosition
...
@@ -500,10 +616,10 @@ class _DraggableScrollableSheetScrollPosition
// Stop the ballistic animation if a new activity starts.
// Stop the ballistic animation if a new activity starts.
// See: [beginActivity].
// See: [beginActivity].
_ballisticCancelCallback
=
ballisticController
.
stop
;
_ballisticCancelCallback
=
ballisticController
.
stop
;
double
last
Delta
=
0
;
double
last
Position
=
extent
.
currentPixels
;
void
_tick
()
{
void
_tick
()
{
final
double
delta
=
ballisticController
.
value
-
last
Delta
;
final
double
delta
=
ballisticController
.
value
-
last
Position
;
last
Delta
=
ballisticController
.
value
;
last
Position
=
ballisticController
.
value
;
extent
.
addPixelDelta
(
delta
,
context
.
notificationContext
!);
extent
.
addPixelDelta
(
delta
,
context
.
notificationContext
!);
if
((
velocity
>
0
&&
extent
.
isAtMax
)
||
(
velocity
<
0
&&
extent
.
isAtMin
))
{
if
((
velocity
>
0
&&
extent
.
isAtMax
)
||
(
velocity
<
0
&&
extent
.
isAtMin
))
{
// Make sure we pass along enough velocity to keep scrolling - otherwise
// Make sure we pass along enough velocity to keep scrolling - otherwise
...
@@ -630,3 +746,80 @@ class _InheritedResetNotifier extends InheritedNotifier<_ResetNotifier> {
...
@@ -630,3 +746,80 @@ class _InheritedResetNotifier extends InheritedNotifier<_ResetNotifier> {
return
wasCalled
;
return
wasCalled
;
}
}
}
}
class
_SnappingSimulation
extends
Simulation
{
_SnappingSimulation
({
required
this
.
position
,
required
double
initialVelocity
,
required
List
<
double
>
pixelSnapSize
,
Tolerance
tolerance
=
Tolerance
.
defaultTolerance
,
})
:
super
(
tolerance:
tolerance
)
{
_pixelSnapSize
=
_getSnapSize
(
initialVelocity
,
pixelSnapSize
);
// Check the direction of the target instead of the sign of the velocity because
// we may snap in the opposite direction of velocity if velocity is very low.
if
(
_pixelSnapSize
<
position
)
{
velocity
=
math
.
min
(-
minimumSpeed
,
initialVelocity
);
}
else
{
velocity
=
math
.
max
(
minimumSpeed
,
initialVelocity
);
}
}
final
double
position
;
late
final
double
velocity
;
// A minimum speed to snap at. Used to ensure that the snapping animation
// does not play too slowly.
static
const
double
minimumSpeed
=
1600.0
;
late
final
double
_pixelSnapSize
;
@override
double
dx
(
double
time
)
{
if
(
isDone
(
time
))
{
return
0
;
}
return
velocity
;
}
@override
bool
isDone
(
double
time
)
{
return
x
(
time
)
==
_pixelSnapSize
;
}
@override
double
x
(
double
time
)
{
final
double
newPosition
=
position
+
velocity
*
time
;
if
((
velocity
>=
0
&&
newPosition
>
_pixelSnapSize
)
||
(
velocity
<
0
&&
newPosition
<
_pixelSnapSize
))
{
// We're passed the snap size, return it instead.
return
_pixelSnapSize
;
}
return
newPosition
;
}
// Find the two closest snap sizes to the position. If the velocity is
// non-zero, select the size in the velocity's direction. Otherwise,
// the nearest snap size.
double
_getSnapSize
(
double
initialVelocity
,
List
<
double
>
pixelSnapSizes
)
{
final
int
indexOfNextSize
=
pixelSnapSizes
.
indexWhere
((
double
size
)
=>
size
>=
position
);
if
(
indexOfNextSize
==
0
)
{
return
pixelSnapSizes
.
first
;
}
final
double
nextSize
=
pixelSnapSizes
[
indexOfNextSize
];
final
double
previousSize
=
pixelSnapSizes
[
indexOfNextSize
-
1
];
if
(
initialVelocity
.
abs
()
<=
tolerance
.
velocity
)
{
// If velocity is zero, snap to the nearest snap size with the minimum velocity.
if
(
position
-
previousSize
<
nextSize
-
position
)
{
return
previousSize
;
}
else
{
return
nextSize
;
}
}
// Snap forward or backward depending on current velocity.
if
(
initialVelocity
<
0.0
)
{
return
pixelSnapSizes
[
indexOfNextSize
-
1
];
}
return
pixelSnapSizes
[
indexOfNextSize
];
}
}
packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart
View file @
e7df5ec5
...
@@ -12,8 +12,11 @@ void main() {
...
@@ -12,8 +12,11 @@ void main() {
double
initialChildSize
=
.
5
,
double
initialChildSize
=
.
5
,
double
maxChildSize
=
1.0
,
double
maxChildSize
=
1.0
,
double
minChildSize
=
.
25
,
double
minChildSize
=
.
25
,
bool
snap
=
false
,
List
<
double
>?
snapSizes
,
double
?
itemExtent
,
double
?
itemExtent
,
Key
?
containerKey
,
Key
?
containerKey
,
Key
?
stackKey
,
NotificationListenerCallback
<
ScrollNotification
>?
onScrollNotification
,
NotificationListenerCallback
<
ScrollNotification
>?
onScrollNotification
,
})
{
})
{
return
Directionality
(
return
Directionality
(
...
@@ -21,15 +24,19 @@ void main() {
...
@@ -21,15 +24,19 @@ void main() {
child:
MediaQuery
(
child:
MediaQuery
(
data:
const
MediaQueryData
(),
data:
const
MediaQueryData
(),
child:
Stack
(
child:
Stack
(
key:
stackKey
,
children:
<
Widget
>[
children:
<
Widget
>[
TextButton
(
TextButton
(
onPressed:
onButtonPressed
,
onPressed:
onButtonPressed
,
child:
const
Text
(
'TapHere'
),
child:
const
Text
(
'TapHere'
),
),
),
DraggableScrollableSheet
(
DraggableScrollableActuator
(
child:
DraggableScrollableSheet
(
maxChildSize:
maxChildSize
,
maxChildSize:
maxChildSize
,
minChildSize:
minChildSize
,
minChildSize:
minChildSize
,
initialChildSize:
initialChildSize
,
initialChildSize:
initialChildSize
,
snap:
snap
,
snapSizes:
snapSizes
,
builder:
(
BuildContext
context
,
ScrollController
scrollController
)
{
builder:
(
BuildContext
context
,
ScrollController
scrollController
)
{
return
NotificationListener
<
ScrollNotification
>(
return
NotificationListener
<
ScrollNotification
>(
onNotification:
onScrollNotification
,
onNotification:
onScrollNotification
,
...
@@ -46,6 +53,7 @@ void main() {
...
@@ -46,6 +53,7 @@ void main() {
);
);
},
},
),
),
),
],
],
),
),
),
),
...
@@ -84,6 +92,27 @@ void main() {
...
@@ -84,6 +92,27 @@ void main() {
expect
(
tester
.
getRect
(
find
.
byKey
(
key
)),
const
Rect
.
fromLTRB
(
0.0
,
325.0
,
800.0
,
600.0
));
expect
(
tester
.
getRect
(
find
.
byKey
(
key
)),
const
Rect
.
fromLTRB
(
0.0
,
325.0
,
800.0
,
600.0
));
});
});
testWidgets
(
'Invalid snap targets throw assertion errors.'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
maxChildSize:
.
8
,
snapSizes:
<
double
>[.
9
],
));
expect
(
tester
.
takeException
(),
isAssertionError
);
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
snapSizes:
<
double
>[.
1
],
));
expect
(
tester
.
takeException
(),
isAssertionError
);
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
snapSizes:
<
double
>[.
6
,
.
6
,
.
9
],
));
expect
(
tester
.
takeException
(),
isAssertionError
);
});
for
(
final
TargetPlatform
platform
in
TargetPlatform
.
values
)
{
for
(
final
TargetPlatform
platform
in
TargetPlatform
.
values
)
{
group
(
'
$platform
Scroll Physics'
,
()
{
group
(
'
$platform
Scroll Physics'
,
()
{
debugDefaultTargetPlatformOverride
=
platform
;
debugDefaultTargetPlatformOverride
=
platform
;
...
@@ -301,6 +330,190 @@ void main() {
...
@@ -301,6 +330,190 @@ void main() {
debugDefaultTargetPlatformOverride
=
null
;
debugDefaultTargetPlatformOverride
=
null
;
});
});
testWidgets
(
'Does not snap away from initial child on build'
,
(
WidgetTester
tester
)
async
{
const
Key
containerKey
=
ValueKey
<
String
>(
'container'
);
const
Key
stackKey
=
ValueKey
<
String
>(
'stack'
);
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
snap:
true
,
initialChildSize:
.
7
,
containerKey:
containerKey
,
stackKey:
stackKey
,
));
await
tester
.
pumpAndSettle
();
final
double
screenHeight
=
tester
.
getSize
(
find
.
byKey
(
stackKey
)).
height
;
// The sheet should not have snapped.
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
7
,
precisionErrorTolerance
,
));
},
variant:
TargetPlatformVariant
.
all
());
testWidgets
(
'Does not snap away from initial child on reset'
,
(
WidgetTester
tester
)
async
{
const
Key
containerKey
=
ValueKey
<
String
>(
'container'
);
const
Key
stackKey
=
ValueKey
<
String
>(
'stack'
);
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
snap:
true
,
containerKey:
containerKey
,
stackKey:
stackKey
,
));
await
tester
.
pumpAndSettle
();
final
double
screenHeight
=
tester
.
getSize
(
find
.
byKey
(
stackKey
)).
height
;
await
tester
.
drag
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
-.
4
*
screenHeight
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(
1.0
,
precisionErrorTolerance
),
);
DraggableScrollableActuator
.
reset
(
tester
.
element
(
find
.
byKey
(
containerKey
)));
await
tester
.
pumpAndSettle
();
// The sheet should have reset without snapping away from initial child.
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
5
,
precisionErrorTolerance
),
);
},
variant:
TargetPlatformVariant
.
all
());
testWidgets
(
'Zero velocity drag snaps to nearest snap target'
,
(
WidgetTester
tester
)
async
{
const
Key
stackKey
=
ValueKey
<
String
>(
'stack'
);
const
Key
containerKey
=
ValueKey
<
String
>(
'container'
);
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
snap:
true
,
stackKey:
stackKey
,
containerKey:
containerKey
,
snapSizes:
<
double
>[.
25
,
.
5
,
.
75
,
1.0
],
));
await
tester
.
pumpAndSettle
();
final
double
screenHeight
=
tester
.
getSize
(
find
.
byKey
(
stackKey
)).
height
;
// We are dragging up, but we'll snap down because we're closer to .75 than 1.
await
tester
.
drag
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
-.
35
*
screenHeight
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
75
,
precisionErrorTolerance
),
);
// Drag up and snap up.
await
tester
.
drag
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
-.
2
*
screenHeight
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(
1.0
,
precisionErrorTolerance
),
);
// Drag down and snap up.
await
tester
.
drag
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
.
1
*
screenHeight
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(
1.0
,
precisionErrorTolerance
),
);
// Drag down and snap down.
await
tester
.
drag
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
.
45
*
screenHeight
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
5
,
precisionErrorTolerance
),
);
// Fling up with negligible velocity and snap down.
await
tester
.
fling
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
.
1
*
screenHeight
),
1
);
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
5
,
precisionErrorTolerance
),
);
},
variant:
TargetPlatformVariant
.
all
());
for
(
final
List
<
double
>?
snapSizes
in
<
List
<
double
>?>[
null
,
<
double
>[]])
{
testWidgets
(
'Setting snapSizes to
$snapSizes
resolves to min and max'
,
(
WidgetTester
tester
)
async
{
const
Key
stackKey
=
ValueKey
<
String
>(
'stack'
);
const
Key
containerKey
=
ValueKey
<
String
>(
'container'
);
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
snap:
true
,
stackKey:
stackKey
,
containerKey:
containerKey
,
snapSizes:
snapSizes
,
));
await
tester
.
pumpAndSettle
();
final
double
screenHeight
=
tester
.
getSize
(
find
.
byKey
(
stackKey
)).
height
;
await
tester
.
drag
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
-.
4
*
screenHeight
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(
1.0
,
precisionErrorTolerance
,
));
await
tester
.
drag
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
.
7
*
screenHeight
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
25
,
precisionErrorTolerance
),
);
},
variant:
TargetPlatformVariant
.
all
());
}
testWidgets
(
'Min and max are implicitly added to snapSizes.'
,
(
WidgetTester
tester
)
async
{
const
Key
stackKey
=
ValueKey
<
String
>(
'stack'
);
const
Key
containerKey
=
ValueKey
<
String
>(
'container'
);
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
snap:
true
,
stackKey:
stackKey
,
containerKey:
containerKey
,
snapSizes:
<
double
>[.
5
],
));
await
tester
.
pumpAndSettle
();
final
double
screenHeight
=
tester
.
getSize
(
find
.
byKey
(
stackKey
)).
height
;
await
tester
.
drag
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
-.
4
*
screenHeight
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(
1.0
,
precisionErrorTolerance
),
);
await
tester
.
drag
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
.
7
*
screenHeight
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
25
,
precisionErrorTolerance
),
);
},
variant:
TargetPlatformVariant
.
all
());
testWidgets
(
'Fling snaps in direction of momentum'
,
(
WidgetTester
tester
)
async
{
const
Key
stackKey
=
ValueKey
<
String
>(
'stack'
);
const
Key
containerKey
=
ValueKey
<
String
>(
'container'
);
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
snap:
true
,
stackKey:
stackKey
,
containerKey:
containerKey
,
snapSizes:
<
double
>[.
5
,
.
75
],
));
await
tester
.
pumpAndSettle
();
final
double
screenHeight
=
tester
.
getSize
(
find
.
byKey
(
stackKey
)).
height
;
await
tester
.
fling
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
-.
1
*
screenHeight
),
1000
);
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
75
,
precisionErrorTolerance
),
);
await
tester
.
fling
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
.
3
*
screenHeight
),
1000
);
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
25
,
precisionErrorTolerance
),
);
},
variant:
TargetPlatformVariant
.
all
());
testWidgets
(
'ScrollNotification correctly dispatched when flung without covering its container'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'ScrollNotification correctly dispatched when flung without covering its container'
,
(
WidgetTester
tester
)
async
{
final
List
<
Type
>
notificationTypes
=
<
Type
>[];
final
List
<
Type
>
notificationTypes
=
<
Type
>[];
await
tester
.
pumpWidget
(
_boilerplate
(
await
tester
.
pumpWidget
(
_boilerplate
(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment