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
ec2e041e
Unverified
Commit
ec2e041e
authored
Sep 22, 2021
by
Casey Rogers
Committed by
GitHub
Sep 22, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make DraggableScrollableSheet Reflect Parameter Updates (#90354)
parent
6fabdd04
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
250 additions
and
40 deletions
+250
-40
draggable_scrollable_sheet.dart
...s/flutter/lib/src/widgets/draggable_scrollable_sheet.dart
+103
-19
draggable_scrollable_sheet_test.dart
...flutter/test/widgets/draggable_scrollable_sheet_test.dart
+147
-21
No files found.
packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart
View file @
ec2e041e
...
...
@@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
import
'package:flutter/gestures.dart'
;
import
'basic.dart'
;
import
'binding.dart'
;
import
'framework.dart'
;
import
'inherited_notifier.dart'
;
import
'layout_builder.dart'
;
...
...
@@ -132,6 +133,10 @@ class DraggableScrollableSheet extends StatefulWidget {
/// The initial fractional value of the parent container's height to use when
/// displaying the widget.
///
/// Rebuilding the sheet with a new [initialChildSize] will only move the
/// the sheet to the new value if the sheet has not yet been dragged since it
/// was first built or since the last call to [DraggableScrollableActuator.reset].
///
/// The default value is `0.5`.
final
double
initialChildSize
;
...
...
@@ -163,6 +168,11 @@ class DraggableScrollableSheet extends StatefulWidget {
/// 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.
///
/// Rebuilding the sheet with snap newly enabled will immediately trigger a
/// snap unless the sheet has not yet been dragged away from
/// [initialChildSize] since first being built or since the last call to
/// [DraggableScrollableActuator.reset].
final
bool
snap
;
/// A list of target sizes that the widget should snap to.
...
...
@@ -175,6 +185,13 @@ class DraggableScrollableSheet extends StatefulWidget {
/// 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].
///
/// Any modifications to the [snapSizes] list will not take effect until the
/// `build` function containing this widget is run again.
///
/// Rebuilding with a modified or new list will trigger a snap unless the
/// sheet has not yet been dragged away from [initialChildSize] since first
/// being built or since the last call to [DraggableScrollableActuator.reset].
final
List
<
double
>?
snapSizes
;
/// The builder that creates a child to display in this widget, which will
...
...
@@ -274,16 +291,20 @@ class _DraggableSheetExtent {
required
this
.
snap
,
required
this
.
snapSizes
,
required
this
.
initialExtent
,
required
VoidCallback
listener
,
})
:
assert
(
minExtent
!=
null
),
assert
(
maxExtent
!=
null
),
assert
(
initialExtent
!=
null
),
assert
(
minExtent
>=
0
),
assert
(
maxExtent
<=
1
),
assert
(
minExtent
<=
initialExtent
),
assert
(
initialExtent
<=
maxExtent
),
_currentExtent
=
ValueNotifier
<
double
>(
initialExtent
)..
addListener
(
listener
),
availablePixels
=
double
.
infinity
;
required
this
.
onExtentChanged
,
ValueNotifier
<
double
>?
currentExtent
,
bool
?
hasChanged
,
})
:
assert
(
minExtent
!=
null
),
assert
(
maxExtent
!=
null
),
assert
(
initialExtent
!=
null
),
assert
(
minExtent
>=
0
),
assert
(
maxExtent
<=
1
),
assert
(
minExtent
<=
initialExtent
),
assert
(
initialExtent
<=
maxExtent
),
_currentExtent
=
(
currentExtent
??
ValueNotifier
<
double
>(
initialExtent
))
..
addListener
(
onExtentChanged
),
availablePixels
=
double
.
infinity
,
hasChanged
=
hasChanged
??
false
;
final
double
minExtent
;
final
double
maxExtent
;
...
...
@@ -291,11 +312,12 @@ class _DraggableSheetExtent {
final
List
<
double
>
snapSizes
;
final
double
initialExtent
;
final
ValueNotifier
<
double
>
_currentExtent
;
final
VoidCallback
onExtentChanged
;
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
hasChanged
;
bool
get
isAtMin
=>
minExtent
>=
_currentExtent
.
value
;
bool
get
isAtMax
=>
maxExtent
<=
_currentExtent
.
value
;
...
...
@@ -341,6 +363,33 @@ class _DraggableSheetExtent {
double
extentToPixels
(
double
extent
)
{
return
extent
/
maxExtent
*
availablePixels
;
}
void
dispose
()
{
_currentExtent
.
removeListener
(
onExtentChanged
);
}
_DraggableSheetExtent
copyWith
({
required
double
minExtent
,
required
double
maxExtent
,
required
bool
snap
,
required
List
<
double
>
snapSizes
,
required
double
initialExtent
,
required
VoidCallback
onExtentChanged
,
})
{
return
_DraggableSheetExtent
(
minExtent:
minExtent
,
maxExtent:
maxExtent
,
snap:
snap
,
snapSizes:
snapSizes
,
initialExtent:
initialExtent
,
onExtentChanged:
onExtentChanged
,
// Use the possibly updated initialExtent if the user hasn't dragged yet.
currentExtent:
ValueNotifier
<
double
>(
hasChanged
?
_currentExtent
.
value
.
clamp
(
minExtent
,
maxExtent
)
:
initialExtent
),
hasChanged:
hasChanged
,
);
}
}
class
_DraggableScrollableSheetState
extends
State
<
DraggableScrollableSheet
>
{
...
...
@@ -356,7 +405,7 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
snap:
widget
.
snap
,
snapSizes:
_impliedSnapSizes
(),
initialExtent:
widget
.
initialChildSize
,
listener
:
_setExtent
,
onExtentChanged
:
_setExtent
,
);
_scrollController
=
_DraggableScrollableSheetScrollController
(
extent:
_extent
);
}
...
...
@@ -385,6 +434,12 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
];
}
@override
void
didUpdateWidget
(
covariant
DraggableScrollableSheet
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
_replaceExtent
();
}
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
...
...
@@ -408,7 +463,6 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
setState
(()
{
// _extent has been updated when this is called.
});
}
@override
...
...
@@ -429,9 +483,34 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
@override
void
dispose
()
{
_scrollController
.
dispose
();
_extent
.
dispose
();
super
.
dispose
();
}
void
_replaceExtent
()
{
_extent
.
dispose
();
_extent
=
_extent
.
copyWith
(
minExtent:
widget
.
minChildSize
,
maxExtent:
widget
.
maxChildSize
,
snap:
widget
.
snap
,
snapSizes:
_impliedSnapSizes
(),
initialExtent:
widget
.
initialChildSize
,
onExtentChanged:
_setExtent
,
);
// Modify the existing scroll controller instead of replacing it so that
// developers listening to the controller do not have to rebuild their listeners.
_scrollController
.
extent
=
_extent
;
if
(
widget
.
snap
)
{
// Trigger a snap in case snap or snapSizes has changed. We put this in a
// post frame callback so that `build` can update `_extent.availablePixels`
// before this runs-we can't use the previous extent's available pixels as
// it may have changed when the widget was updated.
WidgetsBinding
.
instance
!.
addPostFrameCallback
((
Duration
timeStamp
)
{
_scrollController
.
position
.
goBallistic
(
0
);
});
}
}
String
_snapSizeErrorMessage
(
int
invalidIndex
)
{
final
List
<
String
>
snapSizesWithIndicator
=
widget
.
snapSizes
!.
asMap
().
keys
.
map
(
(
int
index
)
{
...
...
@@ -472,7 +551,7 @@ class _DraggableScrollableSheetScrollController extends ScrollController {
initialScrollOffset:
initialScrollOffset
,
);
final
_DraggableSheetExtent
extent
;
_DraggableSheetExtent
extent
;
@override
_DraggableScrollableSheetScrollPosition
createScrollPosition
(
...
...
@@ -484,7 +563,7 @@ class _DraggableScrollableSheetScrollController extends ScrollController {
physics:
physics
,
context:
context
,
oldPosition:
oldPosition
,
extent:
extent
,
getExtent:
()
=>
extent
,
);
}
...
...
@@ -493,6 +572,10 @@ class _DraggableScrollableSheetScrollController extends ScrollController {
super
.
debugFillDescription
(
description
);
description
.
add
(
'extent:
$extent
'
);
}
@override
_DraggableScrollableSheetScrollPosition
get
position
=>
super
.
position
as
_DraggableScrollableSheetScrollPosition
;
}
/// A scroll position that manages scroll activities for
...
...
@@ -516,9 +599,8 @@ class _DraggableScrollableSheetScrollPosition
bool
keepScrollOffset
=
true
,
ScrollPosition
?
oldPosition
,
String
?
debugLabel
,
required
this
.
extent
,
})
:
assert
(
extent
!=
null
),
super
(
required
this
.
getExtent
,
})
:
super
(
physics:
physics
,
context:
context
,
initialPixels:
initialPixels
,
...
...
@@ -529,9 +611,11 @@ class _DraggableScrollableSheetScrollPosition
VoidCallback
?
_dragCancelCallback
;
VoidCallback
?
_ballisticCancelCallback
;
final
_DraggableSheetExtent
e
xtent
;
final
_DraggableSheetExtent
Function
()
getE
xtent
;
bool
get
listShouldScroll
=>
pixels
>
0.0
;
_DraggableSheetExtent
get
extent
=>
getExtent
();
@override
void
beginActivity
(
ScrollActivity
?
newActivity
)
{
// Cancel the running ballistic simulation, if there is one.
...
...
packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart
View file @
ec2e041e
...
...
@@ -18,6 +18,7 @@ void main() {
Key
?
containerKey
,
Key
?
stackKey
,
NotificationListenerCallback
<
ScrollNotification
>?
onScrollNotification
,
bool
ignoreController
=
false
,
})
{
return
Directionality
(
textDirection:
TextDirection
.
ltr
,
...
...
@@ -44,7 +45,7 @@ void main() {
key:
containerKey
,
color:
const
Color
(
0xFFABCDEF
),
child:
ListView
.
builder
(
controller:
scrollController
,
controller:
ignoreController
?
null
:
scrollController
,
itemExtent:
itemExtent
,
itemCount:
itemCount
,
itemBuilder:
(
BuildContext
context
,
int
index
)
=>
Text
(
'Item
$index
'
),
...
...
@@ -323,6 +324,8 @@ void main() {
expect
(
find
.
text
(
'Item 31'
),
findsNothing
);
expect
(
find
.
text
(
'Item 70'
),
findsNothing
);
},
variant:
TargetPlatformVariant
.
all
());
debugDefaultTargetPlatformOverride
=
null
;
});
testWidgets
(
'Does not snap away from initial child on build'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -429,32 +432,32 @@ void main() {
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
(
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
),
);
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
{
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
,
...
...
@@ -481,6 +484,114 @@ void main() {
);
},
variant:
TargetPlatformVariant
.
all
());
testWidgets
(
'Changes to widget parameters are propagated'
,
(
WidgetTester
tester
)
async
{
const
Key
stackKey
=
ValueKey
<
String
>(
'stack'
);
const
Key
containerKey
=
ValueKey
<
String
>(
'container'
);
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
stackKey:
stackKey
,
containerKey:
containerKey
,
));
await
tester
.
pumpAndSettle
();
final
double
screenHeight
=
tester
.
getSize
(
find
.
byKey
(
stackKey
)).
height
;
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
5
,
precisionErrorTolerance
),
);
// Pump the same widget but with a new initial child size.
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
stackKey:
stackKey
,
containerKey:
containerKey
,
initialChildSize:
.
6
,
));
await
tester
.
pumpAndSettle
();
// We jump to the new initial size because the sheet hasn't changed yet.
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
6
,
precisionErrorTolerance
),
);
// Pump the same widget but with a new max child size.
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
stackKey:
stackKey
,
containerKey:
containerKey
,
initialChildSize:
.
6
,
maxChildSize:
.
9
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
6
,
precisionErrorTolerance
),
);
await
tester
.
drag
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
-.
6
*
screenHeight
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
9
,
precisionErrorTolerance
),
);
// Pump the same widget but with a new max child size and initial size.
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
stackKey:
stackKey
,
containerKey:
containerKey
,
maxChildSize:
.
8
,
initialChildSize:
.
7
,
));
await
tester
.
pumpAndSettle
();
// The max child size has been reduced, we should be rebuilt at the new
// max of .8. We changed the initial size again, but the sheet has already
// been changed so the new initial is ignored.
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
8
,
precisionErrorTolerance
),
);
await
tester
.
drag
(
find
.
text
(
'Item 1'
),
Offset
(
0
,
.
2
*
screenHeight
));
// Pump the same widget but with snapping enabled.
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
snap:
true
,
stackKey:
stackKey
,
containerKey:
containerKey
,
maxChildSize:
.
8
,
snapSizes:
<
double
>[.
5
],
));
await
tester
.
pumpAndSettle
();
// Sheet snaps immediately on a change to snap.
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
5
,
precisionErrorTolerance
),
);
final
List
<
double
>
snapSizes
=
<
double
>[.
6
];
// Change the snap sizes.
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
snap:
true
,
stackKey:
stackKey
,
containerKey:
containerKey
,
maxChildSize:
.
8
,
snapSizes:
snapSizes
,
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byKey
(
containerKey
)).
height
/
screenHeight
,
closeTo
(.
6
,
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'
);
...
...
@@ -509,6 +620,21 @@ void main() {
},
variant:
TargetPlatformVariant
.
all
());
testWidgets
(
"Changing parameters with an un-listened controller doesn't throw"
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
snap:
true
,
// Will prevent the sheet's child from listening to the controller.
ignoreController:
true
,
));
await
tester
.
pumpAndSettle
();
await
tester
.
pumpWidget
(
_boilerplate
(
null
,
snap:
true
,
));
await
tester
.
pumpAndSettle
();
},
variant:
TargetPlatformVariant
.
all
());
testWidgets
(
'ScrollNotification correctly dispatched when flung without covering its container'
,
(
WidgetTester
tester
)
async
{
final
List
<
Type
>
notificationTypes
=
<
Type
>[];
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