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
c7d29350
Unverified
Commit
c7d29350
authored
Apr 07, 2022
by
Kate Lovett
Committed by
GitHub
Apr 07, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix reverse cases for App Bar scrolled under behavior (#101460)
parent
f5f9ad91
Changes
5
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
633 additions
and
303 deletions
+633
-303
app_bar.dart
packages/flutter/lib/src/material/app_bar.dart
+35
-19
scaffold.dart
packages/flutter/lib/src/material/scaffold.dart
+1
-1
scroll_notification_observer.dart
...flutter/lib/src/widgets/scroll_notification_observer.dart
+168
-0
app_bar_test.dart
packages/flutter/test/material/app_bar_test.dart
+426
-280
scaffold_test.dart
packages/flutter/test/material/scaffold_test.dart
+3
-3
No files found.
packages/flutter/lib/src/material/app_bar.dart
View file @
c7d29350
...
...
@@ -737,24 +737,24 @@ class _AppBarState extends State<AppBar> {
static
const
double
_defaultElevation
=
4.0
;
static
const
Color
_defaultShadowColor
=
Color
(
0xFF000000
);
Scroll
NotificationObserverState
?
_scroll
NotificationObserver
;
Scroll
MetricsNotificationObserverState
?
_scrollMetrics
NotificationObserver
;
bool
_scrolledUnder
=
false
;
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
if
(
_scrollNotificationObserver
!=
null
)
_scroll
NotificationObserver
!.
removeListener
(
_handleScroll
Notification
);
_scroll
NotificationObserver
=
Scroll
NotificationObserver
.
of
(
context
);
if
(
_scrollNotificationObserver
!=
null
)
_scroll
NotificationObserver
!.
addListener
(
_handleScroll
Notification
);
if
(
_scroll
Metrics
NotificationObserver
!=
null
)
_scroll
MetricsNotificationObserver
!.
removeListener
(
_handleScrollMetrics
Notification
);
_scroll
MetricsNotificationObserver
=
ScrollMetrics
NotificationObserver
.
of
(
context
);
if
(
_scroll
Metrics
NotificationObserver
!=
null
)
_scroll
MetricsNotificationObserver
!.
addListener
(
_handleScrollMetrics
Notification
);
}
@override
void
dispose
()
{
if
(
_scrollNotificationObserver
!=
null
)
{
_scroll
NotificationObserver
!.
removeListener
(
_handleScroll
Notification
);
_scrollNotificationObserver
=
null
;
if
(
_scroll
Metrics
NotificationObserver
!=
null
)
{
_scroll
MetricsNotificationObserver
!.
removeListener
(
_handleScrollMetrics
Notification
);
_scroll
Metrics
NotificationObserver
=
null
;
}
super
.
dispose
();
}
...
...
@@ -767,18 +767,34 @@ 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
&&
notification
.
metrics
.
axis
==
Axis
.
vertical
;
if
(
_scrolledUnder
!=
oldScrolledUnder
)
{
setState
(()
{
// React to a change in MaterialState.scrolledUnder
});
void
_handleScrollMetricsNotification
(
ScrollMetricsNotification
notification
)
{
final
bool
oldScrolledUnder
=
_scrolledUnder
;
final
ScrollMetrics
metrics
=
notification
.
metrics
;
if
(
notification
.
depth
!=
0
)
{
_scrolledUnder
=
false
;
}
else
{
switch
(
metrics
.
axisDirection
)
{
case
AxisDirection
.
up
:
// Scroll view is reversed
_scrolledUnder
=
metrics
.
extentAfter
>
0
;
break
;
case
AxisDirection
.
down
:
_scrolledUnder
=
metrics
.
extentBefore
>
0
;
break
;
case
AxisDirection
.
right
:
case
AxisDirection
.
left
:
// Scrolled under is only supported in the vertical axis.
_scrolledUnder
=
false
;
break
;
}
}
if
(
_scrolledUnder
!=
oldScrolledUnder
)
{
setState
(()
{
// React to a change in MaterialState.scrolledUnder
});
}
}
Color
_resolveColor
(
Set
<
MaterialState
>
states
,
Color
?
widgetColor
,
Color
?
themeColor
,
Color
defaultColor
)
{
...
...
packages/flutter/lib/src/material/scaffold.dart
View file @
c7d29350
...
...
@@ -3096,7 +3096,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
return
_ScaffoldScope
(
hasDrawer:
hasDrawer
,
geometryNotifier:
_geometryNotifier
,
child:
ScrollNotificationObserver
(
child:
Scroll
Metrics
NotificationObserver
(
child:
Material
(
color:
widget
.
backgroundColor
??
themeData
.
scaffoldBackgroundColor
,
child:
AnimatedBuilder
(
animation:
_floatingActionButtonMoveController
,
builder:
(
BuildContext
context
,
Widget
?
child
)
{
...
...
packages/flutter/lib/src/widgets/scroll_notification_observer.dart
View file @
c7d29350
...
...
@@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart';
import
'framework.dart'
;
import
'notification_listener.dart'
;
import
'scroll_notification.dart'
;
import
'scroll_position.dart'
;
/// A [ScrollNotification] listener for [ScrollNotificationObserver].
///
...
...
@@ -172,3 +173,170 @@ class ScrollNotificationObserverState extends State<ScrollNotificationObserver>
super
.
dispose
();
}
}
/// A [ScrollMetricsNotification] listener for [ScrollMetricsNotificationObserver].
///
/// [ScrollMetricsNotificationObserver] 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
ScrollMetricsNotificationCallback
=
void
Function
(
ScrollMetricsNotification
notification
);
class
_ScrollMetricsNotificationObserverScope
extends
InheritedWidget
{
const
_ScrollMetricsNotificationObserverScope
({
Key
?
key
,
required
Widget
child
,
required
ScrollMetricsNotificationObserverState
scrollMetricsNotificationObserverState
,
})
:
_scrollMetricsNotificationObserverState
=
scrollMetricsNotificationObserverState
,
super
(
key:
key
,
child:
child
);
final
ScrollMetricsNotificationObserverState
_scrollMetricsNotificationObserverState
;
@override
bool
updateShouldNotify
(
_ScrollMetricsNotificationObserverScope
old
)
{
return
_scrollMetricsNotificationObserverState
!=
old
.
_scrollMetricsNotificationObserverState
;
}
}
class
_MetricsListenerEntry
extends
LinkedListEntry
<
_MetricsListenerEntry
>
{
_MetricsListenerEntry
(
this
.
listener
);
final
ScrollMetricsNotificationCallback
listener
;
}
/// Notifies its listeners when a descendant ScrollMetrics are
/// initialized or updated.
///
/// To add a listener to a [ScrollMetricsNotificationObserver] ancestor:
/// ```dart
/// void listener(ScrollMetricsNotification notification) {
/// // Do something, maybe setState()
/// }
/// ScrollMetricsNotificationObserver.of(context).addListener(listener)
/// ```
///
/// To remove the listener from a [ScrollMetricsNotificationObserver] ancestor:
/// ```dart
/// ScrollMetricsNotificationObserver.of(context).removeListener(listener);
/// ```
///
/// Stateful widgets that share an ancestor [ScrollMetricsNotificationObserver]
/// 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
ScrollMetricsNotificationObserver
extends
StatefulWidget
{
/// Create a [ScrollMetricsNotificationObserver].
///
/// The [child] parameter must not be null.
const
ScrollMetricsNotificationObserver
({
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 [ScrollMetricsNotificationObserver] widget, then
/// null is returned.
static
ScrollMetricsNotificationObserverState
?
of
(
BuildContext
context
)
{
return
context
.
dependOnInheritedWidgetOfExactType
<
_ScrollMetricsNotificationObserverScope
>()?.
_scrollMetricsNotificationObserverState
;
}
@override
ScrollMetricsNotificationObserverState
createState
()
=>
ScrollMetricsNotificationObserverState
();
}
/// The listener list state for a [ScrollMetricsNotificationObserver] returned
/// by [ScrollMetricsNotificationObserver.of].
///
/// [ScrollMetricsNotificationObserver] 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
ScrollMetricsNotificationObserverState
extends
State
<
ScrollMetricsNotificationObserver
>
{
LinkedList
<
_MetricsListenerEntry
>?
_listeners
=
LinkedList
<
_MetricsListenerEntry
>();
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 [ScrollMetricsNotificationCallback] that will be called each time
/// a descendant scrolls.
void
addListener
(
ScrollMetricsNotificationCallback
listener
)
{
assert
(
_debugAssertNotDisposed
());
_listeners
!.
add
(
_MetricsListenerEntry
(
listener
));
}
/// Remove the specified [ScrollMetricsNotificationCallback].
void
removeListener
(
ScrollMetricsNotificationCallback
listener
)
{
assert
(
_debugAssertNotDisposed
());
for
(
final
_MetricsListenerEntry
entry
in
_listeners
!)
{
if
(
entry
.
listener
==
listener
)
{
entry
.
unlink
();
return
;
}
}
}
void
_notifyListeners
(
ScrollMetricsNotification
notification
)
{
assert
(
_debugAssertNotDisposed
());
if
(
_listeners
!.
isEmpty
)
return
;
final
List
<
_MetricsListenerEntry
>
localListeners
=
List
<
_MetricsListenerEntry
>.
of
(
_listeners
!);
for
(
final
_MetricsListenerEntry
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:
()
=>
<
DiagnosticsNode
>[
DiagnosticsProperty
<
ScrollMetricsNotificationObserverState
>(
'The
$runtimeType
sending notification was'
,
this
,
style:
DiagnosticsTreeStyle
.
errorProperty
,
),
],
));
}
}
}
@override
Widget
build
(
BuildContext
context
)
{
return
NotificationListener
<
ScrollMetricsNotification
>(
onNotification:
(
ScrollMetricsNotification
notification
)
{
_notifyListeners
(
notification
);
return
false
;
},
child:
_ScrollMetricsNotificationObserverScope
(
scrollMetricsNotificationObserverState:
this
,
child:
widget
.
child
,
),
);
}
@override
void
dispose
()
{
assert
(
_debugAssertNotDisposed
());
_listeners
=
null
;
super
.
dispose
();
}
}
packages/flutter/test/material/app_bar_test.dart
View file @
c7d29350
This diff is collapsed.
Click to expand it.
packages/flutter/test/material/scaffold_test.dart
View file @
c7d29350
...
...
@@ -2302,9 +2302,9 @@ void main() {
' PhysicalModel
\n
'
' AnimatedPhysicalModel
\n
'
' Material
\n
'
' _ScrollNotificationObserverScope
\n
'
' NotificationListener<ScrollNotification>
\n
'
' ScrollNotificationObserver
\n
'
' _Scroll
Metrics
NotificationObserverScope
\n
'
' NotificationListener<Scroll
Metrics
Notification>
\n
'
' Scroll
Metrics
NotificationObserver
\n
'
' _ScaffoldScope
\n
'
' Scaffold
\n
'
' MediaQuery
\n
'
...
...
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