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
28481267
Unverified
Commit
28481267
authored
Apr 14, 2021
by
Hans Muller
Committed by
GitHub
Apr 14, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Re-land "Added MaterialState.scrolledUnder and support in AppBar.backgroundColor" (#80395)
parent
d1d80aa8
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
612 additions
and
29 deletions
+612
-29
app_bar.dart
packages/flutter/lib/src/material/app_bar.dart
+58
-4
flexible_space_bar.dart
packages/flutter/lib/src/material/flexible_space_bar.dart
+19
-3
material_state.dart
packages/flutter/lib/src/material/material_state.dart
+7
-1
scaffold.dart
packages/flutter/lib/src/material/scaffold.dart
+23
-21
scroll_notification_observer.dart
...flutter/lib/src/widgets/scroll_notification_observer.dart
+174
-0
widgets.dart
packages/flutter/lib/widgets.dart
+1
-0
app_bar_test.dart
packages/flutter/test/material/app_bar_test.dart
+262
-0
scaffold_test.dart
packages/flutter/test/material/scaffold_test.dart
+3
-0
scroll_notification_test.dart
packages/flutter/test/widgets/scroll_notification_test.dart
+65
-0
No files found.
packages/flutter/lib/src/material/app_bar.dart
View file @
28481267
...
...
@@ -19,6 +19,7 @@ import 'icon_button.dart';
import
'icons.dart'
;
import
'material.dart'
;
import
'material_localizations.dart'
;
import
'material_state.dart'
;
import
'scaffold.dart'
;
import
'tabs.dart'
;
import
'text_theme.dart'
;
...
...
@@ -414,6 +415,10 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
/// null, then [AppBar] uses the overall theme's [ColorScheme.primary] if the
/// overall theme's brightness is [Brightness.light], and [ColorScheme.surface]
/// if the overall theme's [brightness] is [Brightness.dark].
///
/// If this color is a [MaterialStateColor] it will be resolved against
/// [MaterialState.scrolledUnder] when the content of the app's
/// primary scrollable overlaps the app bar.
/// {@endtemplate}
///
/// See also:
...
...
@@ -704,6 +709,28 @@ class _AppBarState extends State<AppBar> {
static
const
double
_defaultElevation
=
4.0
;
static
const
Color
_defaultShadowColor
=
Color
(
0xFF000000
);
ScrollNotificationObserverState
?
_scrollNotificationObserver
;
bool
_scrolledUnder
=
false
;
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
if
(
_scrollNotificationObserver
!=
null
)
_scrollNotificationObserver
!.
removeListener
(
_handleScrollNotification
);
_scrollNotificationObserver
=
ScrollNotificationObserver
.
of
(
context
);
if
(
_scrollNotificationObserver
!=
null
)
_scrollNotificationObserver
!.
addListener
(
_handleScrollNotification
);
}
@override
void
dispose
()
{
if
(
_scrollNotificationObserver
!=
null
)
{
_scrollNotificationObserver
!.
removeListener
(
_handleScrollNotification
);
_scrollNotificationObserver
=
null
;
}
super
.
dispose
();
}
void
_handleDrawerButton
()
{
Scaffold
.
of
(
context
).
openDrawer
();
}
...
...
@@ -712,6 +739,24 @@ class _AppBarState extends State<AppBar> {
Scaffold
.
of
(
context
).
openEndDrawer
();
}
void
_handleScrollNotification
(
ScrollNotification
notification
)
{
if
(
notification
is
ScrollUpdateNotification
)
{
final
bool
oldScrolledUnder
=
_scrolledUnder
;
_scrolledUnder
=
notification
.
depth
==
0
&&
notification
.
metrics
.
extentBefore
>
0
;
if
(
_scrolledUnder
!=
oldScrolledUnder
)
{
setState
(()
{
// React to a change in MaterialState.scrolledUnder
});
}
}
}
Color
_resolveColor
(
Set
<
MaterialState
>
states
,
Color
?
widgetColor
,
Color
?
themeColor
,
Color
defaultColor
)
{
return
MaterialStateProperty
.
resolveAs
<
Color
?>(
widgetColor
,
states
)
??
MaterialStateProperty
.
resolveAs
<
Color
?>(
themeColor
,
states
)
??
MaterialStateProperty
.
resolveAs
<
Color
>(
defaultColor
,
states
);
}
SystemUiOverlayStyle
_systemOverlayStyleForBrightness
(
Brightness
brightness
)
{
return
brightness
==
Brightness
.
dark
?
SystemUiOverlayStyle
.
light
:
SystemUiOverlayStyle
.
dark
;
}
...
...
@@ -726,6 +771,11 @@ class _AppBarState extends State<AppBar> {
final
ScaffoldState
?
scaffold
=
Scaffold
.
maybeOf
(
context
);
final
ModalRoute
<
dynamic
>?
parentRoute
=
ModalRoute
.
of
(
context
);
final
FlexibleSpaceBarSettings
?
settings
=
context
.
dependOnInheritedWidgetOfExactType
<
FlexibleSpaceBarSettings
>();
final
Set
<
MaterialState
>
states
=
<
MaterialState
>{
if
(
settings
?.
isScrolledUnder
??
_scrolledUnder
)
MaterialState
.
scrolledUnder
,
};
final
bool
hasDrawer
=
scaffold
?.
hasDrawer
??
false
;
final
bool
hasEndDrawer
=
scaffold
?.
hasEndDrawer
??
false
;
final
bool
canPop
=
parentRoute
?.
canPop
??
false
;
...
...
@@ -738,9 +788,11 @@ class _AppBarState extends State<AppBar> {
?
widget
.
backgroundColor
??
appBarTheme
.
backgroundColor
??
theme
.
primaryColor
:
widget
.
backgroundColor
??
appBarTheme
.
backgroundColor
??
(
colorScheme
.
brightness
==
Brightness
.
dark
?
colorScheme
.
surface
:
colorScheme
.
primary
);
:
_resolveColor
(
states
,
widget
.
backgroundColor
,
appBarTheme
.
backgroundColor
,
colorScheme
.
brightness
==
Brightness
.
dark
?
colorScheme
.
surface
:
colorScheme
.
primary
);
final
Color
foregroundColor
=
widget
.
foregroundColor
??
appBarTheme
.
foregroundColor
...
...
@@ -1145,6 +1197,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
final
double
extraToolbarHeight
=
math
.
max
(
minExtent
-
_bottomHeight
-
topPadding
-
(
toolbarHeight
??
kToolbarHeight
),
0.0
);
final
double
visibleToolbarHeight
=
visibleMainHeight
-
_bottomHeight
-
extraToolbarHeight
;
final
bool
isScrolledUnder
=
overlapsContent
||
(
pinned
&&
shrinkOffset
>
maxExtent
-
minExtent
);
final
bool
isPinnedWithOpacityFade
=
pinned
&&
floating
&&
bottom
!=
null
&&
extraToolbarHeight
==
0.0
;
final
double
toolbarOpacity
=
!
pinned
||
isPinnedWithOpacityFade
?
(
visibleToolbarHeight
/
(
toolbarHeight
??
kToolbarHeight
)).
clamp
(
0.0
,
1.0
)
...
...
@@ -1155,6 +1208,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
maxExtent:
maxExtent
,
currentExtent:
math
.
max
(
minExtent
,
maxExtent
-
shrinkOffset
),
toolbarOpacity:
toolbarOpacity
,
isScrolledUnder:
isScrolledUnder
,
child:
AppBar
(
leading:
leading
,
automaticallyImplyLeading:
automaticallyImplyLeading
,
...
...
@@ -1164,7 +1218,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
?
Semantics
(
child:
flexibleSpace
,
header:
true
)
:
flexibleSpace
,
bottom:
bottom
,
elevation:
forceElevated
||
overlapsContent
||
(
pinned
&&
shrinkOffset
>
maxExtent
-
minExtent
)
?
elevation
:
0.0
,
elevation:
forceElevated
||
isScrolledUnder
?
elevation
:
0.0
,
shadowColor:
shadowColor
,
backgroundColor:
backgroundColor
,
foregroundColor:
foregroundColor
,
...
...
packages/flutter/lib/src/material/flexible_space_bar.dart
View file @
28481267
...
...
@@ -210,8 +210,9 @@ class FlexibleSpaceBar extends StatefulWidget {
/// height of the resulting [FlexibleSpaceBar] when fully expanded.
/// `currentExtent` sets the scale of the [FlexibleSpaceBar.background] and
/// [FlexibleSpaceBar.title] widgets of [FlexibleSpaceBar] upon
/// initialization.
///
/// initialization. `scrolledUnder` is true if the the [FlexibleSpaceBar]
/// overlaps the app's primary scrollable, false if it does not, and null
/// if the caller has not determined as much.
/// See also:
///
/// * [FlexibleSpaceBarSettings] which creates a settings object that can be
...
...
@@ -220,6 +221,7 @@ class FlexibleSpaceBar extends StatefulWidget {
double
?
toolbarOpacity
,
double
?
minExtent
,
double
?
maxExtent
,
bool
?
isScrolledUnder
,
required
double
currentExtent
,
required
Widget
child
,
})
{
...
...
@@ -228,6 +230,7 @@ class FlexibleSpaceBar extends StatefulWidget {
toolbarOpacity:
toolbarOpacity
??
1.0
,
minExtent:
minExtent
??
currentExtent
,
maxExtent:
maxExtent
??
currentExtent
,
isScrolledUnder:
isScrolledUnder
,
currentExtent:
currentExtent
,
child:
child
,
);
...
...
@@ -441,6 +444,7 @@ class FlexibleSpaceBarSettings extends InheritedWidget {
required
this
.
maxExtent
,
required
this
.
currentExtent
,
required
Widget
child
,
this
.
isScrolledUnder
,
})
:
assert
(
toolbarOpacity
!=
null
),
assert
(
minExtent
!=
null
&&
minExtent
>=
0
),
assert
(
maxExtent
!=
null
&&
maxExtent
>=
0
),
...
...
@@ -465,11 +469,23 @@ class FlexibleSpaceBarSettings extends InheritedWidget {
/// these elements upon initialization.
final
double
currentExtent
;
/// True if the FlexibleSpaceBar overlaps the primary scrollable's contents.
///
/// This value is used by the [AppBar] to resolve
/// [AppBar.backgroundColor] against [MaterialState.scrolledUnder],
/// i.e. to enable apps to specify different colors when content
/// has been scrolled up and behind the app bar.
///
/// Null if the caller hasn't determined if the FlexibleSpaceBar
/// overlaps the primary scrollable's contents.
final
bool
?
isScrolledUnder
;
@override
bool
updateShouldNotify
(
FlexibleSpaceBarSettings
oldWidget
)
{
return
toolbarOpacity
!=
oldWidget
.
toolbarOpacity
||
minExtent
!=
oldWidget
.
minExtent
||
maxExtent
!=
oldWidget
.
maxExtent
||
currentExtent
!=
oldWidget
.
currentExtent
;
||
currentExtent
!=
oldWidget
.
currentExtent
||
isScrolledUnder
!=
oldWidget
.
isScrolledUnder
;
}
}
packages/flutter/lib/src/material/material_state.dart
View file @
28481267
...
...
@@ -63,7 +63,13 @@ enum MaterialState {
/// See: https://material.io/design/interaction/states.html#selected.
selected
,
/// The state when this widget disabled and can not be interacted with.
/// The state when this widget overlaps the content of a scrollable below.
///
/// Used by [AppBar] to indicate that the primary scrollable's
/// content has scrolled up and behind the app bar.
scrolledUnder
,
/// The state when this widget is disabled and cannot be interacted with.
///
/// Disabled widgets should not respond to hover, focus, press, or drag
/// interactions.
...
...
packages/flutter/lib/src/material/scaffold.dart
View file @
28481267
...
...
@@ -3238,27 +3238,29 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
return
_ScaffoldScope
(
hasDrawer:
hasDrawer
,
geometryNotifier:
_geometryNotifier
,
child:
Material
(
color:
widget
.
backgroundColor
??
themeData
.
scaffoldBackgroundColor
,
child:
AnimatedBuilder
(
animation:
_floatingActionButtonMoveController
,
builder:
(
BuildContext
context
,
Widget
?
child
)
{
return
CustomMultiChildLayout
(
children:
children
,
delegate:
_ScaffoldLayout
(
extendBody:
_extendBody
,
extendBodyBehindAppBar:
widget
.
extendBodyBehindAppBar
,
minInsets:
minInsets
,
minViewPadding:
minViewPadding
,
currentFloatingActionButtonLocation:
_floatingActionButtonLocation
!,
floatingActionButtonMoveAnimationProgress:
_floatingActionButtonMoveController
.
value
,
floatingActionButtonMotionAnimator:
_floatingActionButtonAnimator
,
geometryNotifier:
_geometryNotifier
,
previousFloatingActionButtonLocation:
_previousFloatingActionButtonLocation
!,
textDirection:
textDirection
,
isSnackBarFloating:
isSnackBarFloating
,
snackBarWidth:
snackBarWidth
,
),
);
}),
child:
ScrollNotificationObserver
(
child:
Material
(
color:
widget
.
backgroundColor
??
themeData
.
scaffoldBackgroundColor
,
child:
AnimatedBuilder
(
animation:
_floatingActionButtonMoveController
,
builder:
(
BuildContext
context
,
Widget
?
child
)
{
return
CustomMultiChildLayout
(
children:
children
,
delegate:
_ScaffoldLayout
(
extendBody:
_extendBody
,
extendBodyBehindAppBar:
widget
.
extendBodyBehindAppBar
,
minInsets:
minInsets
,
minViewPadding:
minViewPadding
,
currentFloatingActionButtonLocation:
_floatingActionButtonLocation
!,
floatingActionButtonMoveAnimationProgress:
_floatingActionButtonMoveController
.
value
,
floatingActionButtonMotionAnimator:
_floatingActionButtonAnimator
,
geometryNotifier:
_geometryNotifier
,
previousFloatingActionButtonLocation:
_previousFloatingActionButtonLocation
!,
textDirection:
textDirection
,
isSnackBarFloating:
isSnackBarFloating
,
snackBarWidth:
snackBarWidth
,
),
);
}),
),
),
);
}
...
...
packages/flutter/lib/src/widgets/scroll_notification_observer.dart
0 → 100644
View file @
28481267
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:collection'
;
import
'package:flutter/foundation.dart'
;
import
'framework.dart'
;
import
'notification_listener.dart'
;
import
'scroll_notification.dart'
;
/// A [ScrollNotification] listener for [ScrollNotificationObserver].
///
/// [ScrollNotificationObserver] is similar to
/// [NotificationListener]. It supports a listener list instead of
/// just a single listener and its listeners run unconditionally, they
/// do not require a gating boolean return value.
typedef
ScrollNotificationCallback
=
void
Function
(
ScrollNotification
notification
);
class
_ScrollNotificationObserverScope
extends
InheritedWidget
{
const
_ScrollNotificationObserverScope
({
Key
?
key
,
required
Widget
child
,
required
ScrollNotificationObserverState
scrollNotificationObserverState
,
})
:
_scrollNotificationObserverState
=
scrollNotificationObserverState
,
super
(
key:
key
,
child:
child
);
final
ScrollNotificationObserverState
_scrollNotificationObserverState
;
@override
bool
updateShouldNotify
(
_ScrollNotificationObserverScope
old
)
=>
_scrollNotificationObserverState
!=
old
.
_scrollNotificationObserverState
;
}
class
_ListenerEntry
extends
LinkedListEntry
<
_ListenerEntry
>
{
_ListenerEntry
(
this
.
listener
);
final
ScrollNotificationCallback
listener
;
}
/// Notifies its listeners when a descendant scrolls.
///
/// To add a listener to a [ScrollNotificationObserver] ancestor:
/// ```dart
/// void listener(ScrollNotification notification) {
/// // Do something, maybe setState()
/// }
/// ScrollNotificationObserver.of(context).addListener(listener)
/// ```
///
/// To remove the listener from a [ScrollNotificationObserver] ancestor:
/// ```dart
/// ScrollNotificationObserver.of(context).removeListener(listener);
///```
///
/// Stateful widgets that share an ancestor [ScrollNotificationObserver] typically
/// add a listener in [State.didChangeDependencies] (removing the old one
/// if necessary) and remove the listener in their [State.dispose] method.
///
/// This widget is similar to [NotificationListener]. It supports
/// a listener list instead of just a single listener and its listeners
/// run unconditionally, they do not require a gating boolean return value.
class
ScrollNotificationObserver
extends
StatefulWidget
{
/// Create a [ScrollNotificationObserver].
///
/// The [child] parameter must not be null.
const
ScrollNotificationObserver
({
Key
?
key
,
required
this
.
child
,
})
:
assert
(
child
!=
null
),
super
(
key:
key
);
/// The subtree below this widget.
final
Widget
child
;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [ScrollNotificationObserver] widget, then null is returned.
static
ScrollNotificationObserverState
?
of
(
BuildContext
context
)
{
return
context
.
dependOnInheritedWidgetOfExactType
<
_ScrollNotificationObserverScope
>()?.
_scrollNotificationObserverState
;
}
@override
ScrollNotificationObserverState
createState
()
=>
ScrollNotificationObserverState
();
}
/// The listener list state for a [ScrollNotificationObserver] returned by
/// [ScrollNotificationObserver.of].
///
/// [ScrollNotificationObserver] is similar to
/// [NotificationListener]. It supports a listener list instead of
/// just a single listener and its listeners run unconditionally, they
/// do not require a gating boolean return value.
class
ScrollNotificationObserverState
extends
State
<
ScrollNotificationObserver
>
{
LinkedList
<
_ListenerEntry
>?
_listeners
=
LinkedList
<
_ListenerEntry
>();
bool
_debugAssertNotDisposed
()
{
assert
(()
{
if
(
_listeners
==
null
)
{
throw
FlutterError
(
'A
$runtimeType
was used after being disposed.
\n
'
'Once you have called dispose() on a
$runtimeType
, it can no longer be used.'
,
);
}
return
true
;
}());
return
true
;
}
/// Add a [ScrollNotificationCallback] that will be called each time
/// a descendant scrolls.
void
addListener
(
ScrollNotificationCallback
listener
)
{
assert
(
_debugAssertNotDisposed
());
_listeners
!.
add
(
_ListenerEntry
(
listener
));
}
/// Remove the specified [ScrollNotificationCallback].
void
removeListener
(
ScrollNotificationCallback
listener
)
{
assert
(
_debugAssertNotDisposed
());
for
(
final
_ListenerEntry
entry
in
_listeners
!)
{
if
(
entry
.
listener
==
listener
)
{
entry
.
unlink
();
return
;
}
}
}
void
_notifyListeners
(
ScrollNotification
notification
)
{
assert
(
_debugAssertNotDisposed
());
if
(
_listeners
!.
isEmpty
)
return
;
final
List
<
_ListenerEntry
>
localListeners
=
List
<
_ListenerEntry
>.
from
(
_listeners
!);
for
(
final
_ListenerEntry
entry
in
localListeners
)
{
try
{
if
(
entry
.
list
!=
null
)
entry
.
listener
(
notification
);
}
catch
(
exception
,
stack
)
{
FlutterError
.
reportError
(
FlutterErrorDetails
(
exception:
exception
,
stack:
stack
,
library
:
'widget library'
,
context:
ErrorDescription
(
'while dispatching notifications for
$runtimeType
'
),
informationCollector:
()
sync
*
{
yield
DiagnosticsProperty
<
ScrollNotificationObserverState
>(
'The
$runtimeType
sending notification was'
,
this
,
style:
DiagnosticsTreeStyle
.
errorProperty
,
);
},
));
}
}
}
@override
Widget
build
(
BuildContext
context
)
{
return
NotificationListener
<
ScrollNotification
>(
onNotification:
(
ScrollNotification
notification
)
{
_notifyListeners
(
notification
);
return
false
;
},
child:
_ScrollNotificationObserverScope
(
scrollNotificationObserverState:
this
,
child:
widget
.
child
,
),
);
}
@override
void
dispose
()
{
assert
(
_debugAssertNotDisposed
());
_listeners
=
null
;
super
.
dispose
();
}
}
packages/flutter/lib/widgets.dart
View file @
28481267
...
...
@@ -98,6 +98,7 @@ export 'src/widgets/scroll_context.dart';
export
'src/widgets/scroll_controller.dart'
;
export
'src/widgets/scroll_metrics.dart'
;
export
'src/widgets/scroll_notification.dart'
;
export
'src/widgets/scroll_notification_observer.dart'
;
export
'src/widgets/scroll_physics.dart'
;
export
'src/widgets/scroll_position.dart'
;
export
'src/widgets/scroll_position_with_single_context.dart'
;
...
...
packages/flutter/test/material/app_bar_test.dart
View file @
28481267
...
...
@@ -2560,4 +2560,266 @@ void main() {
expect
(
actionIconTheme
.
color
,
foregroundColor
);
});
testWidgets
(
'SliverAppBar.backgroundColor MaterialStateColor scrolledUnder'
,
(
WidgetTester
tester
)
async
{
const
double
collapsedHeight
=
kToolbarHeight
;
const
double
expandedHeight
=
200.0
;
const
Color
scrolledColor
=
Color
(
0xff00ff00
);
const
Color
defaultColor
=
Color
(
0xff0000ff
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
CustomScrollView
(
slivers:
<
Widget
>[
SliverAppBar
(
backwardsCompatibility:
false
,
elevation:
0
,
backgroundColor:
MaterialStateColor
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
scrolledUnder
)
?
scrolledColor
:
defaultColor
;
}),
expandedHeight:
expandedHeight
,
pinned:
true
,
),
SliverList
(
delegate:
SliverChildListDelegate
(
<
Widget
>[
Container
(
height:
1200.0
,
color:
Colors
.
teal
),
],
),
),
],
),
),
),
);
Finder
findAppBarMaterial
()
{
return
find
.
descendant
(
of:
find
.
byType
(
AppBar
),
matching:
find
.
byType
(
Material
));
}
Color
?
getAppBarBackgroundColor
()
{
return
tester
.
widget
<
Material
>(
findAppBarMaterial
()).
color
;
}
expect
(
getAppBarBackgroundColor
(),
defaultColor
);
expect
(
tester
.
getSize
(
findAppBarMaterial
()).
height
,
expandedHeight
);
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
50.0
,
400.0
));
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
-
expandedHeight
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
getAppBarBackgroundColor
(),
scrolledColor
);
expect
(
tester
.
getSize
(
findAppBarMaterial
()).
height
,
collapsedHeight
);
gesture
=
await
tester
.
startGesture
(
const
Offset
(
50.0
,
300.0
));
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
expandedHeight
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
getAppBarBackgroundColor
(),
defaultColor
);
expect
(
tester
.
getSize
(
findAppBarMaterial
()).
height
,
expandedHeight
);
});
testWidgets
(
'SliverAppBar.backgroundColor with FlexibleSpace MaterialStateColor scrolledUnder'
,
(
WidgetTester
tester
)
async
{
const
double
collapsedHeight
=
kToolbarHeight
;
const
double
expandedHeight
=
200.0
;
const
Color
scrolledColor
=
Color
(
0xff00ff00
);
const
Color
defaultColor
=
Color
(
0xff0000ff
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
CustomScrollView
(
slivers:
<
Widget
>[
SliverAppBar
(
backwardsCompatibility:
false
,
elevation:
0
,
backgroundColor:
MaterialStateColor
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
scrolledUnder
)
?
scrolledColor
:
defaultColor
;
}),
expandedHeight:
expandedHeight
,
pinned:
true
,
flexibleSpace:
const
FlexibleSpaceBar
(
title:
Text
(
'SliverAppBar'
),
),
),
SliverList
(
delegate:
SliverChildListDelegate
(
<
Widget
>[
Container
(
height:
1200.0
,
color:
Colors
.
teal
),
],
),
),
],
),
),
),
);
Finder
findAppBarMaterial
()
{
// There are 2 Material widgets below AppBar. The second is only added if
// flexibleSpace is non-null.
return
find
.
descendant
(
of:
find
.
byType
(
AppBar
),
matching:
find
.
byType
(
Material
)).
first
;
}
Color
?
getAppBarBackgroundColor
()
{
return
tester
.
widget
<
Material
>(
findAppBarMaterial
()).
color
;
}
expect
(
getAppBarBackgroundColor
(),
defaultColor
);
expect
(
tester
.
getSize
(
findAppBarMaterial
()).
height
,
expandedHeight
);
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
50.0
,
400.0
));
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
-
expandedHeight
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
getAppBarBackgroundColor
(),
scrolledColor
);
expect
(
tester
.
getSize
(
findAppBarMaterial
()).
height
,
collapsedHeight
);
gesture
=
await
tester
.
startGesture
(
const
Offset
(
50.0
,
300.0
));
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
expandedHeight
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
getAppBarBackgroundColor
(),
defaultColor
);
expect
(
tester
.
getSize
(
findAppBarMaterial
()).
height
,
expandedHeight
);
});
testWidgets
(
'AppBar.backgroundColor MaterialStateColor scrolledUnder'
,
(
WidgetTester
tester
)
async
{
const
Color
scrolledColor
=
Color
(
0xff00ff00
);
const
Color
defaultColor
=
Color
(
0xff0000ff
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
appBar:
AppBar
(
backwardsCompatibility:
false
,
elevation:
0
,
backgroundColor:
MaterialStateColor
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
scrolledUnder
)
?
scrolledColor
:
defaultColor
;
}),
title:
const
Text
(
'AppBar'
),
),
body:
ListView
(
children:
<
Widget
>[
Container
(
height:
1200.0
,
color:
Colors
.
teal
),
],
),
),
),
);
Finder
findAppBarMaterial
()
{
return
find
.
descendant
(
of:
find
.
byType
(
AppBar
),
matching:
find
.
byType
(
Material
));
}
Color
?
getAppBarBackgroundColor
()
{
return
tester
.
widget
<
Material
>(
findAppBarMaterial
()).
color
;
}
expect
(
getAppBarBackgroundColor
(),
defaultColor
);
expect
(
tester
.
getSize
(
findAppBarMaterial
()).
height
,
kToolbarHeight
);
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
50.0
,
400.0
));
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
-
kToolbarHeight
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
getAppBarBackgroundColor
(),
scrolledColor
);
expect
(
tester
.
getSize
(
findAppBarMaterial
()).
height
,
kToolbarHeight
);
gesture
=
await
tester
.
startGesture
(
const
Offset
(
50.0
,
300.0
));
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
kToolbarHeight
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
getAppBarBackgroundColor
(),
defaultColor
);
expect
(
tester
.
getSize
(
findAppBarMaterial
()).
height
,
kToolbarHeight
);
});
testWidgets
(
'AppBar.backgroundColor with FlexibleSpace MaterialStateColor scrolledUnder'
,
(
WidgetTester
tester
)
async
{
const
Color
scrolledColor
=
Color
(
0xff00ff00
);
const
Color
defaultColor
=
Color
(
0xff0000ff
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
appBar:
AppBar
(
backwardsCompatibility:
false
,
elevation:
0
,
backgroundColor:
MaterialStateColor
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
scrolledUnder
)
?
scrolledColor
:
defaultColor
;
}),
title:
const
Text
(
'AppBar'
),
flexibleSpace:
const
FlexibleSpaceBar
(
title:
Text
(
'FlexibleSpace'
),
),
),
body:
ListView
(
children:
<
Widget
>[
Container
(
height:
1200.0
,
color:
Colors
.
teal
),
],
),
),
),
);
Finder
findAppBarMaterial
()
{
// There are 2 Material widgets below AppBar. The second is only added if
// flexibleSpace is non-null.
return
find
.
descendant
(
of:
find
.
byType
(
AppBar
),
matching:
find
.
byType
(
Material
)).
first
;
}
Color
?
getAppBarBackgroundColor
()
{
return
tester
.
widget
<
Material
>(
findAppBarMaterial
()).
color
;
}
expect
(
getAppBarBackgroundColor
(),
defaultColor
);
expect
(
tester
.
getSize
(
findAppBarMaterial
()).
height
,
kToolbarHeight
);
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
50.0
,
400.0
));
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
-
kToolbarHeight
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
getAppBarBackgroundColor
(),
scrolledColor
);
expect
(
tester
.
getSize
(
findAppBarMaterial
()).
height
,
kToolbarHeight
);
gesture
=
await
tester
.
startGesture
(
const
Offset
(
50.0
,
300.0
));
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
kToolbarHeight
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
getAppBarBackgroundColor
(),
defaultColor
);
expect
(
tester
.
getSize
(
findAppBarMaterial
()).
height
,
kToolbarHeight
);
});
testWidgets
(
'AppBar._handleScrollNotification safely calls setState()'
,
(
WidgetTester
tester
)
async
{
// Regression test for failures found in Google internal issue b/185192049.
final
ScrollController
controller
=
ScrollController
(
initialScrollOffset:
400
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
appBar:
AppBar
(
backwardsCompatibility:
false
,
title:
const
Text
(
'AppBar'
),
),
body:
Scrollbar
(
isAlwaysShown:
true
,
controller:
controller
,
child:
ListView
(
controller:
controller
,
children:
<
Widget
>[
Container
(
height:
1200.0
,
color:
Colors
.
teal
),
],
),
),
),
),
);
expect
(
tester
.
takeException
(),
isNull
);
});
}
packages/flutter/test/material/scaffold_test.dart
View file @
28481267
...
...
@@ -2190,6 +2190,9 @@ void main() {
' PhysicalModel
\n
'
' AnimatedPhysicalModel
\n
'
' Material
\n
'
' _ScrollNotificationObserverScope
\n
'
' NotificationListener<ScrollNotification>
\n
'
' ScrollNotificationObserver
\n
'
' _ScaffoldScope
\n
'
' Scaffold
\n
'
' MediaQuery
\n
'
...
...
packages/flutter/test/widgets/scroll_notification_test.dart
View file @
28481267
...
...
@@ -150,4 +150,69 @@ void main() {
expect
(
notificationTypes
,
equals
(
types
));
});
testWidgets
(
'ScrollNotificationObserver'
,
(
WidgetTester
tester
)
async
{
late
ScrollNotificationObserverState
observer
;
ScrollNotification
?
notification
;
void
handleNotification
(
ScrollNotification
value
)
{
if
(
value
is
ScrollStartNotification
||
value
is
ScrollUpdateNotification
||
value
is
ScrollEndNotification
)
notification
=
value
;
}
await
tester
.
pumpWidget
(
ScrollNotificationObserver
(
child:
Builder
(
builder:
(
BuildContext
context
)
{
observer
=
ScrollNotificationObserver
.
of
(
context
)!;
return
const
SingleChildScrollView
(
child:
SizedBox
(
height:
1200.0
),
);
},
),
),
);
observer
.
addListener
(
handleNotification
);
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
100.0
,
100.0
));
await
tester
.
pumpAndSettle
();
expect
(
notification
,
isA
<
ScrollStartNotification
>());
expect
(
notification
!.
depth
,
equals
(
0
));
final
ScrollStartNotification
start
=
notification
!
as
ScrollStartNotification
;
expect
(
start
.
dragDetails
,
isNotNull
);
expect
(
start
.
dragDetails
!.
globalPosition
,
equals
(
const
Offset
(
100.0
,
100.0
)));
await
gesture
.
moveBy
(
const
Offset
(-
10.0
,
-
10.0
));
await
tester
.
pumpAndSettle
();
expect
(
notification
,
isA
<
ScrollUpdateNotification
>());
expect
(
notification
!.
depth
,
equals
(
0
));
final
ScrollUpdateNotification
update
=
notification
!
as
ScrollUpdateNotification
;
expect
(
update
.
dragDetails
,
isNotNull
);
expect
(
update
.
dragDetails
!.
globalPosition
,
equals
(
const
Offset
(
90.0
,
90.0
)));
expect
(
update
.
dragDetails
!.
delta
,
equals
(
const
Offset
(
0.0
,
-
10.0
)));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
notification
,
isA
<
ScrollEndNotification
>());
expect
(
notification
!.
depth
,
equals
(
0
));
final
ScrollEndNotification
end
=
notification
!
as
ScrollEndNotification
;
expect
(
end
.
dragDetails
,
isNotNull
);
expect
(
end
.
dragDetails
!.
velocity
,
equals
(
Velocity
.
zero
));
observer
.
removeListener
(
handleNotification
);
notification
=
null
;
gesture
=
await
tester
.
startGesture
(
const
Offset
(
100.0
,
100.0
));
await
tester
.
pumpAndSettle
();
expect
(
notification
,
isNull
);
await
gesture
.
moveBy
(
const
Offset
(-
10.0
,
-
10.0
));
await
tester
.
pumpAndSettle
();
expect
(
notification
,
isNull
);
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
notification
,
isNull
);
});
}
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