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
63160b3d
Commit
63160b3d
authored
Jan 21, 2017
by
Ian Hickson
Committed by
GitHub
Jan 21, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Scrolling Refactor (#7420)
parent
e52bda2c
Changes
24
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
3085 additions
and
93 deletions
+3085
-93
app.dart
packages/flutter/lib/src/material/app.dart
+19
-1
overscroll_indicator.dart
packages/flutter/lib/src/material/overscroll_indicator.dart
+2
-0
theme_data.dart
packages/flutter/lib/src/material/theme_data.dart
+1
-1
sliver.dart
packages/flutter/lib/src/rendering/sliver.dart
+72
-61
basic.dart
packages/flutter/lib/src/widgets/basic.dart
+10
-0
framework.dart
packages/flutter/lib/src/widgets/framework.dart
+9
-2
notification_listener.dart
packages/flutter/lib/src/widgets/notification_listener.dart
+1
-1
overscroll_indicator.dart
packages/flutter/lib/src/widgets/overscroll_indicator.dart
+466
-0
scroll_absolute.dart
packages/flutter/lib/src/widgets/scroll_absolute.dart
+688
-0
scroll_behavior.dart
packages/flutter/lib/src/widgets/scroll_behavior.dart
+2
-0
scroll_configuration.dart
packages/flutter/lib/src/widgets/scroll_configuration.dart
+2
-0
scroll_notification.dart
packages/flutter/lib/src/widgets/scroll_notification.dart
+213
-0
scroll_simulation.dart
packages/flutter/lib/src/widgets/scroll_simulation.dart
+146
-22
scrollable.dart
packages/flutter/lib/src/widgets/scrollable.dart
+643
-1
viewport.dart
packages/flutter/lib/src/widgets/viewport.dart
+99
-0
widgets.dart
packages/flutter/lib/widgets.dart
+4
-0
mock_canvas.dart
packages/flutter/test/rendering/mock_canvas.dart
+21
-2
slivers_test.dart
packages/flutter/test/rendering/slivers_test.dart
+7
-2
framework_test.dart
packages/flutter/test/widgets/framework_test.dart
+20
-0
overscroll_indicator_test.dart
packages/flutter/test/widgets/overscroll_indicator_test.dart
+248
-0
scrollable_custom_scroll_behavior_test.dart
.../test/widgets/scrollable_custom_scroll_behavior_test.dart
+106
-0
scrollable_test.dart
packages/flutter/test/widgets/scrollable_test.dart
+81
-0
slivers_protocol_test.dart
packages/flutter/test/widgets/slivers_protocol_test.dart
+113
-0
slivers_test.dart
packages/flutter/test/widgets/slivers_test.dart
+112
-0
No files found.
packages/flutter/lib/src/material/app.dart
View file @
63160b3d
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
// found in the LICENSE file.
// found in the LICENSE file.
import
'package:flutter/rendering.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/widgets.dart'
;
import
'arc.dart'
;
import
'arc.dart'
;
...
@@ -212,6 +213,18 @@ class _ScrollLikeMountainViewDelegate extends ScrollConfigurationDelegate {
...
@@ -212,6 +213,18 @@ class _ScrollLikeMountainViewDelegate extends ScrollConfigurationDelegate {
bool
updateShouldNotify
(
ScrollConfigurationDelegate
old
)
=>
false
;
bool
updateShouldNotify
(
ScrollConfigurationDelegate
old
)
=>
false
;
}
}
class
_MaterialScrollBehavior
extends
ViewportScrollBehavior
{
@override
TargetPlatform
getPlatform
(
BuildContext
context
)
{
return
Theme
.
of
(
context
).
platform
;
}
@override
Color
getGlowColor
(
BuildContext
context
)
{
return
Theme
.
of
(
context
).
accentColor
;
}
}
class
_MaterialAppState
extends
State
<
MaterialApp
>
{
class
_MaterialAppState
extends
State
<
MaterialApp
>
{
HeroController
_heroController
;
HeroController
_heroController
;
...
@@ -288,9 +301,14 @@ class _MaterialAppState extends State<MaterialApp> {
...
@@ -288,9 +301,14 @@ class _MaterialAppState extends State<MaterialApp> {
return
true
;
return
true
;
});
});
re
turn
new
ScrollConfiguration
(
re
sult
=
new
ScrollConfiguration
(
delegate:
_getScrollDelegate
(
theme
.
platform
),
delegate:
_getScrollDelegate
(
theme
.
platform
),
child:
result
child:
result
);
);
return
new
ScrollConfiguration2
(
delegate:
new
_MaterialScrollBehavior
(),
child:
result
);
}
}
}
}
packages/flutter/lib/src/material/overscroll_indicator.dart
View file @
63160b3d
...
@@ -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.
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
import
'dart:async'
show
Timer
;
import
'dart:async'
show
Timer
;
import
'dart:math'
as
math
;
import
'dart:math'
as
math
;
...
...
packages/flutter/lib/src/material/theme_data.dart
View file @
63160b3d
...
@@ -547,5 +547,5 @@ class ThemeData {
...
@@ -547,5 +547,5 @@ class ThemeData {
}
}
@override
@override
String
toString
()
=>
'
$runtimeType
(
$brightness
$primaryColor
etc...)'
;
String
toString
()
=>
'
$runtimeType
(
$
{ platform != defaultTargetPlatform ? "$platform " : ''}$
brightness
$primaryColor
etc...)'
;
}
}
packages/flutter/lib/src/rendering/sliver.dart
View file @
63160b3d
...
@@ -97,6 +97,19 @@ enum AxisDirection {
...
@@ -97,6 +97,19 @@ enum AxisDirection {
left
,
left
,
}
}
Axis
axisDirectionToAxis
(
AxisDirection
axisDirection
)
{
assert
(
axisDirection
!=
null
);
switch
(
axisDirection
)
{
case
AxisDirection
.
up
:
case
AxisDirection
.
down
:
return
Axis
.
vertical
;
case
AxisDirection
.
left
:
case
AxisDirection
.
right
:
return
Axis
.
horizontal
;
}
return
null
;
}
AxisDirection
applyGrowthDirectionToAxisDirection
(
AxisDirection
axisDirection
,
GrowthDirection
growthDirection
)
{
AxisDirection
applyGrowthDirectionToAxisDirection
(
AxisDirection
axisDirection
,
GrowthDirection
growthDirection
)
{
assert
(
axisDirection
!=
null
);
assert
(
axisDirection
!=
null
);
assert
(
growthDirection
!=
null
);
assert
(
growthDirection
!=
null
);
...
@@ -241,18 +254,7 @@ class SliverConstraints extends Constraints {
...
@@ -241,18 +254,7 @@ class SliverConstraints extends Constraints {
return
null
;
return
null
;
}
}
Axis
get
axis
{
Axis
get
axis
=>
axisDirectionToAxis
(
axisDirection
);
assert
(
axisDirection
!=
null
);
switch
(
axisDirection
)
{
case
AxisDirection
.
up
:
case
AxisDirection
.
down
:
return
Axis
.
vertical
;
case
AxisDirection
.
left
:
case
AxisDirection
.
right
:
return
Axis
.
horizontal
;
}
return
null
;
}
/// Return what the [growthDirection] would be if the [axisDirection] was
/// Return what the [growthDirection] would be if the [axisDirection] was
/// either [AxisDirection.down] or [AxisDirection.right].
/// either [AxisDirection.down] or [AxisDirection.right].
...
@@ -352,7 +354,6 @@ class SliverConstraints extends Constraints {
...
@@ -352,7 +354,6 @@ class SliverConstraints extends Constraints {
@override
@override
int
get
hashCode
{
int
get
hashCode
{
assert
(
debugAssertIsValid
());
return
hashValues
(
axis
,
growthDirection
,
scrollOffset
,
overlap
,
remainingPaintExtent
,
crossAxisExtent
);
return
hashValues
(
axis
,
growthDirection
,
scrollOffset
,
overlap
,
remainingPaintExtent
,
crossAxisExtent
);
}
}
...
@@ -413,7 +414,7 @@ class SliverGeometry {
...
@@ -413,7 +414,7 @@ class SliverGeometry {
/// The (estimated) total paint extent that this sliver would be able to
/// The (estimated) total paint extent that this sliver would be able to
/// provide if the [SliverConstraints.remainingPaintExtent] was infinite.
/// provide if the [SliverConstraints.remainingPaintExtent] was infinite.
///
///
/// This is used
for shrink-wrapping (see [RenderViewport2.shrinkWrap])
.
/// This is used
by viewports that implement shrink-wrapping
.
///
///
/// By definition, this cannot be less than [paintExtent].
/// By definition, this cannot be less than [paintExtent].
final
double
maxPaintExtent
;
final
double
maxPaintExtent
;
...
@@ -1067,15 +1068,6 @@ abstract class ViewportOffset extends ChangeNotifier {
...
@@ -1067,15 +1068,6 @@ abstract class ViewportOffset extends ChangeNotifier {
/// typically be 0.0 and the maximum scroll extent will typically be 20.0,
/// typically be 0.0 and the maximum scroll extent will typically be 20.0,
/// because there's only 20.0 pixels of actual scroll slack.
/// because there's only 20.0 pixels of actual scroll slack.
///
///
/// The scroll extents also have any fixed scroll extents added to them. For
/// example, if there's 100.0 pixels of scrollable content, 20.0 pixels of
/// fixed content (e.g. a pinned heading), and the viewport is 80.0 pixels
/// high, then the minimum scroll extent will typically be 0.0 and the maximum
/// scroll extent will typically be 40.0. The fixed content essentially
/// shrinks the viewport to 60.0 pixels, which means there's 40.0 pixels of
/// content off screen when it is scroll to the top, and thus there's 40.0
/// pixels of content that can be scrolled into view.
///
/// If applying the content dimensions changes the scroll offset, return
/// If applying the content dimensions changes the scroll offset, return
/// false. Otherwise, return true. If you return false, the [RenderViewport2]
/// false. Otherwise, return true. If you return false, the [RenderViewport2]
/// will be laid out again with the new scroll offset. This is expensive.
/// will be laid out again with the new scroll offset. This is expensive.
...
@@ -1155,13 +1147,11 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1155,13 +1147,11 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
/// list, if any, is used.
/// list, if any, is used.
RenderViewport2
({
RenderViewport2
({
AxisDirection
axisDirection:
AxisDirection
.
down
,
AxisDirection
axisDirection:
AxisDirection
.
down
,
bool
shrinkWrap:
false
,
double
anchor:
0.0
,
double
anchor:
0.0
,
ViewportOffset
offset
,
ViewportOffset
offset
,
List
<
RenderSliver
>
children
,
List
<
RenderSliver
>
children
,
RenderSliver
center
,
RenderSliver
center
,
})
:
_axisDirection
=
axisDirection
,
})
:
_axisDirection
=
axisDirection
,
_shrinkWrap
=
shrinkWrap
,
_anchor
=
anchor
,
_anchor
=
anchor
,
_offset
=
offset
??
new
ViewportOffset
.
zero
(),
_offset
=
offset
??
new
ViewportOffset
.
zero
(),
_center
=
center
{
_center
=
center
{
...
@@ -1169,7 +1159,6 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1169,7 +1159,6 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
if
(
center
==
null
&&
firstChild
!=
null
)
if
(
center
==
null
&&
firstChild
!=
null
)
_center
=
firstChild
;
_center
=
firstChild
;
assert
(
axisDirection
!=
null
);
assert
(
axisDirection
!=
null
);
assert
(
shrinkWrap
!=
null
);
assert
(
anchor
!=
null
);
assert
(
anchor
!=
null
);
assert
(
anchor
>=
0.0
&&
anchor
<=
1.0
);
assert
(
anchor
>=
0.0
&&
anchor
<=
1.0
);
}
}
...
@@ -1184,28 +1173,10 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1184,28 +1173,10 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
markNeedsLayout
();
markNeedsLayout
();
}
}
Axis
get
axis
{
Axis
get
axis
=>
axisDirectionToAxis
(
axisDirection
);
assert
(
axisDirection
!=
null
);
switch
(
axisDirection
)
{
case
AxisDirection
.
up
:
case
AxisDirection
.
down
:
return
Axis
.
vertical
;
case
AxisDirection
.
left
:
case
AxisDirection
.
right
:
return
Axis
.
horizontal
;
}
return
null
;
}
bool
get
shrinkWrap
=>
_shrinkWrap
;
// TODO(ianh): Extract the shrink-wrap logic into a separate viewport class.
bool
_shrinkWrap
;
bool
_shrinkWrap
=
false
;
set
shrinkWrap
(
bool
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_shrinkWrap
)
return
;
_shrinkWrap
=
value
;
markNeedsLayout
();
}
double
get
anchor
=>
_anchor
;
double
get
anchor
=>
_anchor
;
double
_anchor
;
double
_anchor
;
...
@@ -1221,7 +1192,7 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1221,7 +1192,7 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
ViewportOffset
get
offset
=>
_offset
;
ViewportOffset
get
offset
=>
_offset
;
ViewportOffset
_offset
;
ViewportOffset
_offset
;
set
offset
(
ViewportOffset
value
)
{
set
offset
(
ViewportOffset
value
)
{
value
??=
new
ViewportOffset
.
zero
(
);
assert
(
value
!=
null
);
if
(
value
==
_offset
)
if
(
value
==
_offset
)
return
;
return
;
if
(
attached
)
if
(
attached
)
...
@@ -1231,6 +1202,31 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1231,6 +1202,31 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
_offset
=
value
;
_offset
=
value
;
if
(
attached
)
if
(
attached
)
_offset
.
addListener
(
markNeedsLayout
);
_offset
.
addListener
(
markNeedsLayout
);
if
(
hasSize
)
{
assert
(
_minScrollExtent
!=
null
);
assert
(
_maxScrollExtent
!=
null
);
assert
(
anchor
!=
null
);
// If we already have a size, then we should re-report the dimensions
// to the new ViewportOffset. If we don't then we'll report them when
// we establish the dimensions later, so don't worry about it now.
double
effectiveExtent
;
switch
(
axis
)
{
case
Axis
.
vertical
:
effectiveExtent
=
size
.
height
;
break
;
case
Axis
.
horizontal
:
effectiveExtent
=
size
.
width
;
break
;
}
assert
(
effectiveExtent
!=
null
);
offset
.
applyViewportDimension
(
effectiveExtent
);
if
(
offset
.
applyContentDimensions
(
// when updating this, also update similar code in performLayout()
math
.
min
(
0.0
,
_minScrollExtent
+
effectiveExtent
*
anchor
),
math
.
max
(
0.0
,
_maxScrollExtent
-
effectiveExtent
*
(
1.0
-
anchor
)),
))
markNeedsLayout
();
}
}
}
RenderSliver
get
center
=>
_center
;
RenderSliver
get
center
=>
_center
;
...
@@ -1256,19 +1252,19 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1256,19 +1252,19 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
@override
@override
void
detach
()
{
void
detach
()
{
super
.
detach
();
_offset
.
removeListener
(
markNeedsLayout
);
_offset
.
removeListener
(
markNeedsLayout
);
super
.
detach
();
}
}
@override
@override
bool
get
isRepaintBoundary
=>
true
;
bool
get
isRepaintBoundary
=>
true
;
@override
@override
bool
get
sizedByParent
=>
!
shrinkWrap
;
bool
get
sizedByParent
=>
!
_
shrinkWrap
;
@override
@override
void
performResize
()
{
void
performResize
()
{
assert
(!
shrinkWrap
);
assert
(!
_
shrinkWrap
);
assert
(
constraints
.
hasBoundedHeight
&&
constraints
.
hasBoundedWidth
);
assert
(
constraints
.
hasBoundedHeight
&&
constraints
.
hasBoundedWidth
);
size
=
constraints
.
biggest
;
size
=
constraints
.
biggest
;
switch
(
axis
)
{
switch
(
axis
)
{
...
@@ -1288,9 +1284,10 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1288,9 +1284,10 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
@override
@override
void
performLayout
()
{
void
performLayout
()
{
assert
(!
_shrinkWrap
||
anchor
==
0.0
);
if
(
center
==
null
)
{
if
(
center
==
null
)
{
assert
(
firstChild
==
null
);
assert
(
firstChild
==
null
);
if
(
shrinkWrap
)
{
if
(
_
shrinkWrap
)
{
switch
(
axis
)
{
switch
(
axis
)
{
case
Axis
.
vertical
:
case
Axis
.
vertical
:
size
=
new
Size
(
constraints
.
maxWidth
,
0.0
);
size
=
new
Size
(
constraints
.
maxWidth
,
0.0
);
...
@@ -1301,6 +1298,9 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1301,6 +1298,9 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
}
}
offset
.
applyViewportDimension
(
0.0
);
offset
.
applyViewportDimension
(
0.0
);
}
}
_minScrollExtent
=
0.0
;
_maxScrollExtent
=
0.0
;
_shrinkWrapExtent
=
0.0
;
offset
.
applyContentDimensions
(
0.0
,
0.0
);
offset
.
applyContentDimensions
(
0.0
,
0.0
);
return
;
return
;
}
}
...
@@ -1308,19 +1308,21 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1308,19 +1308,21 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
double
extent
;
double
extent
;
double
crossExtent
;
double
crossExtent
;
if
(
shrinkWrap
)
{
if
(
_shrinkWrap
)
{
assert
(
constraints
.
hasBoundedHeight
&&
constraints
.
hasBoundedWidth
);
switch
(
axis
)
{
switch
(
axis
)
{
case
Axis
.
vertical
:
case
Axis
.
vertical
:
assert
(
constraints
.
hasBoundedWidth
);
extent
=
constraints
.
maxHeight
;
extent
=
constraints
.
maxHeight
;
crossExtent
=
constraints
.
maxWidth
;
crossExtent
=
constraints
.
maxWidth
;
break
;
break
;
case
Axis
.
horizontal
:
case
Axis
.
horizontal
:
assert
(
constraints
.
hasBoundedHeight
);
extent
=
constraints
.
maxWidth
;
extent
=
constraints
.
maxWidth
;
crossExtent
=
constraints
.
maxHeight
;
crossExtent
=
constraints
.
maxHeight
;
break
;
break
;
}
}
}
else
{
}
else
{
assert
(
constraints
.
hasBoundedHeight
&&
constraints
.
hasBoundedWidth
);
switch
(
axis
)
{
switch
(
axis
)
{
case
Axis
.
vertical
:
case
Axis
.
vertical
:
extent
=
size
.
height
;
extent
=
size
.
height
;
...
@@ -1343,7 +1345,7 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1343,7 +1345,7 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
if
(
correction
!=
0.0
)
{
if
(
correction
!=
0.0
)
{
offset
.
correctBy
(
correction
);
offset
.
correctBy
(
correction
);
}
else
{
}
else
{
if
(
shrinkWrap
)
{
if
(
_
shrinkWrap
)
{
switch
(
axis
)
{
switch
(
axis
)
{
case
Axis
.
vertical
:
case
Axis
.
vertical
:
effectiveExtent
=
constraints
.
constrainHeight
(
_shrinkWrapExtent
);
effectiveExtent
=
constraints
.
constrainHeight
(
_shrinkWrapExtent
);
...
@@ -1363,16 +1365,17 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1363,16 +1365,17 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
break
;
break
;
}
}
}
}
// when updating this, also update similar code in offset setter
if
(
offset
.
applyContentDimensions
(
if
(
offset
.
applyContentDimensions
(
math
.
min
(
0.0
,
_minScrollExtent
+
effectiveExtent
*
anchor
),
math
.
min
(
0.0
,
_minScrollExtent
+
effectiveExtent
*
anchor
),
math
.
max
(
0.0
,
_maxScrollExtent
-
effectiveExtent
*
(
1.0
-
anchor
)
math
.
max
(
0.0
,
_maxScrollExtent
-
effectiveExtent
*
(
1.0
-
anchor
)
)
))
)
))
break
;
break
;
}
}
}
while
(
true
);
}
while
(
true
);
assert
(
shrinkWrap
!=
sizedByParent
);
assert
(
_
shrinkWrap
!=
sizedByParent
);
if
(
shrinkWrap
)
{
if
(
_
shrinkWrap
)
{
switch
(
axis
)
{
switch
(
axis
)
{
case
Axis
.
vertical
:
case
Axis
.
vertical
:
size
=
new
Size
(
crossExtent
,
effectiveExtent
);
size
=
new
Size
(
crossExtent
,
effectiveExtent
);
...
@@ -1385,6 +1388,11 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1385,6 +1388,11 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
}
}
double
_attemptLayout
(
double
extent
,
double
crossExtent
,
double
correctedOffset
)
{
double
_attemptLayout
(
double
extent
,
double
crossExtent
,
double
correctedOffset
)
{
assert
(!
extent
.
isNaN
);
assert
(
extent
>=
0.0
);
assert
(
crossExtent
.
isFinite
);
assert
(
crossExtent
>=
0.0
);
assert
(
correctedOffset
.
isFinite
);
_minScrollExtent
=
0.0
;
_minScrollExtent
=
0.0
;
_maxScrollExtent
=
0.0
;
_maxScrollExtent
=
0.0
;
_shrinkWrapExtent
=
0.0
;
_shrinkWrapExtent
=
0.0
;
...
@@ -1431,6 +1439,8 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1431,6 +1439,8 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
GrowthDirection
growthDirection
,
GrowthDirection
growthDirection
,
_Advancer
advance
,
_Advancer
advance
,
)
{
)
{
assert
(
scrollOffset
.
isFinite
);
assert
(
scrollOffset
>=
0.0
);
ScrollDirection
adjustedUserScrollDirection
;
ScrollDirection
adjustedUserScrollDirection
;
switch
(
growthDirection
)
{
switch
(
growthDirection
)
{
case
GrowthDirection
.
forward
:
case
GrowthDirection
.
forward
:
...
@@ -1454,6 +1464,7 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1454,6 +1464,7 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
double
maxPaintOffset
=
layoutOffset
;
double
maxPaintOffset
=
layoutOffset
;
double
initialLayoutOffset
=
layoutOffset
;
double
initialLayoutOffset
=
layoutOffset
;
while
(
child
!=
null
)
{
while
(
child
!=
null
)
{
assert
(
scrollOffset
>=
0.0
);
child
.
layout
(
new
SliverConstraints
(
child
.
layout
(
new
SliverConstraints
(
axisDirection:
axisDirection
,
axisDirection:
axisDirection
,
growthDirection:
growthDirection
,
growthDirection:
growthDirection
,
...
@@ -1654,7 +1665,7 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1654,7 +1665,7 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
void
debugFillDescription
(
List
<
String
>
description
)
{
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
super
.
debugFillDescription
(
description
);
description
.
add
(
'
$axisDirection
'
);
description
.
add
(
'
$axisDirection
'
);
if
(
shrinkWrap
)
if
(
_
shrinkWrap
)
description
.
add
(
'shrink-wrap enabled'
);
description
.
add
(
'shrink-wrap enabled'
);
description
.
add
(
'anchor:
$anchor
'
);
description
.
add
(
'anchor:
$anchor
'
);
description
.
add
(
'offset:
$offset
'
);
description
.
add
(
'offset:
$offset
'
);
...
@@ -1714,7 +1725,7 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
...
@@ -1714,7 +1725,7 @@ class RenderViewport2 extends RenderBox with ContainerRenderObjectMixin<RenderSl
class
RenderSliverToBoxAdapter
extends
RenderSliver
with
RenderObjectWithChildMixin
<
RenderBox
>,
RenderSliverHelpers
{
class
RenderSliverToBoxAdapter
extends
RenderSliver
with
RenderObjectWithChildMixin
<
RenderBox
>,
RenderSliverHelpers
{
/// Creates a [RenderSliver] that wraps a [RenderBox].
/// Creates a [RenderSliver] that wraps a [RenderBox].
RenderSliverToBoxAdapter
({
RenderSliverToBoxAdapter
({
RenderBox
child
RenderBox
child
,
})
{
})
{
this
.
child
=
child
;
this
.
child
=
child
;
}
}
...
...
packages/flutter/lib/src/widgets/basic.dart
View file @
63160b3d
...
@@ -2703,6 +2703,16 @@ class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget {
...
@@ -2703,6 +2703,16 @@ class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget {
}
}
}
}
class
SliverToBoxAdapter
extends
SingleChildRenderObjectWidget
{
SliverToBoxAdapter
({
Key
key
,
Widget
child
,
})
:
super
(
key:
key
,
child:
child
);
@override
RenderSliverToBoxAdapter
createRenderObject
(
BuildContext
context
)
=>
new
RenderSliverToBoxAdapter
();
}
// EVENT HANDLING
// EVENT HANDLING
...
...
packages/flutter/lib/src/widgets/framework.dart
View file @
63160b3d
...
@@ -12,9 +12,9 @@ import 'package:flutter/rendering.dart';
...
@@ -12,9 +12,9 @@ import 'package:flutter/rendering.dart';
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
export
'dart:ui'
show
hashValues
,
hashList
;
export
'dart:ui'
show
hashValues
,
hashList
;
export
'package:flutter/foundation.dart'
show
FlutterError
;
export
'package:flutter/foundation.dart'
show
FlutterError
,
debugPrint
,
debugPrintStack
;
export
'package:flutter/foundation.dart'
show
VoidCallback
,
ValueChanged
,
ValueGetter
,
ValueSetter
;
export
'package:flutter/foundation.dart'
show
VoidCallback
,
ValueChanged
,
ValueGetter
,
ValueSetter
;
export
'package:flutter/rendering.dart'
show
RenderObject
,
RenderBox
,
debug
Print
;
export
'package:flutter/rendering.dart'
show
RenderObject
,
RenderBox
,
debug
DumpRenderTree
;
// KEYS
// KEYS
...
@@ -3864,6 +3864,13 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
...
@@ -3864,6 +3864,13 @@ class MultiChildRenderObjectElement extends RenderObjectElement {
@override
@override
MultiChildRenderObjectWidget
get
widget
=>
super
.
widget
;
MultiChildRenderObjectWidget
get
widget
=>
super
.
widget
;
/// The current list of children of this element.
///
/// This list is filtered to hide elements that have been forgotten (using
/// [forgetChild]).
@protected
Iterable
<
Element
>
get
children
=>
_children
.
where
((
Element
child
)
=>
!
_forgottenChildren
.
contains
(
child
));
List
<
Element
>
_children
;
List
<
Element
>
_children
;
// We keep a set of forgotten children to avoid O(n^2) work walking _children
// We keep a set of forgotten children to avoid O(n^2) work walking _children
// repeatedly to remove children.
// repeatedly to remove children.
...
...
packages/flutter/lib/src/widgets/notification_listener.dart
View file @
63160b3d
...
@@ -122,7 +122,7 @@ class NotificationListener<T extends Notification> extends StatelessWidget {
...
@@ -122,7 +122,7 @@ class NotificationListener<T extends Notification> extends StatelessWidget {
/// Useful if, for instance, you're trying to align multiple descendants.
/// Useful if, for instance, you're trying to align multiple descendants.
///
///
/// In the widgets library, only the [SizeChangedLayoutNotifier] class and
/// In the widgets library, only the [SizeChangedLayoutNotifier] class and
/// [Scrollable] classes dispatch this notification (specifically, they dispatch
/// [Scrollable
2
] classes dispatch this notification (specifically, they dispatch
/// [SizeChangedLayoutNotification]s and [ScrollNotification]s respectively).
/// [SizeChangedLayoutNotification]s and [ScrollNotification]s respectively).
/// Transitions, in particular, do not. Changing one's layout in one's build
/// Transitions, in particular, do not. Changing one's layout in one's build
/// function does not cause this notification to be dispatched automatically. If
/// function does not cause this notification to be dispatched automatically. If
...
...
packages/flutter/lib/src/widgets/overscroll_indicator.dart
0 → 100644
View file @
63160b3d
// Copyright 2016 The Chromium 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:async'
show
Timer
;
import
'dart:math'
as
math
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/physics.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/widgets.dart'
;
class
GlowingOverscrollIndicator
extends
StatefulWidget
{
GlowingOverscrollIndicator
({
Key
key
,
this
.
showLeading
:
true
,
this
.
showTrailing
:
true
,
@required
this
.
axisDirection
,
@required
this
.
color
,
this
.
child
,
})
:
super
(
key:
key
)
{
assert
(
showLeading
!=
null
);
assert
(
showTrailing
!=
null
);
assert
(
axisDirection
!=
null
);
assert
(
color
!=
null
);
}
/// Whether to show the overscroll glow on the side with negative scroll
/// offsets.
///
/// For a vertical downwards viewport, this is the top side.
///
/// Defaults to true.
///
/// See [showTrailing] for the corresponding control on the other side of the
/// viewport.
final
bool
showLeading
;
/// Whether to show the overscroll glow on the side with positive scroll
/// offsets.
///
/// For a vertical downwards viewport, this is the bottom side.
///
/// Defaults to true.
///
/// See [showLeading] for the corresponding control on the other side of the
/// viewport.
final
bool
showTrailing
;
/// The direction of positive scroll offsets in the viewport of the
/// [Scrollable2] whose overscrolls are to be visualized.
final
AxisDirection
axisDirection
;
Axis
get
axis
=>
axisDirectionToAxis
(
axisDirection
);
/// The color of the glow. The alpha channel is ignored.
final
Color
color
;
/// The subtree to place inside the overscroll indicator. This should include
/// a source of [ScrollNotification2] notifications, typically a [Scrollable2]
/// widget.
///
/// Typically a [GlowingOverscrollIndicator] is created by a
/// [ScrollBehavior2.wrap] method, in which case the child is usually the one
/// provided as an argument to that method.
final
Widget
child
;
@override
_GlowingOverscrollIndicatorState
createState
()
=>
new
_GlowingOverscrollIndicatorState
();
@override
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
description
.
add
(
'
$axisDirection
'
);
if
(
showLeading
&&
showTrailing
)
{
description
.
add
(
'show: both sides'
);
}
else
if
(
showLeading
)
{
description
.
add
(
'show: leading side only'
);
}
else
if
(
showTrailing
)
{
description
.
add
(
'show: trailing side only'
);
}
else
{
description
.
add
(
'show: neither side (!)'
);
}
description
.
add
(
'
$color
'
);
}
}
class
_GlowingOverscrollIndicatorState
extends
State
<
GlowingOverscrollIndicator
>
with
TickerProviderStateMixin
{
_GlowController
_leadingController
;
_GlowController
_trailingController
;
@override
void
initState
()
{
super
.
initState
();
_leadingController
=
new
_GlowController
(
vsync:
this
,
color:
config
.
color
,
axis:
config
.
axis
);
_trailingController
=
new
_GlowController
(
vsync:
this
,
color:
config
.
color
,
axis:
config
.
axis
);
}
@override
void
didUpdateConfig
(
GlowingOverscrollIndicator
oldConfig
)
{
if
(
oldConfig
.
color
!=
config
.
color
||
oldConfig
.
axis
!=
config
.
axis
)
{
_leadingController
.
color
=
config
.
color
;
_leadingController
.
axis
=
config
.
axis
;
_trailingController
.
color
=
config
.
color
;
_trailingController
.
axis
=
config
.
axis
;
}
}
bool
_handleScrollNotification
(
ScrollNotification2
notification
)
{
if
(
notification
is
OverscrollNotification
)
{
_GlowController
controller
;
if
(
notification
.
overscroll
<
0.0
)
{
controller
=
_leadingController
;
}
else
if
(
notification
.
overscroll
>
0.0
)
{
controller
=
_trailingController
;
}
else
{
assert
(
false
);
}
assert
(
controller
!=
null
);
assert
(
notification
.
axis
==
config
.
axis
);
if
(
notification
.
velocity
!=
0.0
)
{
assert
(
notification
.
dragDetails
==
null
);
controller
.
absorbImpact
(
notification
.
velocity
.
abs
());
}
else
{
assert
(
notification
.
overscroll
!=
0.0
);
if
(
notification
.
dragDetails
!=
null
)
{
assert
(
notification
.
dragDetails
.
globalPosition
!=
null
);
final
RenderBox
renderer
=
notification
.
context
.
findRenderObject
();
assert
(
renderer
!=
null
);
assert
(
renderer
.
hasSize
);
final
Size
size
=
renderer
.
size
;
final
Point
position
=
renderer
.
globalToLocal
(
notification
.
dragDetails
.
globalPosition
);
switch
(
notification
.
axis
)
{
case
Axis
.
horizontal
:
controller
.
pull
(
notification
.
overscroll
.
abs
(),
size
.
width
,
position
.
y
.
clamp
(
0.0
,
size
.
height
),
size
.
height
);
break
;
case
Axis
.
vertical
:
controller
.
pull
(
notification
.
overscroll
.
abs
(),
size
.
height
,
position
.
x
.
clamp
(
0.0
,
size
.
width
),
size
.
width
);
break
;
}
}
}
}
else
if
(
notification
is
ScrollEndNotification
||
notification
is
ScrollUpdateNotification
)
{
if
(
notification
.
dragDetails
!=
null
)
{
// ignore: undefined_getter
_leadingController
.
scrollEnd
();
_trailingController
.
scrollEnd
();
}
}
return
false
;
}
@override
void
dispose
()
{
_leadingController
.
dispose
();
_trailingController
.
dispose
();
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
new
NotificationListener
<
ScrollNotification2
>(
onNotification:
_handleScrollNotification
,
child:
new
RepaintBoundary
(
child:
new
CustomPaint
(
foregroundPainter:
new
_GlowingOverscrollIndicatorPainter
(
leadingController:
config
.
showLeading
?
_leadingController
:
null
,
trailingController:
config
.
showTrailing
?
_trailingController
:
null
,
axisDirection:
config
.
axisDirection
,
),
child:
new
RepaintBoundary
(
child:
config
.
child
,
),
),
),
);
}
}
// The Glow logic is a port of the logic in the following file:
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/EdgeEffect.java
// as of December 2016.
enum
_GlowState
{
idle
,
absorb
,
pull
,
recede
}
class
_GlowController
extends
ChangeNotifier
{
_GlowController
({
TickerProvider
vsync
,
@required
Color
color
,
@required
Axis
axis
,
})
:
_color
=
color
,
_axis
=
axis
{
assert
(
vsync
!=
null
);
assert
(
color
!=
null
);
assert
(
axis
!=
null
);
_glowController
=
new
AnimationController
(
vsync:
vsync
)
..
addStatusListener
(
_changePhase
);
Animation
<
double
>
decelerator
=
new
CurvedAnimation
(
parent:
_glowController
,
curve:
Curves
.
decelerate
,
)..
addListener
(
notifyListeners
);
_glowOpacity
=
_glowOpacityTween
.
animate
(
decelerator
);
_glowSize
=
_glowSizeTween
.
animate
(
decelerator
);
_displacementTicker
=
vsync
.
createTicker
(
_tickDisplacement
);
}
// animation of the main axis direction
_GlowState
_state
=
_GlowState
.
idle
;
AnimationController
_glowController
;
Timer
_pullRecedeTimer
;
// animation values
final
Tween
<
double
>
_glowOpacityTween
=
new
Tween
<
double
>(
begin:
0.0
,
end:
0.0
);
Animation
<
double
>
_glowOpacity
;
final
Tween
<
double
>
_glowSizeTween
=
new
Tween
<
double
>(
begin:
0.0
,
end:
0.0
);
Animation
<
double
>
_glowSize
;
// animation of the cross axis position
Ticker
_displacementTicker
;
Duration
_displacementTickerLastElapsed
;
double
_displacementTarget
=
0.5
;
double
_displacement
=
0.5
;
// tracking the pull distance
double
_pullDistance
=
0.0
;
Color
get
color
=>
_color
;
Color
_color
;
set
color
(
Color
value
)
{
assert
(
color
!=
null
);
if
(
color
==
value
)
return
;
_color
=
value
;
notifyListeners
();
}
Axis
get
axis
=>
_axis
;
Axis
_axis
;
set
axis
(
Axis
value
)
{
assert
(
axis
!=
null
);
if
(
axis
==
value
)
return
;
_axis
=
value
;
notifyListeners
();
}
static
const
Duration
_recedeTime
=
const
Duration
(
milliseconds:
600
);
static
const
Duration
_pullTime
=
const
Duration
(
milliseconds:
167
);
static
const
Duration
_pullHoldTime
=
const
Duration
(
milliseconds:
167
);
static
const
Duration
_pullDecayTime
=
const
Duration
(
milliseconds:
2000
);
static
final
Duration
_crossAxisHalfTime
=
new
Duration
(
microseconds:
(
Duration
.
MICROSECONDS_PER_SECOND
/
60.0
).
round
());
static
const
double
_maxOpacity
=
0.5
;
static
const
double
_pullOpacityGlowFactor
=
0.8
;
static
const
double
_velocityGlowFactor
=
0.00006
;
static
const
double
_SQRT3
=
1.73205080757
;
// const math.sqrt(3)
static
const
double
_kWidthToHeightFactor
=
(
3.0
/
4.0
)
*
(
2.0
-
_SQRT3
);
// absorbed velocities are clamped to the range _minVelocity.._maxVelocity
static
const
double
_minVelocity
=
100.0
;
// logical pixels per second
static
const
double
_maxVelocity
=
10000.0
;
// logical pixels per second
@override
void
dispose
()
{
_glowController
.
dispose
();
_displacementTicker
.
dispose
();
_pullRecedeTimer
?.
cancel
();
super
.
dispose
();
}
/// Handle a scroll slamming into the edge at a particular velocity.
///
/// The velocity must be positive.
void
absorbImpact
(
double
velocity
)
{
assert
(
velocity
>=
0.0
);
_pullRecedeTimer
?.
cancel
();
_pullRecedeTimer
=
null
;
velocity
=
velocity
.
clamp
(
_minVelocity
,
_maxVelocity
);
_glowOpacityTween
.
begin
=
_state
==
_GlowState
.
idle
?
0.3
:
_glowOpacity
.
value
;
_glowOpacityTween
.
end
=
(
velocity
*
_velocityGlowFactor
).
clamp
(
_glowOpacityTween
.
begin
,
_maxOpacity
);
_glowSizeTween
.
begin
=
_glowSize
.
value
;
_glowSizeTween
.
end
=
math
.
min
(
0.025
+
7.5e-7
*
velocity
*
velocity
,
1.0
);
_glowController
.
duration
=
new
Duration
(
milliseconds:
(
0.15
+
velocity
*
0.02
).
round
());
_glowController
.
forward
(
from:
0.0
);
_displacement
=
0.5
;
_state
=
_GlowState
.
absorb
;
}
/// Handle a user-driven overscroll.
///
/// The `overscroll` argument should be the scroll distance in logical pixels,
/// the `extent` argument should be the total dimension of the viewport in the
/// main axis in logical pixels, the `crossAxisOffset` argument should be the
/// distance from the leading (left or top) edge of the cross axis of the
/// viewport, and the `crossExtent` should be the size of the cross axis. For
/// example, a pull of 50 pixels up the middle of a 200 pixel high and 100
/// pixel wide vertical viewport should result in a call of `pull(50.0, 200.0,
/// 50.0, 100.0)`. The `overscroll` value should be positive regardless of the
/// direction.
void
pull
(
double
overscroll
,
double
extent
,
double
crossAxisOffset
,
double
crossExtent
)
{
_pullRecedeTimer
?.
cancel
();
_pullDistance
+=
overscroll
/
200.0
;
// This factor is magic. Not clear why we need it to match Android.
_glowOpacityTween
.
begin
=
_glowOpacity
.
value
;
_glowOpacityTween
.
end
=
math
.
min
(
_glowOpacity
.
value
+
overscroll
/
extent
*
_pullOpacityGlowFactor
,
_maxOpacity
);
final
double
height
=
math
.
min
(
extent
,
crossExtent
*
_kWidthToHeightFactor
);
_glowSizeTween
.
begin
=
_glowSize
.
value
;
_glowSizeTween
.
end
=
math
.
max
((
1.0
-
1.0
/
(
0.7
*
math
.
sqrt
(
_pullDistance
*
height
))),
_glowSize
.
value
);
_displacementTarget
=
crossAxisOffset
/
crossExtent
;
if
(
_displacementTarget
!=
_displacement
)
{
if
(!
_displacementTicker
.
isTicking
)
{
assert
(
_displacementTickerLastElapsed
==
null
);
_displacementTicker
.
start
();
}
}
else
{
_displacementTicker
.
stop
();
_displacementTickerLastElapsed
=
null
;
}
_glowController
.
duration
=
_pullTime
;
if
(
_state
!=
_GlowState
.
pull
)
{
_glowController
.
forward
(
from:
0.0
);
_state
=
_GlowState
.
pull
;
}
else
{
if
(!
_glowController
.
isAnimating
)
{
assert
(
_glowController
.
value
==
1.0
);
notifyListeners
();
}
}
_pullRecedeTimer
=
new
Timer
(
_pullHoldTime
,
()
=>
_recede
(
_pullDecayTime
));
}
void
scrollEnd
()
{
if
(
_state
==
_GlowState
.
pull
)
_recede
(
_recedeTime
);
}
void
_changePhase
(
AnimationStatus
status
)
{
if
(
status
!=
AnimationStatus
.
completed
)
return
;
switch
(
_state
)
{
case
_GlowState
.
absorb
:
_recede
(
_recedeTime
);
break
;
case
_GlowState
.
recede
:
_state
=
_GlowState
.
idle
;
_pullDistance
=
0.0
;
break
;
case
_GlowState
.
pull
:
case
_GlowState
.
idle
:
break
;
}
}
void
_recede
(
Duration
duration
)
{
if
(
_state
==
_GlowState
.
recede
||
_state
==
_GlowState
.
idle
)
return
;
_pullRecedeTimer
?.
cancel
();
_pullRecedeTimer
=
null
;
_glowOpacityTween
.
begin
=
_glowOpacity
.
value
;
_glowOpacityTween
.
end
=
0.0
;
_glowSizeTween
.
begin
=
_glowSize
.
value
;
_glowSizeTween
.
end
=
0.0
;
_glowController
.
duration
=
duration
;
_glowController
.
forward
(
from:
0.0
);
_state
=
_GlowState
.
recede
;
}
void
_tickDisplacement
(
Duration
elapsed
)
{
if
(
_displacementTickerLastElapsed
!=
null
)
{
double
t
=
(
elapsed
.
inMicroseconds
-
_displacementTickerLastElapsed
.
inMicroseconds
).
toDouble
();
_displacement
=
_displacementTarget
-
(
_displacementTarget
-
_displacement
)
*
math
.
pow
(
2.0
,
-
t
/
_crossAxisHalfTime
.
inMicroseconds
);
notifyListeners
();
}
if
(
nearEqual
(
_displacementTarget
,
_displacement
,
Tolerance
.
defaultTolerance
.
distance
))
{
_displacementTicker
.
stop
();
_displacementTickerLastElapsed
=
null
;
}
else
{
_displacementTickerLastElapsed
=
elapsed
;
}
}
void
paint
(
Canvas
canvas
,
Size
size
)
{
if
(
_glowOpacity
.
value
==
0.0
)
return
;
final
double
baseGlowScale
=
size
.
width
>
size
.
height
?
size
.
height
/
size
.
width
:
1.0
;
final
double
radius
=
size
.
width
*
3.0
/
2.0
;
final
double
height
=
math
.
min
(
size
.
height
,
size
.
width
*
_kWidthToHeightFactor
);
final
double
scaleY
=
_glowSize
.
value
*
baseGlowScale
;
final
Rect
rect
=
new
Rect
.
fromLTWH
(
0.0
,
0.0
,
size
.
width
,
height
);
final
Point
center
=
new
Point
((
size
.
width
/
2.0
)
*
(
0.5
+
_displacement
),
height
-
radius
);
final
Paint
paint
=
new
Paint
()..
color
=
color
.
withOpacity
(
_glowOpacity
.
value
);
canvas
.
save
();
canvas
.
scale
(
1.0
,
scaleY
);
canvas
.
clipRect
(
rect
);
canvas
.
drawCircle
(
center
,
radius
,
paint
);
canvas
.
restore
();
}
}
class
_GlowingOverscrollIndicatorPainter
extends
CustomPainter
{
_GlowingOverscrollIndicatorPainter
({
this
.
leadingController
,
this
.
trailingController
,
this
.
axisDirection
,
})
:
super
(
repaint:
new
Listenable
.
merge
(<
Listenable
>[
leadingController
,
trailingController
])
);
/// The controller for the overscroll glow on the side with negative scroll offsets.
///
/// For a vertical downwards viewport, this is the top side.
final
_GlowController
leadingController
;
/// The controller for the overscroll glow on the side with positive scroll offsets.
///
/// For a vertical downwards viewport, this is the bottom side.
final
_GlowController
trailingController
;
/// The direction of the viewport.
final
AxisDirection
axisDirection
;
static
const
double
piOver2
=
math
.
PI
/
2.0
;
void
_paintSide
(
Canvas
canvas
,
Size
size
,
_GlowController
controller
,
AxisDirection
axisDirection
,
GrowthDirection
growthDirection
)
{
if
(
controller
==
null
)
return
;
switch
(
applyGrowthDirectionToAxisDirection
(
axisDirection
,
growthDirection
))
{
case
AxisDirection
.
up
:
controller
.
paint
(
canvas
,
size
);
break
;
case
AxisDirection
.
down
:
canvas
.
save
();
canvas
.
translate
(
0.0
,
size
.
height
);
canvas
.
scale
(
1.0
,
-
1.0
);
controller
.
paint
(
canvas
,
size
);
canvas
.
restore
();
break
;
case
AxisDirection
.
left
:
canvas
.
save
();
canvas
.
translate
(
0.0
,
size
.
height
);
canvas
.
rotate
(-
piOver2
);
controller
.
paint
(
canvas
,
new
Size
(
size
.
height
,
size
.
width
));
canvas
.
restore
();
break
;
case
AxisDirection
.
right
:
canvas
.
save
();
canvas
.
translate
(
size
.
width
,
size
.
height
);
canvas
.
rotate
(-
piOver2
);
canvas
.
scale
(
1.0
,
-
1.0
);
controller
.
paint
(
canvas
,
new
Size
(
size
.
height
,
size
.
width
));
canvas
.
restore
();
break
;
}
}
@override
void
paint
(
Canvas
canvas
,
Size
size
)
{
_paintSide
(
canvas
,
size
,
leadingController
,
axisDirection
,
GrowthDirection
.
reverse
);
_paintSide
(
canvas
,
size
,
trailingController
,
axisDirection
,
GrowthDirection
.
forward
);
}
@override
bool
shouldRepaint
(
_GlowingOverscrollIndicatorPainter
oldDelegate
)
{
return
oldDelegate
.
leadingController
!=
leadingController
||
oldDelegate
.
trailingController
!=
trailingController
;
}
}
packages/flutter/lib/src/widgets/scroll_absolute.dart
0 → 100644
View file @
63160b3d
// Copyright 2015 The Chromium 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:async'
;
import
'dart:math'
as
math
;
import
'dart:ui'
as
ui
show
window
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/physics.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'basic.dart'
;
import
'framework.dart'
;
import
'gesture_detector.dart'
;
import
'overscroll_indicator.dart'
;
import
'scroll_simulation.dart'
;
import
'notification_listener.dart'
;
import
'scroll_notification.dart'
;
import
'scrollable.dart'
;
/// Scrolling logic delegate for lists and other unremarkable scrollable
/// viewports.
///
/// See also:
///
/// * [BouncingAbsoluteScrollPositionMixIn], which is used by this class to
/// implement the scroll behavior for iOS.
/// * [ClampingAbsoluteScrollPositionMixIn] and [GlowingOverscrollIndicator],
/// which are used by this class to implement the scroll behavior for Android.
class
ViewportScrollBehavior
extends
ScrollBehavior2
{
ViewportScrollBehavior
({
Tolerance
scrollTolerances
,
})
:
scrollTolerances
=
scrollTolerances
??
defaultScrollTolerances
;
/// The accuracy to which scrolling is computed.
///
/// Defaults to [defaultScrollTolerances].
final
Tolerance
scrollTolerances
;
/// The accuracy to which scrolling is computed by default.
///
/// This is the default value for [scrollTolerances].
static
final
Tolerance
defaultScrollTolerances
=
new
Tolerance
(
// TODO(ianh): Handle the case of the device pixel ratio changing.
// TODO(ianh): Get this from the local MediaQuery not dart:ui's window object.
velocity:
1.0
/
(
0.050
*
ui
.
window
.
devicePixelRatio
),
// logical pixels per second
distance:
1.0
/
ui
.
window
.
devicePixelRatio
// logical pixels
);
/// The platform whose scroll physics should be implemented.
///
/// Defaults to the current platform.
TargetPlatform
getPlatform
(
BuildContext
context
)
=>
defaultTargetPlatform
;
/// The color to use for the glow effect when [platform] indicates a platform
/// that uses a [GlowingOverscrollIndicator].
///
/// Defaults to white.
Color
getGlowColor
(
BuildContext
context
)
=>
const
Color
(
0xFFFFFFFF
);
@override
Widget
wrap
(
BuildContext
context
,
Widget
child
,
AxisDirection
axisDirection
)
{
switch
(
getPlatform
(
context
))
{
case
TargetPlatform
.
iOS
:
return
child
;
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
return
new
GlowingOverscrollIndicator
(
child:
child
,
axisDirection:
axisDirection
,
color:
getGlowColor
(
context
),
);
}
return
null
;
}
@override
ScrollPosition
createScrollPosition
(
BuildContext
context
,
Scrollable2State
state
,
ScrollPosition
oldPosition
)
{
switch
(
getPlatform
(
context
))
{
case
TargetPlatform
.
iOS
:
return
new
_CupertinoViewportScrollPosition
(
state
,
scrollTolerances
,
oldPosition
,
);
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
return
new
_MountainViewViewportScrollPosition
(
state
,
scrollTolerances
,
oldPosition
,
);
}
return
null
;
}
@override
bool
shouldNotify
(
ViewportScrollBehavior
oldDelegate
)
{
return
scrollTolerances
!=
oldDelegate
.
scrollTolerances
;
}
}
abstract
class
AbsoluteScrollPosition
extends
ScrollPosition
{
AbsoluteScrollPosition
(
Scrollable2State
state
,
Tolerance
scrollTolerances
,
ScrollPosition
oldPosition
,
)
:
super
(
state
,
scrollTolerances
,
oldPosition
);
@override
double
get
pixels
=>
_pixels
;
double
_pixels
=
0.0
;
@override
double
setPixels
(
double
value
)
{
assert
(
SchedulerBinding
.
instance
.
schedulerPhase
.
index
<=
SchedulerPhase
.
transientCallbacks
.
index
);
assert
(
activity
.
isScrolling
);
if
(
value
!=
pixels
)
{
final
double
overScroll
=
computeOverscroll
(
value
);
assert
(()
{
double
delta
=
value
-
pixels
;
if
(
overScroll
.
abs
()
>
delta
.
abs
())
{
throw
new
FlutterError
(
'
$runtimeType
.computeOverscroll returned invalid overscroll value.
\n
'
'setPixels() was called to change the scroll offset from
$pixels
to
$value
.
\n
'
'That is a delta of
$delta
units.
\n
'
'computeOverscroll() reported an overscroll of
$overScroll
units.
\n
'
'The scroll extents are
$minScrollExtent
..
$maxScrollExtent
, and the '
'viewport dimension is
$viewportDimension
.'
);
}
return
true
;
});
double
oldPixels
=
_pixels
;
_pixels
=
value
-
overScroll
;
if
(
_pixels
!=
oldPixels
)
{
notifyListeners
();
dispatchNotification
(
activity
.
createScrollUpdateNotification
(
state
,
_pixels
-
oldPixels
));
}
if
(
overScroll
!=
0.0
)
{
reportOverscroll
(
overScroll
);
return
overScroll
;
}
}
return
0.0
;
}
/// Called by [setPixels] just before the [pixels] value is updated, to
/// determine how much of the offset is to be clamped off and sent to
/// [reportOverscroll].
///
/// The `value` argument is guaranteed to not equal [pixels] when this is
/// called.
@protected
double
computeOverscroll
(
double
value
)
=>
0.0
;
@protected
void
reportOverscroll
(
double
value
)
{
assert
(
activity
.
isScrolling
);
dispatchNotification
(
activity
.
createOverscrollNotification
(
state
,
value
));
}
double
get
viewportDimension
=>
_viewportDimension
;
double
_viewportDimension
;
double
get
minScrollExtent
=>
_minScrollExtent
;
double
_minScrollExtent
;
double
get
maxScrollExtent
=>
_maxScrollExtent
;
double
_maxScrollExtent
;
bool
get
outOfRange
=>
pixels
<
minScrollExtent
||
pixels
>
maxScrollExtent
;
bool
get
atEdge
=>
pixels
==
minScrollExtent
||
pixels
==
maxScrollExtent
;
bool
_didChangeViewportDimension
=
true
;
@override
void
applyViewportDimension
(
double
viewportDimension
)
{
if
(
_viewportDimension
!=
viewportDimension
)
{
_viewportDimension
=
viewportDimension
;
_didChangeViewportDimension
=
true
;
// If this is called, you can rely on applyContentDimensions being called
// soon afterwards in the same layout phase. So we put all the logic that
// relies on both values being computed into applyContentDimensions.
}
super
.
applyViewportDimension
(
viewportDimension
);
}
@override
bool
applyContentDimensions
(
double
minScrollExtent
,
double
maxScrollExtent
)
{
if
(
_minScrollExtent
!=
minScrollExtent
||
_maxScrollExtent
!=
maxScrollExtent
||
_didChangeViewportDimension
)
{
_minScrollExtent
=
minScrollExtent
;
_maxScrollExtent
=
maxScrollExtent
;
activity
.
applyNewDimensions
();
_didChangeViewportDimension
=
false
;
}
return
super
.
applyContentDimensions
(
minScrollExtent
,
maxScrollExtent
);
}
@override
ScrollableMetrics
getMetrics
()
{
return
new
ScrollableMetrics
(
extentBefore:
math
.
max
(
pixels
-
minScrollExtent
,
0.0
),
extentInside:
math
.
min
(
pixels
,
maxScrollExtent
)
-
math
.
max
(
pixels
,
minScrollExtent
)
+
math
.
min
(
viewportDimension
,
maxScrollExtent
-
minScrollExtent
),
extentAfter:
math
.
max
(
maxScrollExtent
-
pixels
,
0.0
),
);
}
@override
bool
get
canDrag
=>
true
;
@override
bool
get
shouldIgnorePointer
=>
activity
?.
shouldIgnorePointer
;
@override
void
correctBy
(
double
correction
)
{
_pixels
+=
correction
;
}
@override
void
absorb
(
ScrollPosition
other
)
{
if
(
other
is
AbsoluteScrollPosition
)
{
final
AbsoluteScrollPosition
typedOther
=
other
;
_pixels
=
typedOther
.
_pixels
;
_viewportDimension
=
typedOther
.
viewportDimension
;
_minScrollExtent
=
typedOther
.
minScrollExtent
;
_maxScrollExtent
=
typedOther
.
maxScrollExtent
;
}
super
.
absorb
(
other
);
}
@override
DragScrollActivity
beginDragActivity
(
DragStartDetails
details
)
{
beginActivity
(
new
AbsoluteDragScrollActivity
(
this
,
details
,
scrollTolerances
));
return
activity
;
}
@override
void
beginBallisticActivity
(
double
velocity
)
{
final
Simulation
simulation
=
createBallisticSimulation
(
velocity
);
if
(
simulation
!=
null
)
{
simulation
.
tolerance
=
scrollTolerances
;
beginActivity
(
new
AbsoluteBallisticScrollActivity
(
this
,
simulation
,
vsync
));
}
else
{
beginIdleActivity
();
}
}
/// Animates the position from its current value to the given value `to`.
///
/// Any active animation is canceled. If the user is currently scrolling, that
/// action is canceled.
///
/// The returned [Future] will complete when the animation ends, whether it
/// completed successfully or whether it was interrupted prematurely.
///
/// An animation will be interrupted whenever the user attempts to scroll
/// manually, or whenever another activity is started, or whenever the
/// animation reaches the edge of the viewport and attempts to overscroll. (If
/// the [ScrollPosition] does not overscroll but instead allows scrolling
/// beyond the extents, then going beyond the extents will not interrupt the
/// animation.)
///
/// The animation is indifferent to changes to the viewport or content
/// dimensions.
///
/// Once the animation has completed, the scroll position will attempt to
/// begin a ballistic activity in case its value is not stable (for example,
/// if it is scrolled beyond the extents and in that situation the scroll
/// position would normally bounce back).
///
/// The duration must not be zero. To jump to a particular value without an
/// animation, use [setPixels].
///
/// The animation is handled by an [AbsoluteDrivenScrollActivity].
Future
<
Null
>
animate
({
@required
double
to
,
@required
Duration
duration
,
@required
Curve
curve
,
})
{
final
AbsoluteDrivenScrollActivity
activity
=
new
AbsoluteDrivenScrollActivity
(
this
,
from:
pixels
,
to:
to
,
duration:
duration
,
curve:
curve
,
vsync:
vsync
,
);
beginActivity
(
activity
);
return
activity
.
done
;
}
/// Jumps the scroll position from its current value to the given value,
/// without animation, and without checking if the new value is in range.
///
/// Any active animation is canceled. If the user is currently scrolling, that
/// action is canceled.
///
/// If this method changes the scroll position, a sequence of start/update/end
/// scroll notifications will be dispatched. No overscroll notifications can
/// be generated by this method.
///
/// Immediately after the jump, a ballistic activity is started, in case the
/// value was out of range.
void
jumpTo
(
double
value
)
{
beginIdleActivity
();
if
(
_pixels
!=
value
)
{
final
double
oldPixels
=
_pixels
;
_pixels
=
value
;
notifyListeners
();
dispatchNotification
(
activity
.
createScrollStartNotification
(
state
));
dispatchNotification
(
activity
.
createScrollUpdateNotification
(
state
,
_pixels
-
oldPixels
));
dispatchNotification
(
activity
.
createScrollEndNotification
(
state
));
}
beginBallisticActivity
(
0.0
);
}
@protected
Simulation
createBallisticSimulation
(
double
velocity
);
@override
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
description
.
add
(
'range:
${minScrollExtent?.toStringAsFixed(1)}
..
${maxScrollExtent?.toStringAsFixed(1)}
'
);
description
.
add
(
'viewport:
${viewportDimension?.toStringAsFixed(1)}
'
);
}
}
/// Scroll position mixin for environments that allow the scroll offset to go
/// beyond the bounds of the content, but then bounce the content back to the
/// edge of those bounds.
///
/// This is the behavior typically seen on iOS.
///
/// Mixing this class into an [AbsoluteScrollPosition] subclass, and then
/// overriding [computeOverscroll] to provide some overscroll at one edge, will
/// allow that edge to clamp while the other edge still has the bounce effect.
///
/// See also:
///
/// * [ViewportScrollBehavior], which uses this to provide the iOS component of
/// its scroll behavior.
/// * [ClampingAbsoluteScrollPositionMixIn], which is the equivalent mixin for
/// Android's clamping behavior.
abstract
class
BouncingAbsoluteScrollPositionMixIn
implements
AbsoluteScrollPosition
{
/// The multiple applied to overscroll to make it appear that scrolling past
/// the edge of the scrollable contents is harder than scrolling the list.
///
/// By default this is 0.5, meaning that overscroll is twice as hard as normal
/// scroll.
double
get
frictionFactor
=>
0.5
;
@override
double
applyPhysicsToUserOffset
(
double
offset
)
{
assert
(
offset
!=
0.0
);
assert
(
minScrollExtent
<=
maxScrollExtent
);
if
(
offset
>
0.0
)
return
_applyFriction
(
pixels
,
minScrollExtent
,
maxScrollExtent
,
offset
,
frictionFactor
);
return
-
_applyFriction
(-
pixels
,
-
maxScrollExtent
,
-
minScrollExtent
,
-
offset
,
frictionFactor
);
}
static
double
_applyFriction
(
double
start
,
double
lowLimit
,
double
highLimit
,
double
delta
,
double
gamma
)
{
assert
(
lowLimit
<=
highLimit
);
assert
(
delta
>
0.0
);
double
total
=
0.0
;
if
(
start
<
lowLimit
)
{
double
distanceToLimit
=
lowLimit
-
start
;
double
deltaToLimit
=
distanceToLimit
/
gamma
;
if
(
delta
<
deltaToLimit
)
return
total
+
delta
*
gamma
;
total
+=
distanceToLimit
;
delta
-=
deltaToLimit
;
}
return
total
+
delta
;
}
@override
Simulation
createBallisticSimulation
(
double
velocity
)
{
if
(
velocity
.
abs
()
>=
scrollTolerances
.
velocity
||
outOfRange
)
{
return
new
BouncingScrollSimulation
(
position:
pixels
,
velocity:
velocity
,
leadingExtent:
minScrollExtent
,
trailingExtent:
maxScrollExtent
,
);
}
return
null
;
}
}
class
_CupertinoViewportScrollPosition
extends
AbsoluteScrollPosition
with
BouncingAbsoluteScrollPositionMixIn
{
_CupertinoViewportScrollPosition
(
Scrollable2State
state
,
Tolerance
scrollTolerances
,
ScrollPosition
oldPosition
,
)
:
super
(
state
,
scrollTolerances
,
oldPosition
);
}
/// Scroll position mixin for environments that prevent the scroll offset from
/// reaching beyond the bounds of the content.
///
/// This is the behavior typically seen on Android.
///
/// See also:
///
/// * [ViewportScrollBehavior], which uses this to provide the Android component
/// of its scroll behavior.
/// * [BouncingAbsoluteScrollPositionMixIn], which is the equivalent mixin for
/// iOS' bouncing behavior.
/// * [GlowingOverscrollIndicator], which is used by [ViewportScrollBehavior] to
/// provide the glowing effect that is usually found with this clamping effect
/// on Android.
abstract
class
ClampingAbsoluteScrollPositionMixIn
implements
AbsoluteScrollPosition
{
@override
double
computeOverscroll
(
double
value
)
{
assert
(
value
!=
pixels
);
if
(
value
<
pixels
&&
pixels
<=
minScrollExtent
)
// underscroll
return
value
-
pixels
;
if
(
maxScrollExtent
<=
pixels
&&
pixels
<
value
)
// overscroll
return
value
-
pixels
;
if
(
value
<
minScrollExtent
&&
minScrollExtent
<
pixels
)
// hit top edge
return
value
-
minScrollExtent
;
if
(
pixels
<
maxScrollExtent
&&
maxScrollExtent
<
value
)
// hit bottom edge
return
value
-
maxScrollExtent
;
return
0.0
;
}
static
final
SpringDescription
_defaultScrollSpring
=
new
SpringDescription
.
withDampingRatio
(
mass:
0.5
,
springConstant:
100.0
,
ratio:
1.1
,
);
@override
Simulation
createBallisticSimulation
(
double
velocity
)
{
if
(
outOfRange
)
{
if
(
pixels
>
maxScrollExtent
)
return
new
ScrollSpringSimulation
(
_defaultScrollSpring
,
pixels
,
maxScrollExtent
,
velocity
);
if
(
pixels
<
minScrollExtent
)
return
new
ScrollSpringSimulation
(
_defaultScrollSpring
,
pixels
,
minScrollExtent
,
velocity
);
assert
(
false
);
}
if
(!
atEdge
&&
velocity
.
abs
()
>=
scrollTolerances
.
velocity
)
{
return
new
ClampingScrollSimulation
(
position:
pixels
,
velocity:
velocity
,
);
}
return
null
;
}
}
class
_MountainViewViewportScrollPosition
extends
AbsoluteScrollPosition
with
ClampingAbsoluteScrollPositionMixIn
{
_MountainViewViewportScrollPosition
(
Scrollable2State
state
,
Tolerance
scrollTolerances
,
ScrollPosition
oldPosition
,
)
:
super
(
state
,
scrollTolerances
,
oldPosition
);
}
class
AbsoluteDragScrollActivity
extends
DragScrollActivity
{
AbsoluteDragScrollActivity
(
ScrollPosition
position
,
DragStartDetails
details
,
this
.
scrollTolerances
,
)
:
_lastDetails
=
details
,
super
(
position
);
final
Tolerance
scrollTolerances
;
@override
void
update
(
DragUpdateDetails
details
,
{
bool
reverse
})
{
assert
(
details
.
primaryDelta
!=
null
);
_lastDetails
=
details
;
double
offset
=
details
.
primaryDelta
;
if
(
offset
==
0.0
)
return
;
if
(
reverse
)
// e.g. an AxisDirection.up scrollable
offset
=
-
offset
;
position
.
updateUserScrollDirection
(
offset
>
0.0
?
ScrollDirection
.
forward
:
ScrollDirection
.
reverse
);
position
.
setPixels
(
position
.
pixels
-
position
.
applyPhysicsToUserOffset
(
offset
));
// We ignore any reported overscroll returned by setPixels,
// because it gets reported via the reportOverscroll path.
}
@override
void
end
(
DragEndDetails
details
,
{
bool
reverse
})
{
assert
(
details
.
primaryVelocity
!=
null
);
double
velocity
=
details
.
primaryVelocity
;
if
(
reverse
)
// e.g. an AxisDirection.up scrollable
velocity
=
-
velocity
;
_lastDetails
=
details
;
// We negate the velocity here because if the touch is moving downwards,
// the scroll has to move upwards. It's the same reason that update()
// above negates the delta before applying it to the scroll offset.
position
.
beginBallisticActivity
(-
velocity
);
}
@override
void
dispose
()
{
_lastDetails
=
null
;
super
.
dispose
();
}
dynamic
_lastDetails
;
@override
Notification
createScrollStartNotification
(
Scrollable2State
scrollable
)
{
assert
(
_lastDetails
is
DragStartDetails
);
return
new
ScrollStartNotification
(
scrollable:
scrollable
,
dragDetails:
_lastDetails
);
}
@override
Notification
createScrollUpdateNotification
(
Scrollable2State
scrollable
,
double
scrollDelta
)
{
assert
(
_lastDetails
is
DragUpdateDetails
);
return
new
ScrollUpdateNotification
(
scrollable:
scrollable
,
scrollDelta:
scrollDelta
,
dragDetails:
_lastDetails
);
}
@override
Notification
createOverscrollNotification
(
Scrollable2State
scrollable
,
double
overscroll
)
{
assert
(
_lastDetails
is
DragUpdateDetails
);
return
new
OverscrollNotification
(
scrollable:
scrollable
,
overscroll:
overscroll
,
dragDetails:
_lastDetails
);
}
@override
Notification
createScrollEndNotification
(
Scrollable2State
scrollable
)
{
assert
(
_lastDetails
is
DragEndDetails
);
return
new
ScrollEndNotification
(
scrollable:
scrollable
,
dragDetails:
_lastDetails
);
}
@override
bool
get
shouldIgnorePointer
=>
true
;
@override
bool
get
isScrolling
=>
true
;
}
class
AbsoluteBallisticScrollActivity
extends
ScrollActivity
{
///
/// The velocity should be in logical pixels per second.
AbsoluteBallisticScrollActivity
(
AbsoluteScrollPosition
position
,
Simulation
simulation
,
TickerProvider
vsync
,
)
:
super
(
position
)
{
_controller
=
new
AnimationController
.
unbounded
(
value:
position
.
pixels
,
debugLabel:
'
$runtimeType
'
,
vsync:
vsync
,
)
..
addListener
(
_tick
)
..
animateWith
(
simulation
)
.
then
(
_end
);
}
@override
AbsoluteScrollPosition
get
position
=>
super
.
position
;
double
get
velocity
=>
_controller
.
velocity
;
AnimationController
_controller
;
@override
void
resetActivity
()
{
position
.
beginBallisticActivity
(
velocity
);
}
@override
void
touched
()
{
position
.
beginIdleActivity
();
}
@override
void
applyNewDimensions
()
{
position
.
beginBallisticActivity
(
velocity
);
}
void
_tick
()
{
if
(
position
.
setPixels
(
_controller
.
value
)
!=
0.0
)
position
.
beginIdleActivity
();
}
void
_end
(
Null
value
)
{
position
.
beginIdleActivity
();
}
@override
Notification
createOverscrollNotification
(
Scrollable2State
scrollable
,
double
overscroll
)
{
return
new
OverscrollNotification
(
scrollable:
scrollable
,
overscroll:
overscroll
,
velocity:
velocity
);
}
@override
bool
get
shouldIgnorePointer
=>
true
;
@override
bool
get
isScrolling
=>
true
;
@override
void
dispose
()
{
_controller
.
dispose
();
super
.
dispose
();
}
@override
String
toString
()
{
return
'
$runtimeType
(
$_controller
)'
;
}
}
class
AbsoluteDrivenScrollActivity
extends
ScrollActivity
{
AbsoluteDrivenScrollActivity
(
ScrollPosition
position
,
{
@required
double
from
,
@required
double
to
,
@required
Duration
duration
,
@required
Curve
curve
,
@required
TickerProvider
vsync
,
})
:
super
(
position
)
{
assert
(
from
!=
null
);
assert
(
to
!=
null
);
assert
(
duration
!=
null
);
assert
(
duration
>
Duration
.
ZERO
);
assert
(
curve
!=
null
);
_completer
=
new
Completer
<
Null
>();
_controller
=
new
AnimationController
.
unbounded
(
value:
from
,
debugLabel:
'
$runtimeType
'
,
vsync:
vsync
,
)
..
addListener
(
_tick
)
..
animateTo
(
to
,
duration:
duration
,
curve:
curve
)
.
then
(
_end
);
}
@override
AbsoluteScrollPosition
get
position
=>
super
.
position
;
Completer
<
Null
>
_completer
;
AnimationController
_controller
;
Future
<
Null
>
get
done
=>
_completer
.
future
;
double
get
velocity
=>
_controller
.
velocity
;
@override
void
touched
()
{
position
.
beginIdleActivity
();
}
void
_tick
()
{
if
(
position
.
setPixels
(
_controller
.
value
)
!=
0.0
)
position
.
beginIdleActivity
();
}
void
_end
(
Null
value
)
{
position
.
beginBallisticActivity
(
velocity
);
}
@override
Notification
createOverscrollNotification
(
Scrollable2State
scrollable
,
double
overscroll
)
{
return
new
OverscrollNotification
(
scrollable:
scrollable
,
overscroll:
overscroll
,
velocity:
velocity
);
}
@override
bool
get
shouldIgnorePointer
=>
true
;
@override
bool
get
isScrolling
=>
true
;
@override
void
dispose
()
{
_completer
.
complete
();
_controller
.
dispose
();
super
.
dispose
();
}
@override
String
toString
()
{
return
'
$runtimeType
(
$_controller
)'
;
}
}
packages/flutter/lib/src/widgets/scroll_behavior.dart
View file @
63160b3d
...
@@ -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.
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
import
'dart:math'
as
math
;
import
'dart:math'
as
math
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
...
...
packages/flutter/lib/src/widgets/scroll_configuration.dart
View file @
63160b3d
...
@@ -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.
// DELETE THIS FILE WHEN REMOVING LEGACY SCROLLING CODE
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'framework.dart'
;
import
'framework.dart'
;
...
...
packages/flutter/lib/src/widgets/scroll_notification.dart
0 → 100644
View file @
63160b3d
// Copyright 2016 The Chromium 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
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
import
'framework.dart'
;
import
'basic.dart'
;
import
'notification_listener.dart'
;
import
'scrollable.dart'
show
Scrollable2
,
Scrollable2State
;
/// A description of a [Scrollable2]'s contents, useful for modelling the state
/// of the viewport, for example by a [Scrollbar].
///
/// The units used by the [extentBefore], [extentInside], and [extentAfter] are
/// not defined, but must be consistent. For example, they could be in pixels,
/// or in percentages, or in units of the [extentInside] (in the latter case,
/// [extentInside] would always be 1.0).
class
ScrollableMetrics
{
/// Create a description of the metrics of a [Scrollable2]'s contents.
///
/// The three arguments must be present, non-null, finite, and non-negative.
const
ScrollableMetrics
({
@required
this
.
extentBefore
,
@required
this
.
extentInside
,
@required
this
.
extentAfter
,
});
/// The quantity of content conceptually "above" the currently visible content
/// of the viewport in the scrollable. This is the content above the content
/// described by [extentInside].
///
/// The units are in general arbitrary, and decided by the [ScrollPosition]
/// that generated the [ScrollableMetrics]. They will be the same units as for
/// [extentInside] and [extentAfter].
final
double
extentBefore
;
/// The quantity of visible content. If [extentBefore] and [extentAfter] are
/// non-zero, then this is typically the height of the viewport. It could be
/// less if there is less content visible than the size of the viewport.
///
/// The units are in general arbitrary, and decided by the [ScrollPosition]
/// that generated the [ScrollableMetrics]. They will be the same units as for
/// [extentBefore] and [extentAfter].
final
double
extentInside
;
/// The quantity of content conceptually "below" the currently visible content
/// of the viewport in the scrollable. This is the content below the content
/// described by [extentInside].
///
/// The units are in general arbitrary, and decided by the [ScrollPosition]
/// that generated the [ScrollableMetrics]. They will be the same units as for
/// [extentBefore] and [extentInside].
final
double
extentAfter
;
@override
String
toString
()
{
return
'
$runtimeType
(
${extentBefore.toStringAsFixed(1)}
..[
${extentInside.toStringAsFixed(1)}
]..
${extentAfter.toStringAsFixed(1)}
})'
;
}
}
abstract
class
ScrollNotification2
extends
LayoutChangedNotification
{
/// Creates a notification about scrolling.
ScrollNotification2
({
@required
Scrollable2State
scrollable
,
})
:
axisDirection
=
scrollable
.
config
.
axisDirection
,
metrics
=
scrollable
.
position
.
getMetrics
(),
context
=
scrollable
.
context
;
/// The direction that positive scroll offsets indicate.
final
AxisDirection
axisDirection
;
Axis
get
axis
=>
axisDirectionToAxis
(
axisDirection
);
final
ScrollableMetrics
metrics
;
/// The build context of the [Scrollable2] that fired this notification.
///
/// This can be used to find the scrollable's render objects to determine the
/// size of the viewport, for instance.
// TODO(ianh): Maybe just fold those into the ScrollableMetrics?
final
BuildContext
context
;
/// The number of [Scrollable2] widgets that this notification has bubbled
/// through. Typically listeners only respond to notifications with a [depth]
/// of zero.
int
get
depth
=>
_depth
;
int
_depth
=
0
;
@override
bool
visitAncestor
(
Element
element
)
{
if
(
element
.
widget
is
Scrollable2
)
_depth
+=
1
;
return
super
.
visitAncestor
(
element
);
}
@override
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
description
.
add
(
'
$axisDirection
'
);
description
.
add
(
'metrics:
$metrics
'
);
description
.
add
(
'depth:
$depth
'
);
}
}
class
ScrollStartNotification
extends
ScrollNotification2
{
ScrollStartNotification
({
@required
Scrollable2State
scrollable
,
this
.
dragDetails
,
})
:
super
(
scrollable:
scrollable
);
final
DragStartDetails
dragDetails
;
@override
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
if
(
dragDetails
!=
null
)
description
.
add
(
'
$dragDetails
'
);
}
}
class
ScrollUpdateNotification
extends
ScrollNotification2
{
ScrollUpdateNotification
({
@required
Scrollable2State
scrollable
,
this
.
dragDetails
,
this
.
scrollDelta
,
})
:
super
(
scrollable:
scrollable
);
final
DragUpdateDetails
dragDetails
;
/// The distance by which the [Scrollable2] was scrolled, in logical pixels.
final
double
scrollDelta
;
@override
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
description
.
add
(
'scrollDelta:
$scrollDelta
'
);
if
(
dragDetails
!=
null
)
description
.
add
(
'
$dragDetails
'
);
}
}
class
OverscrollNotification
extends
ScrollNotification2
{
OverscrollNotification
({
@required
Scrollable2State
scrollable
,
this
.
dragDetails
,
@required
this
.
overscroll
,
this
.
velocity
:
0.0
,
})
:
super
(
scrollable:
scrollable
)
{
assert
(
overscroll
!=
null
);
assert
(
overscroll
.
isFinite
);
assert
(
overscroll
!=
0.0
);
assert
(
velocity
!=
null
);
}
final
DragUpdateDetails
dragDetails
;
/// The number of logical pixels that the [Scrollable2] avoided scrolling.
///
/// This will be negative for overscroll on the "start" side and positive for
/// overscroll on the "end" side.
final
double
overscroll
;
/// The velocity at which the [ScrollPosition] was changing when this
/// overscroll happened.
///
/// This will typically be 0.0 for touch-driven overscrolls, and positive
/// for overscrolls that happened from a [BallisticScrollActivity] or
/// [DrivenScrollActivity].
final
double
velocity
;
@override
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
description
.
add
(
'overscroll:
${overscroll.toStringAsFixed(1)}
'
);
description
.
add
(
'velocity:
${velocity.toStringAsFixed(1)}
'
);
if
(
dragDetails
!=
null
)
description
.
add
(
'
$dragDetails
'
);
}
}
class
ScrollEndNotification
extends
ScrollNotification2
{
ScrollEndNotification
({
@required
Scrollable2State
scrollable
,
this
.
dragDetails
,
})
:
super
(
scrollable:
scrollable
);
final
DragEndDetails
dragDetails
;
@override
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
if
(
dragDetails
!=
null
)
description
.
add
(
'
$dragDetails
'
);
}
}
class
UserScrollNotification
extends
ScrollNotification2
{
UserScrollNotification
({
@required
Scrollable2State
scrollable
,
this
.
direction
,
})
:
super
(
scrollable:
scrollable
);
final
ScrollDirection
direction
;
@override
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
description
.
add
(
'direction:
$direction
'
);
}
}
packages/flutter/lib/src/widgets/scroll_simulation.dart
View file @
63160b3d
...
@@ -7,17 +7,120 @@ import 'dart:math' as math;
...
@@ -7,17 +7,120 @@ import 'dart:math' as math;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/physics.dart'
;
import
'package:flutter/physics.dart'
;
final
SpringDescription
_kScrollSpring
=
new
SpringDescription
.
withDampingRatio
(
mass:
0.5
,
springConstant:
100.0
,
ratio:
1.1
);
/// An implementation of scroll physics that matches iOS.
final
double
_kDrag
=
0.025
;
///
/// See also:
///
/// * [ClampingScrollSimulation], which implements Android scroll physics.
class
BouncingScrollSimulation
extends
SimulationGroup
{
/// Creates a simulation group for scrolling on iOS, with the given
/// parameters.
///
/// The position and velocity arguments must use the same units as will be
/// expected from the [x] and [dx] methods respectively (typically logical
/// pixels and logical pixels per second respectively).
///
/// The leading and trailing extents must use the unit of length, the same
/// unit as used for the position argument and as expected from the [x]
/// method (typically logical pixels).
///
/// The units used with the provided [SpringDescription] must similarly be
/// consistent with the other arguments. A default set of constants is used
/// for the `spring` description if it is omitted; these defaults assume
/// that the unit of length is the logical pixel.
BouncingScrollSimulation
({
@required
double
position
,
@required
double
velocity
,
@required
double
leadingExtent
,
@required
double
trailingExtent
,
SpringDescription
spring
,
})
:
_leadingExtent
=
leadingExtent
,
_trailingExtent
=
trailingExtent
,
_spring
=
spring
??
_defaultScrollSpring
{
assert
(
position
!=
null
);
assert
(
velocity
!=
null
);
assert
(
_leadingExtent
!=
null
);
assert
(
_trailingExtent
!=
null
);
assert
(
_leadingExtent
<=
_trailingExtent
);
assert
(
_spring
!=
null
);
_chooseSimulation
(
position
,
velocity
,
0.0
);
}
// This class is based on Scroller.java from
final
double
_leadingExtent
;
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget
final
double
_trailingExtent
;
// The "See" comments refer to Scroller methods and values. Some simplifications
final
SpringDescription
_spring
;
// have been made.
class
_MountainViewSimulation
extends
Simulation
{
static
final
SpringDescription
_defaultScrollSpring
=
new
SpringDescription
.
withDampingRatio
(
_MountainViewSimulation
({
mass:
0.5
,
this
.
position
,
springConstant:
100.0
,
this
.
velocity
,
ratio:
1.1
,
);
bool
_isSpringing
=
false
;
Simulation
_currentSimulation
;
double
_offset
=
0.0
;
// This simulation can only step forward.
@override
bool
step
(
double
time
)
=>
_chooseSimulation
(
_currentSimulation
.
x
(
time
-
_offset
),
_currentSimulation
.
dx
(
time
-
_offset
),
time
,
);
@override
Simulation
get
currentSimulation
=>
_currentSimulation
;
@override
double
get
currentIntervalOffset
=>
_offset
;
bool
_chooseSimulation
(
double
position
,
double
velocity
,
double
intervalOffset
)
{
if
(!
_isSpringing
)
{
if
(
position
>
_trailingExtent
)
{
_isSpringing
=
true
;
_offset
=
intervalOffset
;
_currentSimulation
=
new
ScrollSpringSimulation
(
_spring
,
position
,
_trailingExtent
,
velocity
);
return
true
;
}
else
if
(
position
<
_leadingExtent
)
{
_isSpringing
=
true
;
_offset
=
intervalOffset
;
_currentSimulation
=
new
ScrollSpringSimulation
(
_spring
,
position
,
_leadingExtent
,
velocity
);
return
true
;
}
else
if
(
_currentSimulation
==
null
)
{
_currentSimulation
=
new
FrictionSimulation
(
0.135
,
position
,
velocity
*
0.91
);
return
true
;
}
}
return
false
;
}
@override
String
toString
()
{
return
'
$runtimeType
(leadingExtent:
$_leadingExtent
, trailingExtent:
$_trailingExtent
)'
;
}
}
/// An implementation of scroll physics that matches Android.
///
/// See also:
///
/// * [BouncingScrollSimulation], which implements iOS scroll physics.
//
// This class is based on Scroller.java from Android:
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget
//
// The "See..." comments below refer to Scroller methods and values. Some
// simplifications have been made.
class
ClampingScrollSimulation
extends
Simulation
{
/// Creates a scroll physics simulation that matches Android scrolling.
//
// TODO(ianh): The incoming `velocity` is used to determine the starting speed
// and duration, but does not represent the exact velocity of the simulation
// at t=0 as it should. This causes crazy scrolling irregularities when the
// scroll dimensions change during a fling.
ClampingScrollSimulation
({
@required
this
.
position
,
@required
this
.
velocity
,
this
.
friction
:
0.015
,
this
.
friction
:
0.015
,
})
{
})
{
_scaledFriction
=
friction
*
_decelerationForFriction
(
0.84
);
// See mPhysicalCoeff
_scaledFriction
=
friction
*
_decelerationForFriction
(
0.84
);
// See mPhysicalCoeff
...
@@ -33,7 +136,7 @@ class _MountainViewSimulation extends Simulation {
...
@@ -33,7 +136,7 @@ class _MountainViewSimulation extends Simulation {
double
_duration
;
double
_duration
;
double
_distance
;
double
_distance
;
// See DECELERATION_RATE
// See DECELERATION_RATE
.
static
final
double
_decelerationRate
=
math
.
log
(
0.78
)
/
math
.
log
(
0.9
);
static
final
double
_decelerationRate
=
math
.
log
(
0.78
)
/
math
.
log
(
0.9
);
// See computeDeceleration().
// See computeDeceleration().
...
@@ -41,24 +144,26 @@ class _MountainViewSimulation extends Simulation {
...
@@ -41,24 +144,26 @@ class _MountainViewSimulation extends Simulation {
return
friction
*
61774.04968
;
return
friction
*
61774.04968
;
}
}
// See getSplineDeceleration()
// See getSplineDeceleration()
.
double
_flingDeceleration
(
double
velocity
)
{
double
_flingDeceleration
(
double
velocity
)
{
return
math
.
log
(
0.35
*
velocity
.
abs
()
/
_scaledFriction
);
return
math
.
log
(
0.35
*
velocity
.
abs
()
/
_scaledFriction
);
}
}
// See getSplineFlingDuration(). Returns a value in seconds.
// See getSplineFlingDuration(). Returns a value in seconds.
double
_flingDuration
(
double
velocity
)
{
double
_flingDuration
(
double
velocity
)
{
return
math
.
exp
(
_flingDeceleration
(
velocity
)
/
(
_decelerationRate
-
1.0
));
return
math
.
exp
(
_flingDeceleration
(
velocity
)
/
(
_decelerationRate
-
1.0
));
}
}
// See getSplineFlingDistance()
// See getSplineFlingDistance()
.
double
_flingDistance
(
double
velocity
)
{
double
_flingDistance
(
double
velocity
)
{
final
double
rate
=
_decelerationRate
/
(
_decelerationRate
-
1.0
)
*
_flingDeceleration
(
velocity
);
final
double
rate
=
_decelerationRate
/
(
_decelerationRate
-
1.0
)
*
_flingDeceleration
(
velocity
);
return
_scaledFriction
*
math
.
exp
(
rate
);
return
_scaledFriction
*
math
.
exp
(
rate
);
}
}
// Based on a cubic curve fit to the computeScrollOffset() values produced
// Based on a cubic curve fit to the Scroller.computeScrollOffset() values
// for an initial velocity of 4000. The value of scroller.getDuration()
// produced for an initial velocity of 4000. The value of Scroller.getDuration()
// and scroller.getFinalY() were 686ms and 961 pixels respectively.
// and Scroller.getFinalY() were 686ms and 961 pixels respectively.
//
// Algebra courtesy of Wolfram Alpha.
// Algebra courtesy of Wolfram Alpha.
//
//
// f(x) = scrollOffset, x is time in millseconds
// f(x) = scrollOffset, x is time in millseconds
...
@@ -74,7 +179,7 @@ class _MountainViewSimulation extends Simulation {
...
@@ -74,7 +179,7 @@ class _MountainViewSimulation extends Simulation {
return
(
1.2
*
t
*
t
*
t
)
-
(
3.27
*
t
*
t
)
+
(
3.065
*
t
);
return
(
1.2
*
t
*
t
*
t
)
-
(
3.27
*
t
*
t
)
+
(
3.065
*
t
);
}
}
// The deriv
iate of the _fling
Penetration() function.
// The deriv
ative of the _flingDistance
Penetration() function.
double
_flingVelocityPenetration
(
double
t
)
{
double
_flingVelocityPenetration
(
double
t
)
{
return
(
3.63693
*
t
*
t
)
-
(
6.5424
*
t
)
+
3.06542
;
return
(
3.63693
*
t
*
t
)
-
(
6.5424
*
t
)
+
3.06542
;
}
}
...
@@ -88,7 +193,7 @@ class _MountainViewSimulation extends Simulation {
...
@@ -88,7 +193,7 @@ class _MountainViewSimulation extends Simulation {
@override
@override
double
dx
(
double
time
)
{
double
dx
(
double
time
)
{
final
double
t
=
(
time
/
_duration
).
clamp
(
0.0
,
1.0
);
final
double
t
=
(
time
/
_duration
).
clamp
(
0.0
,
1.0
);
return
velocity
*
_flingVelocityPenetration
(
t
)
;
return
_distance
*
_flingVelocityPenetration
(
t
)
*
velocity
.
sign
;
}
}
@override
@override
...
@@ -97,12 +202,29 @@ class _MountainViewSimulation extends Simulation {
...
@@ -97,12 +202,29 @@ class _MountainViewSimulation extends Simulation {
}
}
}
}
// DELETE EVERYTHING BELOW THIS LINE WHEN REMOVING LEGACY SCROLLING CODE
final
SpringDescription
_kScrollSpring
=
new
SpringDescription
.
withDampingRatio
(
mass:
0.5
,
springConstant:
100.0
,
ratio:
1.1
);
final
double
_kDrag
=
0.025
;
class
_CupertinoSimulation
extends
FrictionSimulation
{
class
_CupertinoSimulation
extends
FrictionSimulation
{
static
const
double
drag
=
0.135
;
static
const
double
drag
=
0.135
;
_CupertinoSimulation
({
double
position
,
double
velocity
})
_CupertinoSimulation
({
double
position
,
double
velocity
})
:
super
(
drag
,
position
,
velocity
*
0.91
);
:
super
(
drag
,
position
,
velocity
*
0.91
);
}
}
class
_MountainViewSimulation
extends
ClampingScrollSimulation
{
_MountainViewSimulation
({
double
position
,
double
velocity
,
double
friction:
0.015
,
})
:
super
(
position:
position
,
velocity:
velocity
,
friction:
friction
,
);
}
/// Composite simulation for scrollable interfaces.
/// Composite simulation for scrollable interfaces.
///
///
/// Simulates kinetic scrolling behavior between a leading and trailing
/// Simulates kinetic scrolling behavior between a leading and trailing
...
@@ -123,10 +245,10 @@ class ScrollSimulation extends SimulationGroup {
...
@@ -123,10 +245,10 @@ class ScrollSimulation extends SimulationGroup {
///
///
/// The final argument is the coefficient of friction, which is unitless.
/// The final argument is the coefficient of friction, which is unitless.
ScrollSimulation
({
ScrollSimulation
({
double
position
,
@required
double
position
,
double
velocity
,
@required
double
velocity
,
double
leadingExtent
,
@required
double
leadingExtent
,
double
trailingExtent
,
@required
double
trailingExtent
,
SpringDescription
spring
,
SpringDescription
spring
,
double
drag
,
double
drag
,
TargetPlatform
platform
,
TargetPlatform
platform
,
...
@@ -135,6 +257,8 @@ class ScrollSimulation extends SimulationGroup {
...
@@ -135,6 +257,8 @@ class ScrollSimulation extends SimulationGroup {
_spring
=
spring
??
_kScrollSpring
,
_spring
=
spring
??
_kScrollSpring
,
_drag
=
drag
??
_kDrag
,
_drag
=
drag
??
_kDrag
,
_platform
=
platform
{
_platform
=
platform
{
assert
(
position
!=
null
);
assert
(
velocity
!=
null
);
assert
(
_leadingExtent
!=
null
);
assert
(
_leadingExtent
!=
null
);
assert
(
_trailingExtent
!=
null
);
assert
(
_trailingExtent
!=
null
);
assert
(
_spring
!=
null
);
assert
(
_spring
!=
null
);
...
...
packages/flutter/lib/src/widgets/scrollable.dart
View file @
63160b3d
...
@@ -9,6 +9,7 @@ import 'dart:ui' as ui show window;
...
@@ -9,6 +9,7 @@ import 'dart:ui' as ui show window;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/physics.dart'
;
import
'package:flutter/physics.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'basic.dart'
;
import
'basic.dart'
;
...
@@ -17,9 +18,650 @@ import 'framework.dart';
...
@@ -17,9 +18,650 @@ import 'framework.dart';
import
'gesture_detector.dart'
;
import
'gesture_detector.dart'
;
import
'notification_listener.dart'
;
import
'notification_listener.dart'
;
import
'page_storage.dart'
;
import
'page_storage.dart'
;
import
'scroll_absolute.dart'
show
ViewportScrollBehavior
;
import
'scroll_behavior.dart'
;
import
'scroll_behavior.dart'
;
import
'scroll_configuration.dart'
;
import
'scroll_configuration.dart'
;
import
'scroll_notification.dart'
;
import
'ticker_provider.dart'
;
import
'ticker_provider.dart'
;
import
'viewport.dart'
;
export
'package:flutter/physics.dart'
show
Tolerance
;
// This file defines an unopinionated scrolling mechanism.
// See scroll_absolute.dart for variants that do things by pixels.
abstract
class
ScrollPosition
extends
ViewportOffset
{
/// Create a new [ScrollPosition].
///
/// The first argument is the [Scrollable2State] object with which this scroll
/// position is associated. The second provides the tolerances for activities
/// that use simulations and need to decide when to end them. The final
/// argument is the previous instance of [ScrollPosition] that was being used
/// by the same [Scrollable2State], if any.
ScrollPosition
(
this
.
state
,
this
.
scrollTolerances
,
ScrollPosition
oldPosition
)
{
assert
(
state
is
TickerProvider
);
assert
(
scrollTolerances
!=
null
);
if
(
oldPosition
!=
null
)
absorb
(
oldPosition
);
if
(
activity
==
null
)
beginIdleActivity
();
assert
(
activity
!=
null
);
assert
(
activity
.
position
==
this
);
}
@protected
final
Scrollable2State
state
;
final
Tolerance
scrollTolerances
;
@protected
TickerProvider
get
vsync
=>
state
;
@protected
ScrollActivity
get
activity
=>
_activity
;
ScrollActivity
_activity
;
/// Take any current applicable state from the given [ScrollPosition].
///
/// This method is called by the constructor, instead of calling
/// [beginIdleActivity], if it is given an `oldPosition`. It adopts the old
/// position's current [activity] as its own.
///
/// This method is destructive to the other [ScrollPosition]. The other
/// object must be disposed immediately after this call (in the same call
/// stack, before microtask resolution, by whomever called this object's
/// constructor).
///
/// If the old [ScrollPosition] object is a different [runtimeType] than this
/// one, the [ScrollActivity.resetActivity] method is invoked on the newly
/// adopted [ScrollActivity].
///
/// When overriding this method, call `super.absorb` after setting any
/// metrics-related or activity-related state, since this method may restart
/// the activity and scroll activities tend to use those metrics when being
/// restarted.
@protected
@mustCallSuper
void
absorb
(
ScrollPosition
other
)
{
assert
(
activity
==
null
);
assert
(
other
!=
this
);
assert
(
other
.
state
==
state
);
assert
(
other
.
activity
!=
null
);
final
bool
oldIgnorePointer
=
shouldIgnorePointer
;
_userScrollDirection
=
other
.
_userScrollDirection
;
other
.
activity
.
_position
=
this
;
_activity
=
other
.
activity
;
other
.
_activity
=
null
;
if
(
oldIgnorePointer
!=
shouldIgnorePointer
)
state
.
_updateIgnorePointer
(
shouldIgnorePointer
);
if
(
other
.
runtimeType
!=
runtimeType
)
activity
.
resetActivity
();
}
/// Change the current [activity], disposing of the old one and
/// sending scroll notifications as necessary.
///
/// If the argument is null, this method has no effect. This is convenient for
/// cases where the new activity is obtained from another method, and that
/// method might return null, since it means the caller does not have to
/// explictly null-check the argument.
void
beginActivity
(
ScrollActivity
newActivity
)
{
if
(
newActivity
==
null
)
return
;
assert
(
newActivity
.
position
==
this
);
final
bool
oldIgnorePointer
=
shouldIgnorePointer
;
bool
wasScrolling
;
if
(
activity
!=
null
)
{
wasScrolling
=
activity
.
isScrolling
;
if
(
wasScrolling
&&
!
newActivity
.
isScrolling
)
dispatchNotification
(
activity
.
createScrollEndNotification
(
state
));
activity
.
dispose
();
}
else
{
wasScrolling
=
false
;
}
_activity
=
newActivity
;
if
(
oldIgnorePointer
!=
shouldIgnorePointer
)
state
.
_updateIgnorePointer
(
shouldIgnorePointer
);
if
(!
activity
.
isScrolling
)
updateUserScrollDirection
(
ScrollDirection
.
idle
);
if
(!
wasScrolling
&&
activity
.
isScrolling
)
dispatchNotification
(
activity
.
createScrollStartNotification
(
state
));
}
@protected
void
dispatchNotification
(
Notification
notification
)
{
assert
(
state
.
mounted
);
notification
.
dispatch
(
state
.
_viewportKey
.
currentContext
);
}
@override
void
dispose
()
{
activity
?.
dispose
();
// it will be null if it got absorbed by another ScrollPosition
_activity
=
null
;
super
.
dispose
();
}
void
touched
()
{
_activity
.
touched
();
}
@override
@mustCallSuper
void
applyViewportDimension
(
double
viewportDimension
)
{
state
.
_updateGestureDetectors
(
canDrag
);
}
@override
@mustCallSuper
bool
applyContentDimensions
(
double
minScrollExtent
,
double
maxScrollExtent
)
{
state
.
_updateGestureDetectors
(
canDrag
);
return
true
;
}
/// The direction that the user most recently began scrolling in.
@override
ScrollDirection
get
userScrollDirection
=>
_userScrollDirection
;
ScrollDirection
_userScrollDirection
=
ScrollDirection
.
idle
;
/// Set [userScrollDirection] to the given value.
///
/// If this changes the value, then a [UserScrollNotification] is dispatched.
///
/// This should only be set from the current [ScrollActivity] (see [activity]).
void
updateUserScrollDirection
(
ScrollDirection
value
)
{
assert
(
value
!=
null
);
if
(
userScrollDirection
==
value
)
return
;
_userScrollDirection
=
value
;
dispatchNotification
(
new
UserScrollNotification
(
scrollable:
state
,
direction:
value
));
}
@override
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
description
.
add
(
'
$activity
'
);
description
.
add
(
'
$userScrollDirection
'
);
}
bool
get
canDrag
=>
false
;
bool
get
shouldIgnorePointer
=>
false
;
@mustCallSuper
void
postFrameCleanup
()
{
}
void
beginIdleActivity
()
{
beginActivity
(
new
IdleScrollActivity
(
this
));
}
DragScrollActivity
beginDragActivity
(
DragStartDetails
details
)
{
if
(
canDrag
)
{
throw
new
FlutterError
(
'
$runtimeType
does not implement beginDragActivity but canDrag is true.
\n
'
'If a ScrollPosition class ever returns true from canDrag, then it must '
'implement the beginDragActivity method to handle drags.
\n
'
'The beginDragActivity method should call beginActivity, passing it a new '
'instance of a DragScrollActivity subclass that has been initialized with '
'this ScrollPosition object as its position.'
);
}
assert
(
false
);
return
null
;
}
/// Used by [AbsoluteDragScrollActivity] and other user-driven activities to
/// convert an offset in logical pixels as provided by the [DragUpdateDetails]
/// into a delta to apply using [setPixels].
///
/// This is used by some [ScrollPosition] subclasses to apply friction during
/// overscroll situations.
double
applyPhysicsToUserOffset
(
double
offset
)
=>
offset
;
// ///
// /// The velocity should be in logical pixels per second.
void
beginBallisticActivity
(
double
velocity
)
{
beginIdleActivity
();
}
// ABSTRACT METHODS
/// Update the scroll position ([pixels]) to a given pixel value.
///
/// This should only be called by the current [ScrollActivity], either during
/// the transient callback phase or in response to user input.
///
/// Returns the overscroll, if any. If the return value is 0.0, that means
/// that [pixels] now returns the given `value`. If the return value is
/// positive, then [pixels] is less than the requested `value` by the given
/// amount (overscroll past the max extent), and if it is negative, it is
/// greater than the requested `value` by the given amount (underscroll past
/// the min extent).
///
/// Implementations of this method must dispatch scroll update notifications
/// (using [dispatchNotification] and
/// [ScrollActivity.createScrollUpdateNotification]) after applying the new
/// value (so after [pixels] changes). If the entire change is not applied,
/// the overscroll should be reported by subsequently also dispatching an
/// overscroll notification using
/// [ScrollActivity.createOverscrollNotification].
double
setPixels
(
double
value
);
/// Returns a description of the [Scrollable].
///
/// Accurately describing the metrics typicaly requires using information
/// provided by the viewport to the [applyViewportDimension] and
/// [applyContentDimensions] methods.
///
/// The metrics do not need to be in absolute (pixel) units, but they must be
/// in consistent units (so that they can be compared over time or used to
/// drive diagrammatic user interfaces such as scrollbars).
ScrollableMetrics
getMetrics
();
// Subclasses must also implement the [pixels] getter and [correctBy].
}
/// Base class for scrolling activities like dragging, and flinging.
abstract
class
ScrollActivity
{
ScrollActivity
(
ScrollPosition
position
)
{
_position
=
position
;
}
@protected
ScrollPosition
get
position
=>
_position
;
ScrollPosition
_position
;
/// Called by the [ScrollPosition] when it has changed type (for example, when
/// changing from an Android-style scroll position to an iOS-style scroll
/// position). If this activity can differ between the two modes, then it
/// should tell the position to restart that activity appropriately.
///
/// For example, [BallisticScrollActivity]'s implementation calls
/// [ScrollPosition.beginBallisticActivity].
void
resetActivity
()
{
}
Notification
createScrollStartNotification
(
Scrollable2State
scrollable
)
{
return
new
ScrollStartNotification
(
scrollable:
scrollable
);
}
Notification
createScrollUpdateNotification
(
Scrollable2State
scrollable
,
double
scrollDelta
)
{
return
new
ScrollUpdateNotification
(
scrollable:
scrollable
,
scrollDelta:
scrollDelta
);
}
Notification
createOverscrollNotification
(
Scrollable2State
scrollable
,
double
overscroll
)
{
return
new
OverscrollNotification
(
scrollable:
scrollable
,
overscroll:
overscroll
);
}
Notification
createScrollEndNotification
(
Scrollable2State
scrollable
)
{
return
new
ScrollEndNotification
(
scrollable:
scrollable
);
}
void
touched
()
{
}
void
applyNewDimensions
()
{
}
bool
get
shouldIgnorePointer
;
bool
get
isScrolling
;
@mustCallSuper
void
dispose
()
{
}
@override
String
toString
()
=>
'
$runtimeType
'
;
}
class
IdleScrollActivity
extends
ScrollActivity
{
IdleScrollActivity
(
ScrollPosition
position
)
:
super
(
position
);
@override
void
applyNewDimensions
()
{
position
.
beginBallisticActivity
(
0.0
);
}
@override
bool
get
shouldIgnorePointer
=>
false
;
@override
bool
get
isScrolling
=>
false
;
}
abstract
class
DragScrollActivity
extends
ScrollActivity
{
DragScrollActivity
(
ScrollPosition
position
)
:
super
(
position
);
void
update
(
DragUpdateDetails
details
,
{
bool
reverse
});
void
end
(
DragEndDetails
details
,
{
bool
reverse
});
@override
void
touched
()
{
assert
(
false
);
}
@override
void
dispose
()
{
position
.
state
.
_drag
=
null
;
super
.
dispose
();
}
}
/// Base class for delegates that instantiate [ScrollPosition] objects.
abstract
class
ScrollBehavior2
{
const
ScrollBehavior2
();
Widget
wrap
(
BuildContext
context
,
Widget
child
,
AxisDirection
axisDirection
);
/// Returns a new instance of the ScrollPosition class that this
/// ScrollBehavior2 subclass creates.
///
/// A given ScrollBehavior2 object must always return the same kind of
/// ScrollPosition, with the same configuration.
///
/// The `oldPosition` argument should be passed to the `ScrollPosition`
/// constructor so that the new position can take over the old position's
/// offset and (if it's the same type) activity.
///
/// When calling [createScrollPosition] with a non-null `oldPosition`, that
/// object must be disposed (via [ScrollPosition.oldPosition]) in the same
/// call stack. Passing a non-null `oldPosition` is a destructive operation
/// for that [ScrollPosition].
ScrollPosition
createScrollPosition
(
BuildContext
context
,
Scrollable2State
state
,
ScrollPosition
oldPosition
);
/// Whether this delegate is different than the old delegate, or would now
/// return meaningfully different widgets from [wrap] or a meaningfully
/// different [ScrollPosition] from [createScrollPosition].
///
/// It is not necessary to return true if the return values for [wrap] and
/// [createScrollPosition] would only be different because of depending on the
/// [BuildContext] argument they are provided, as dependency checking is
/// handled separately.
bool
shouldNotify
(
@checked
ScrollBehavior2
oldDelegate
);
@override
String
toString
()
=>
'
$runtimeType
'
;
}
class
ScrollConfiguration2
extends
InheritedWidget
{
const
ScrollConfiguration2
({
Key
key
,
@required
this
.
delegate
,
@required
Widget
child
,
})
:
super
(
key:
key
,
child:
child
);
final
ScrollBehavior2
delegate
;
static
ScrollBehavior2
of
(
BuildContext
context
)
{
ScrollConfiguration2
configuration
=
context
.
inheritFromWidgetOfExactType
(
ScrollConfiguration2
);
return
configuration
?.
delegate
;
}
@override
bool
updateShouldNotify
(
ScrollConfiguration2
old
)
{
assert
(
delegate
!=
null
);
return
delegate
.
runtimeType
!=
old
.
delegate
.
runtimeType
||
delegate
.
shouldNotify
(
old
.
delegate
);
}
}
class
Scrollable2
extends
StatefulWidget
{
Scrollable2
({
Key
key
,
this
.
axisDirection
:
AxisDirection
.
down
,
this
.
anchor
:
0.0
,
this
.
initialScrollOffset
:
0.0
,
this
.
scrollBehavior
,
this
.
center
,
this
.
children
,
})
:
super
(
key:
key
)
{
assert
(
axisDirection
!=
null
);
assert
(
anchor
!=
null
);
assert
(
initialScrollOffset
!=
null
);
}
final
AxisDirection
axisDirection
;
final
double
anchor
;
final
double
initialScrollOffset
;
/// The delegate that creates the [ScrollPosition] and wraps the viewport
/// in extra widgets (e.g. for overscroll effects).
///
/// If no scroll behavior delegate is explicitly supplied, the scroll behavior
/// from the nearest [ScrollConfiguration2] is used. If there is no
/// [ScrollConfiguration2] in scope, a new [ViewportScrollBehavior] is used.
final
ScrollBehavior2
scrollBehavior
;
final
Key
center
;
final
List
<
Widget
>
children
;
Axis
get
axis
=>
axisDirectionToAxis
(
axisDirection
);
@override
Scrollable2State
createState
()
=>
new
Scrollable2State
();
ScrollBehavior2
getScrollBehavior
(
BuildContext
context
)
{
return
scrollBehavior
??
ScrollConfiguration2
.
of
(
context
)
??
new
ViewportScrollBehavior
();
}
/// Whether, when this widget has been replaced by another, the scroll
/// behavior and scroll position need to be updated as well.
bool
shouldUpdateScrollPosition
(
Scrollable2
oldWidget
)
{
return
scrollBehavior
.
runtimeType
!=
oldWidget
.
scrollBehavior
.
runtimeType
||
(
scrollBehavior
!=
null
&&
scrollBehavior
.
shouldNotify
(
oldWidget
.
scrollBehavior
));
}
@override
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
description
.
add
(
'
$axisDirection
'
);
if
(
anchor
!=
0.0
)
description
.
add
(
'anchor:
${anchor.toStringAsFixed(1)}
'
);
if
(
initialScrollOffset
!=
0.0
)
description
.
add
(
'initialScrollOffset:
${initialScrollOffset.toStringAsFixed(1)}
'
);
if
(
scrollBehavior
!=
null
)
{
description
.
add
(
'scrollBehavior:
$scrollBehavior
'
);
}
else
{
description
.
add
(
'scrollBehavior: use inherited ScrollBehavior2'
);
}
if
(
center
!=
null
)
description
.
add
(
'center:
$center
'
);
}
}
/// State object for a [Scrollable2] widget.
///
/// To manipulate a [Scrollable2] widget's scroll position, use the object
/// obtained from the [position] property.
///
/// To be informed of when a [Scrollable2] widget is scrolling, use a
/// [NotificationListener] to listen for [ScrollNotification2] notifications.
///
/// This class is not intended to be subclassed. To specialize the behavior of a
/// [Scrollable2], provide it with a custom [ScrollBehavior2] delegate.
class
Scrollable2State
extends
State
<
Scrollable2
>
with
TickerProviderStateMixin
{
/// The controller for this [Scrollable2] widget's viewport position.
///
/// To control what kind of [ScrollPosition] is created for a [Scrollable2],
/// provide it with a custom [ScrollBehavior2] delegate that creates the
/// appropriate [ScrollPosition] controller in its
/// [ScrollBehavior2.createScrollPosition] method.
ScrollPosition
get
position
=>
_position
;
ScrollPosition
_position
;
ScrollBehavior2
_scrollBehavior
;
// only call this from places that will definitely trigger a rebuild
void
_updatePosition
()
{
_scrollBehavior
=
config
.
getScrollBehavior
(
context
);
final
ScrollPosition
oldPosition
=
position
;
_position
=
_scrollBehavior
.
createScrollPosition
(
context
,
this
,
oldPosition
);
assert
(
position
!=
null
);
if
(
oldPosition
!=
null
)
{
// It's important that we not do this until after the RenderViewport2
// object has had a chance to unregister its listeners from the old
// position. So, schedule a microtask to do it.
scheduleMicrotask
(
oldPosition
.
dispose
);
}
}
@override
void
dependenciesChanged
()
{
super
.
dependenciesChanged
();
_updatePosition
();
}
@override
void
didUpdateConfig
(
Scrollable2
oldConfig
)
{
super
.
didUpdateConfig
(
oldConfig
);
if
(
config
.
shouldUpdateScrollPosition
(
oldConfig
))
_updatePosition
();
}
@override
void
dispose
()
{
position
.
dispose
();
super
.
dispose
();
}
// GESTURE RECOGNITION AND POINTER IGNORING
final
GlobalKey
<
RawGestureDetectorState
>
_gestureDetectorKey
=
new
GlobalKey
<
RawGestureDetectorState
>();
final
GlobalKey
_ignorePointerKey
=
new
GlobalKey
();
final
GlobalKey
_viewportKey
=
new
GlobalKey
();
// This field is set during layout, and then reused until the next time it is set.
Map
<
Type
,
GestureRecognizerFactory
>
_gestureRecognizers
=
const
<
Type
,
GestureRecognizerFactory
>{};
bool
_shouldIgnorePointer
=
false
;
bool
_lastCanDrag
;
Axis
_lastAxisDirection
;
void
_updateGestureDetectors
(
bool
canDrag
)
{
if
(
canDrag
==
_lastCanDrag
&&
(!
canDrag
||
config
.
axis
==
_lastAxisDirection
))
return
;
if
(!
canDrag
)
{
_gestureRecognizers
=
const
<
Type
,
GestureRecognizerFactory
>{};
}
else
{
switch
(
config
.
axis
)
{
case
Axis
.
vertical
:
_gestureRecognizers
=
<
Type
,
GestureRecognizerFactory
>{
VerticalDragGestureRecognizer:
(
VerticalDragGestureRecognizer
recognizer
)
{
// ignore: map_value_type_not_assignable, https://github.com/flutter/flutter/issues/7173
return
(
recognizer
??=
new
VerticalDragGestureRecognizer
())
..
onDown
=
_handleDragDown
..
onStart
=
_handleDragStart
..
onUpdate
=
_handleDragUpdate
..
onEnd
=
_handleDragEnd
;
}
};
break
;
case
Axis
.
horizontal
:
_gestureRecognizers
=
<
Type
,
GestureRecognizerFactory
>{
HorizontalDragGestureRecognizer:
(
HorizontalDragGestureRecognizer
recognizer
)
{
// ignore: map_value_type_not_assignable, https://github.com/flutter/flutter/issues/7173
return
(
recognizer
??=
new
HorizontalDragGestureRecognizer
())
..
onDown
=
_handleDragDown
..
onStart
=
_handleDragStart
..
onUpdate
=
_handleDragUpdate
..
onEnd
=
_handleDragEnd
;
}
};
break
;
}
}
_lastCanDrag
=
canDrag
;
_lastAxisDirection
=
config
.
axis
;
if
(
_gestureDetectorKey
.
currentState
!=
null
)
_gestureDetectorKey
.
currentState
.
replaceGestureRecognizers
(
_gestureRecognizers
);
}
void
_updateIgnorePointer
(
bool
value
)
{
if
(
_shouldIgnorePointer
==
value
)
return
;
_shouldIgnorePointer
=
value
;
if
(
_ignorePointerKey
.
currentContext
!=
null
)
{
RenderIgnorePointer
renderBox
=
_ignorePointerKey
.
currentContext
.
findRenderObject
();
renderBox
.
ignoring
=
_shouldIgnorePointer
;
}
}
// TOUCH HANDLERS
DragScrollActivity
_drag
;
bool
get
_reverseDirection
{
assert
(
config
.
axisDirection
!=
null
);
switch
(
config
.
axisDirection
)
{
case
AxisDirection
.
up
:
case
AxisDirection
.
left
:
return
true
;
case
AxisDirection
.
down
:
case
AxisDirection
.
right
:
return
false
;
}
return
null
;
}
void
_handleDragDown
(
DragDownDetails
details
)
{
assert
(
_drag
==
null
);
position
.
touched
();
}
void
_handleDragStart
(
DragStartDetails
details
)
{
assert
(
_drag
==
null
);
_drag
=
position
.
beginDragActivity
(
details
);
}
void
_handleDragUpdate
(
DragUpdateDetails
details
)
{
assert
(
_drag
!=
null
);
_drag
.
update
(
details
,
reverse:
_reverseDirection
);
}
void
_handleDragEnd
(
DragEndDetails
details
)
{
assert
(
_drag
!=
null
);
_drag
.
end
(
details
,
reverse:
_reverseDirection
);
assert
(
_drag
==
null
);
}
// DESCRIPTION
@override
Widget
build
(
BuildContext
context
)
{
assert
(
position
!=
null
);
// TODO(ianh): Having all these global keys is sad.
Widget
result
=
new
RawGestureDetector
(
key:
_gestureDetectorKey
,
gestures:
_gestureRecognizers
,
behavior:
HitTestBehavior
.
opaque
,
child:
new
IgnorePointer
(
key:
_ignorePointerKey
,
ignoring:
_shouldIgnorePointer
,
child:
new
Viewport2
(
key:
_viewportKey
,
axisDirection:
config
.
axisDirection
,
anchor:
config
.
anchor
,
offset:
position
,
center:
config
.
center
,
children:
config
.
children
,
),
),
);
return
_scrollBehavior
.
wrap
(
context
,
result
,
config
.
axisDirection
);
}
@override
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
description
.
add
(
'position:
$position
'
);
}
}
// DELETE EVERYTHING BELOW THIS LINE WHEN REMOVING LEGACY SCROLLING CODE
/// Identifies one or both limits of a [Scrollable] in terms of its scrollDirection.
/// Identifies one or both limits of a [Scrollable] in terms of its scrollDirection.
enum
ScrollableEdge
{
enum
ScrollableEdge
{
...
@@ -207,7 +849,7 @@ class Scrollable extends StatefulWidget {
...
@@ -207,7 +849,7 @@ class Scrollable extends StatefulWidget {
double
scrollOffsetDelta
;
double
scrollOffsetDelta
;
if
(
targetMin
<
scrollableMin
)
{
if
(
targetMin
<
scrollableMin
)
{
if
(
targetMax
>
scrollableMax
)
{
if
(
targetMax
>
scrollableMax
)
{
// The target is to big to fit inside the scrollable. The best we can do
// The target is to
o
big to fit inside the scrollable. The best we can do
// is to center the target.
// is to center the target.
double
targetCenter
=
(
targetMin
+
targetMax
)
/
2.0
;
double
targetCenter
=
(
targetMin
+
targetMax
)
/
2.0
;
double
scrollableCenter
=
(
scrollableMin
+
scrollableMax
)
/
2.0
;
double
scrollableCenter
=
(
scrollableMin
+
scrollableMax
)
/
2.0
;
...
...
packages/flutter/lib/src/widgets/viewport.dart
0 → 100644
View file @
63160b3d
// Copyright 2015 The Chromium 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
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'framework.dart'
;
export
'package:flutter/rendering.dart'
show
AxisDirection
,
GrowthDirection
;
class
Viewport2
extends
MultiChildRenderObjectWidget
{
Viewport2
({
Key
key
,
this
.
axisDirection
:
AxisDirection
.
down
,
this
.
anchor
:
0.0
,
this
.
offset
,
this
.
center
,
List
<
Widget
>
children:
const
<
Widget
>[],
})
:
super
(
key:
key
,
children:
children
)
{
assert
(
center
==
null
||
children
.
where
((
Widget
child
)
=>
child
.
key
==
center
).
length
==
1
);
}
final
AxisDirection
axisDirection
;
final
double
anchor
;
final
ViewportOffset
offset
;
final
Key
center
;
@override
RenderViewport2
createRenderObject
(
BuildContext
context
)
{
return
new
RenderViewport2
(
axisDirection:
axisDirection
,
anchor:
anchor
,
offset:
offset
,
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
RenderViewport2
renderObject
)
{
renderObject
.
axisDirection
=
axisDirection
;
renderObject
.
anchor
=
anchor
;
renderObject
.
offset
=
offset
;
}
@override
Viewport2Element
createElement
()
=>
new
Viewport2Element
(
this
);
@override
void
debugFillDescription
(
List
<
String
>
description
)
{
super
.
debugFillDescription
(
description
);
description
.
add
(
'
$axisDirection
'
);
description
.
add
(
'anchor:
$anchor
'
);
description
.
add
(
'offset:
$offset
'
);
if
(
center
!=
null
)
{
description
.
add
(
'center:
$center
'
);
}
else
if
(
children
.
isNotEmpty
&&
children
.
first
.
key
!=
null
)
{
description
.
add
(
'center:
${children.first.key}
(implicit)'
);
}
}
}
class
Viewport2Element
extends
MultiChildRenderObjectElement
{
/// Creates an element that uses the given widget as its configuration.
Viewport2Element
(
Viewport2
widget
)
:
super
(
widget
);
@override
Viewport2
get
widget
=>
super
.
widget
;
@override
RenderViewport2
get
renderObject
=>
super
.
renderObject
;
@override
void
mount
(
Element
parent
,
dynamic
newSlot
)
{
super
.
mount
(
parent
,
newSlot
);
updateCenter
();
}
@override
void
update
(
MultiChildRenderObjectWidget
newWidget
)
{
super
.
update
(
newWidget
);
updateCenter
();
}
@protected
void
updateCenter
()
{
// TODO(ianh): cache the keys to make this faster
if
(
widget
.
center
!=
null
)
{
renderObject
.
center
=
children
.
singleWhere
(
(
Element
element
)
=>
element
.
widget
.
key
==
widget
.
center
).
renderObject
;
}
else
if
(
children
.
isNotEmpty
)
{
renderObject
.
center
=
children
.
first
.
renderObject
;
}
else
{
renderObject
.
center
=
null
;
}
}
}
packages/flutter/lib/widgets.dart
View file @
63160b3d
...
@@ -36,6 +36,7 @@ export 'src/widgets/navigator.dart';
...
@@ -36,6 +36,7 @@ export 'src/widgets/navigator.dart';
export
'src/widgets/notification_listener.dart'
;
export
'src/widgets/notification_listener.dart'
;
export
'src/widgets/orientation_builder.dart'
;
export
'src/widgets/orientation_builder.dart'
;
export
'src/widgets/overlay.dart'
;
export
'src/widgets/overlay.dart'
;
export
'src/widgets/overscroll_indicator.dart'
;
export
'src/widgets/page_storage.dart'
;
export
'src/widgets/page_storage.dart'
;
export
'src/widgets/pageable_list.dart'
;
export
'src/widgets/pageable_list.dart'
;
export
'src/widgets/pages.dart'
;
export
'src/widgets/pages.dart'
;
...
@@ -43,8 +44,10 @@ export 'src/widgets/performance_overlay.dart';
...
@@ -43,8 +44,10 @@ export 'src/widgets/performance_overlay.dart';
export
'src/widgets/placeholder.dart'
;
export
'src/widgets/placeholder.dart'
;
export
'src/widgets/raw_keyboard_listener.dart'
;
export
'src/widgets/raw_keyboard_listener.dart'
;
export
'src/widgets/routes.dart'
;
export
'src/widgets/routes.dart'
;
export
'src/widgets/scroll_absolute.dart'
;
export
'src/widgets/scroll_behavior.dart'
;
export
'src/widgets/scroll_behavior.dart'
;
export
'src/widgets/scroll_configuration.dart'
;
export
'src/widgets/scroll_configuration.dart'
;
export
'src/widgets/scroll_notification.dart'
;
export
'src/widgets/scroll_simulation.dart'
;
export
'src/widgets/scroll_simulation.dart'
;
export
'src/widgets/scrollable.dart'
;
export
'src/widgets/scrollable.dart'
;
export
'src/widgets/scrollable_grid.dart'
;
export
'src/widgets/scrollable_grid.dart'
;
...
@@ -59,6 +62,7 @@ export 'src/widgets/ticker_provider.dart';
...
@@ -59,6 +62,7 @@ export 'src/widgets/ticker_provider.dart';
export
'src/widgets/title.dart'
;
export
'src/widgets/title.dart'
;
export
'src/widgets/transitions.dart'
;
export
'src/widgets/transitions.dart'
;
export
'src/widgets/unique_widget.dart'
;
export
'src/widgets/unique_widget.dart'
;
export
'src/widgets/viewport.dart'
;
export
'src/widgets/virtual_viewport.dart'
;
export
'src/widgets/virtual_viewport.dart'
;
export
'package:vector_math/vector_math_64.dart'
show
Matrix4
;
export
'package:vector_math/vector_math_64.dart'
show
Matrix4
;
packages/flutter/test/rendering/mock_canvas.dart
View file @
63160b3d
...
@@ -265,14 +265,14 @@ class _TestRecordingCanvas implements Canvas {
...
@@ -265,14 +265,14 @@ class _TestRecordingCanvas implements Canvas {
@override
@override
void
save
()
{
void
save
()
{
_saveCount
+=
1
;
_saveCount
+=
1
;
super
.
save
();
// ends up in noSuchMethod
_invocations
.
add
(
new
_MethodCall
(
#save
));
}
}
@override
@override
void
restore
()
{
void
restore
()
{
_saveCount
-=
1
;
_saveCount
-=
1
;
assert
(
_saveCount
>=
0
);
assert
(
_saveCount
>=
0
);
super
.
restore
();
// ends up in noSuchMethod
_invocations
.
add
(
new
_MethodCall
(
#restore
));
}
}
@override
@override
...
@@ -281,6 +281,25 @@ class _TestRecordingCanvas implements Canvas {
...
@@ -281,6 +281,25 @@ class _TestRecordingCanvas implements Canvas {
}
}
}
}
class
_MethodCall
implements
Invocation
{
_MethodCall
(
this
.
_name
);
final
Symbol
_name
;
@override
bool
get
isAccessor
=>
false
;
@override
bool
get
isGetter
=>
false
;
@override
bool
get
isMethod
=>
true
;
@override
bool
get
isSetter
=>
false
;
@override
Symbol
get
memberName
=>
_name
;
@override
Map
<
Symbol
,
dynamic
>
get
namedArguments
=>
<
Symbol
,
dynamic
>{};
@override
List
<
dynamic
>
get
positionalArguments
=>
<
dynamic
>[];
}
class
_TestRecordingPaintingContext
implements
PaintingContext
{
class
_TestRecordingPaintingContext
implements
PaintingContext
{
_TestRecordingPaintingContext
(
this
.
canvas
);
_TestRecordingPaintingContext
(
this
.
canvas
);
...
...
packages/flutter/test/rendering/slivers_test.dart
View file @
63160b3d
...
@@ -8,6 +8,13 @@ import 'package:test/test.dart';
...
@@ -8,6 +8,13 @@ import 'package:test/test.dart';
import
'rendering_tester.dart'
;
import
'rendering_tester.dart'
;
void
main
(
)
{
void
main
(
)
{
test
(
'RenderViewport2 basic test - no children'
,
()
{
RenderViewport2
root
=
new
RenderViewport2
();
layout
(
root
);
root
.
offset
=
new
ViewportOffset
.
fixed
(
900.0
);
pumpFrame
();
});
test
(
'RenderViewport2 basic test - down'
,
()
{
test
(
'RenderViewport2 basic test - down'
,
()
{
RenderBox
a
,
b
,
c
,
d
,
e
;
RenderBox
a
,
b
,
c
,
d
,
e
;
RenderViewport2
root
=
new
RenderViewport2
(
RenderViewport2
root
=
new
RenderViewport2
(
...
@@ -199,8 +206,6 @@ void main() {
...
@@ -199,8 +206,6 @@ void main() {
expect
(
e
.
localToGlobal
(
const
Point
(
0.0
,
0.0
)),
const
Point
(-
300.0
,
0.0
));
expect
(
e
.
localToGlobal
(
const
Point
(
0.0
,
0.0
)),
const
Point
(-
300.0
,
0.0
));
});
});
// TODO(ianh): test positioning when the children are too big to fit in the main axis
// TODO(ianh): test shrinkWrap
// TODO(ianh): test anchor
// TODO(ianh): test anchor
// TODO(ianh): test offset
// TODO(ianh): test offset
// TODO(ianh): test center
// TODO(ianh): test center
...
...
packages/flutter/test/widgets/framework_test.dart
View file @
63160b3d
...
@@ -98,6 +98,7 @@ void main() {
...
@@ -98,6 +98,7 @@ void main() {
expect
(
didReceiveCallback
,
isTrue
);
expect
(
didReceiveCallback
,
isTrue
);
});
});
testWidgets
(
'Defunct setState throws exception'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Defunct setState throws exception'
,
(
WidgetTester
tester
)
async
{
StateSetter
setState
;
StateSetter
setState
;
...
@@ -143,4 +144,23 @@ void main() {
...
@@ -143,4 +144,23 @@ void main() {
expect
(
log
[
0
],
matches
(
'Deactivated'
));
expect
(
log
[
0
],
matches
(
'Deactivated'
));
expect
(
log
[
1
],
matches
(
'Discarding .+ from inactive elements list.'
));
expect
(
log
[
1
],
matches
(
'Discarding .+ from inactive elements list.'
));
});
});
testWidgets
(
'MultiChildRenderObjectElement.children'
,
(
WidgetTester
tester
)
async
{
GlobalKey
key0
,
key1
,
key2
;
await
tester
.
pumpWidget
(
new
Column
(
key:
key0
=
new
GlobalKey
(),
children:
<
Widget
>[
new
Container
(),
new
Container
(
key:
key1
=
new
GlobalKey
()),
new
Container
(
child:
new
Container
()),
new
Container
(
key:
key2
=
new
GlobalKey
()),
new
Container
(),
],
));
MultiChildRenderObjectElement
element
=
key0
.
currentContext
;
expect
(
element
.
children
.
map
((
Element
element
)
=>
element
.
widget
.
key
),
// ignore: INVALID_USE_OF_PROTECTED_MEMBER
<
Key
>[
null
,
key1
,
null
,
key2
,
null
],
);
});
}
}
packages/flutter/test/widgets/overscroll_indicator_test.dart
0 → 100644
View file @
63160b3d
// Copyright 2017 The Chromium 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:math'
as
math
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/rendering.dart'
;
import
'../rendering/mock_canvas.dart'
;
final
Matcher
doesNotOverscroll
=
isNot
(
paints
..
circle
());
Future
<
Null
>
slowDrag
(
WidgetTester
tester
,
Point
start
,
Offset
offset
)
async
{
TestGesture
gesture
=
await
tester
.
startGesture
(
start
);
for
(
int
index
=
0
;
index
<
10
;
index
+=
1
)
{
await
gesture
.
moveBy
(
offset
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
20
));
}
await
gesture
.
up
();
}
void
main
(
)
{
testWidgets
(
'Overscroll indicator color'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
Scrollable2
(
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
2000.0
)),
],
),
);
RenderObject
painter
=
tester
.
renderObject
(
find
.
byType
(
CustomPaint
));
expect
(
painter
,
doesNotOverscroll
);
// the scroll gesture from tester.scroll happens in zero time, so nothing should appear:
await
tester
.
scroll
(
find
.
byType
(
Scrollable2
),
const
Offset
(
0.0
,
100.0
));
expect
(
painter
,
doesNotOverscroll
);
await
tester
.
pump
();
// allow the ticker to register itself
expect
(
painter
,
doesNotOverscroll
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
// animate
expect
(
painter
,
doesNotOverscroll
);
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Point
(
200.0
,
200.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
// animate
expect
(
painter
,
doesNotOverscroll
);
await
gesture
.
up
();
expect
(
painter
,
doesNotOverscroll
);
await
slowDrag
(
tester
,
const
Point
(
200.0
,
200.0
),
const
Offset
(
0.0
,
5.0
));
expect
(
painter
,
paints
..
circle
(
color:
const
Color
(
0x0DFFFFFF
)));
await
tester
.
pumpUntilNoTransientCallbacks
(
const
Duration
(
seconds:
1
));
expect
(
painter
,
doesNotOverscroll
);
});
testWidgets
(
'Overscroll indicator changes side when you drag on the other side'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
Scrollable2
(
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
2000.0
)),
],
),
);
RenderObject
painter
=
tester
.
renderObject
(
find
.
byType
(
CustomPaint
));
await
slowDrag
(
tester
,
const
Point
(
400.0
,
200.0
),
const
Offset
(
0.0
,
10.0
));
expect
(
painter
,
paints
..
circle
(
x:
400.0
));
await
slowDrag
(
tester
,
const
Point
(
100.0
,
200.0
),
const
Offset
(
0.0
,
10.0
));
expect
(
painter
,
paints
..
something
((
Symbol
method
,
List
<
dynamic
>
arguments
)
{
if
(
method
!=
#drawCircle
)
return
false
;
final
Point
center
=
arguments
[
0
];
if
(
center
.
x
<
400.0
)
return
true
;
throw
'Dragging on left hand side did not overscroll on left hand side.'
;
}));
await
slowDrag
(
tester
,
const
Point
(
700.0
,
200.0
),
const
Offset
(
0.0
,
10.0
));
expect
(
painter
,
paints
..
something
((
Symbol
method
,
List
<
dynamic
>
arguments
)
{
if
(
method
!=
#drawCircle
)
return
false
;
final
Point
center
=
arguments
[
0
];
if
(
center
.
x
>
400.0
)
return
true
;
throw
'Dragging on right hand side did not overscroll on right hand side.'
;
}));
await
tester
.
pumpUntilNoTransientCallbacks
(
const
Duration
(
seconds:
1
));
expect
(
painter
,
doesNotOverscroll
);
});
testWidgets
(
'Overscroll indicator changes side when you shift sides'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
Scrollable2
(
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
2000.0
)),
],
),
);
RenderObject
painter
=
tester
.
renderObject
(
find
.
byType
(
CustomPaint
));
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Point
(
300.0
,
200.0
));
await
gesture
.
moveBy
(
const
Offset
(
0.0
,
10.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
20
));
double
oldX
=
0.0
;
for
(
int
index
=
0
;
index
<
10
;
index
+=
1
)
{
await
gesture
.
moveBy
(
const
Offset
(
50.0
,
50.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
20
));
expect
(
painter
,
paints
..
something
((
Symbol
method
,
List
<
dynamic
>
arguments
)
{
if
(
method
!=
#drawCircle
)
return
false
;
final
Point
center
=
arguments
[
0
];
if
(
center
.
x
<=
oldX
)
throw
'Sliding to the right did not make the center of the radius slide to the right.'
;
oldX
=
center
.
x
;
return
true
;
}));
}
await
gesture
.
up
();
await
tester
.
pumpUntilNoTransientCallbacks
(
const
Duration
(
seconds:
1
));
expect
(
painter
,
doesNotOverscroll
);
});
group
(
'Flipping direction of scrollable doesn
\'
t change overscroll behavior'
,
()
{
testWidgets
(
'down'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
Scrollable2
(
axisDirection:
AxisDirection
.
down
,
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
20.0
)),
],
),
);
RenderObject
painter
=
tester
.
renderObject
(
find
.
byType
(
CustomPaint
));
await
slowDrag
(
tester
,
const
Point
(
200.0
,
200.0
),
const
Offset
(
0.0
,
5.0
));
expect
(
painter
,
paints
..
save
()..
circle
()..
restore
()..
save
()..
scale
(
y:
-
1.0
)..
restore
()..
restore
());
await
tester
.
pumpUntilNoTransientCallbacks
(
const
Duration
(
seconds:
1
));
expect
(
painter
,
doesNotOverscroll
);
});
testWidgets
(
'up'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
Scrollable2
(
axisDirection:
AxisDirection
.
up
,
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
20.0
)),
],
),
);
RenderObject
painter
=
tester
.
renderObject
(
find
.
byType
(
CustomPaint
));
await
slowDrag
(
tester
,
const
Point
(
200.0
,
200.0
),
const
Offset
(
0.0
,
5.0
));
expect
(
painter
,
paints
..
save
()..
scale
(
y:
-
1.0
)..
restore
()..
save
()..
circle
()..
restore
()..
restore
());
await
tester
.
pumpUntilNoTransientCallbacks
(
const
Duration
(
seconds:
1
));
expect
(
painter
,
doesNotOverscroll
);
});
});
testWidgets
(
'Overscroll in both directions'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
Scrollable2
(
axisDirection:
AxisDirection
.
down
,
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
20.0
)),
],
),
);
RenderObject
painter
=
tester
.
renderObject
(
find
.
byType
(
CustomPaint
));
await
slowDrag
(
tester
,
const
Point
(
200.0
,
200.0
),
const
Offset
(
0.0
,
5.0
));
expect
(
painter
,
paints
..
circle
());
expect
(
painter
,
isNot
(
paints
..
circle
()..
circle
()));
await
slowDrag
(
tester
,
const
Point
(
200.0
,
200.0
),
const
Offset
(
0.0
,
-
5.0
));
expect
(
painter
,
paints
..
circle
()..
circle
());
await
tester
.
pumpUntilNoTransientCallbacks
(
const
Duration
(
seconds:
1
));
expect
(
painter
,
doesNotOverscroll
);
});
testWidgets
(
'Overscroll horizontally'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
Scrollable2
(
axisDirection:
AxisDirection
.
right
,
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
20.0
)),
],
),
);
RenderObject
painter
=
tester
.
renderObject
(
find
.
byType
(
CustomPaint
));
await
slowDrag
(
tester
,
const
Point
(
200.0
,
200.0
),
const
Offset
(
5.0
,
0.0
));
expect
(
painter
,
paints
..
rotate
(
angle:
-
math
.
PI
/
2.0
)..
circle
()..
scale
(
y:
-
1.0
));
expect
(
painter
,
isNot
(
paints
..
circle
()..
circle
()));
await
slowDrag
(
tester
,
const
Point
(
200.0
,
200.0
),
const
Offset
(-
5.0
,
0.0
));
expect
(
painter
,
paints
..
rotate
(
angle:
-
math
.
PI
/
2.0
)..
circle
()
..
rotate
(
angle:
-
math
.
PI
/
2.0
)..
scale
(
y:
-
1.0
)..
circle
());
await
tester
.
pumpUntilNoTransientCallbacks
(
const
Duration
(
seconds:
1
));
expect
(
painter
,
doesNotOverscroll
);
});
testWidgets
(
'Changing settings'
,
(
WidgetTester
tester
)
async
{
RenderObject
painter
;
await
tester
.
pumpWidget
(
new
Scrollable2
(
axisDirection:
AxisDirection
.
left
,
scrollBehavior:
new
TestScrollBehavior1
(),
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
20.0
)),
],
),
);
painter
=
tester
.
renderObject
(
find
.
byType
(
CustomPaint
));
await
slowDrag
(
tester
,
const
Point
(
200.0
,
200.0
),
const
Offset
(
5.0
,
0.0
));
expect
(
painter
,
paints
..
scale
(
y:
-
1.0
)..
rotate
(
angle:
-
math
.
PI
/
2.0
)..
circle
(
color:
const
Color
(
0x0A00FF00
)));
expect
(
painter
,
isNot
(
paints
..
circle
()..
circle
()));
await
tester
.
pumpUntilNoTransientCallbacks
(
const
Duration
(
seconds:
1
));
await
tester
.
pumpWidget
(
new
Scrollable2
(
axisDirection:
AxisDirection
.
right
,
scrollBehavior:
new
TestScrollBehavior2
(),
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
20.0
)),
],
),
);
painter
=
tester
.
renderObject
(
find
.
byType
(
CustomPaint
));
await
slowDrag
(
tester
,
const
Point
(
200.0
,
200.0
),
const
Offset
(
5.0
,
0.0
));
expect
(
painter
,
paints
..
rotate
(
angle:
-
math
.
PI
/
2.0
)..
circle
(
color:
const
Color
(
0x0A0000FF
))..
scale
(
y:
-
1.0
));
expect
(
painter
,
isNot
(
paints
..
circle
()..
circle
()));
});
}
class
TestScrollBehavior1
extends
ViewportScrollBehavior
{
@override
Color
getGlowColor
(
BuildContext
context
)
{
return
const
Color
(
0xFF00FF00
);
}
}
class
TestScrollBehavior2
extends
ViewportScrollBehavior
{
@override
Color
getGlowColor
(
BuildContext
context
)
{
return
const
Color
(
0xFF0000FF
);
}
}
packages/flutter/test/widgets/scrollable_custom_scroll_behavior_test.dart
0 → 100644
View file @
63160b3d
// Copyright 2017 The Chromium 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
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
class
TestScrollPosition
extends
ScrollPosition
{
TestScrollPosition
(
this
.
extentMultiplier
,
Scrollable2State
state
,
Tolerance
scrollTolerances
,
ScrollPosition
oldPosition
,
)
:
_pixels
=
100.0
,
super
(
state
,
scrollTolerances
,
oldPosition
);
final
double
extentMultiplier
;
double
_min
,
_viewport
,
_max
,
_pixels
;
@override
double
get
pixels
=>
_pixels
;
@override
double
setPixels
(
double
value
)
{
double
oldPixels
=
_pixels
;
_pixels
=
value
;
dispatchNotification
(
activity
.
createScrollUpdateNotification
(
state
,
_pixels
-
oldPixels
));
return
0.0
;
}
@override
void
correctBy
(
double
correction
)
{
_pixels
+=
correction
;
}
@override
void
applyViewportDimension
(
double
viewportDimension
)
{
_viewport
=
viewportDimension
;
super
.
applyViewportDimension
(
viewportDimension
);
}
@override
bool
applyContentDimensions
(
double
minScrollExtent
,
double
maxScrollExtent
)
{
_min
=
minScrollExtent
;
_max
=
maxScrollExtent
;
return
super
.
applyContentDimensions
(
minScrollExtent
,
maxScrollExtent
);
}
@override
ScrollableMetrics
getMetrics
()
{
double
insideExtent
=
_viewport
;
double
beforeExtent
=
_pixels
-
_min
;
double
afterExtent
=
_max
-
_pixels
;
if
(
insideExtent
>
0.0
)
{
return
new
ScrollableMetrics
(
extentBefore:
extentMultiplier
*
beforeExtent
/
insideExtent
,
extentInside:
extentMultiplier
,
extentAfter:
extentMultiplier
*
afterExtent
/
insideExtent
,
);
}
else
{
return
new
ScrollableMetrics
(
extentBefore:
0.0
,
extentInside:
0.0
,
extentAfter:
0.0
,
);
}
}
}
class
TestScrollBehavior
extends
ScrollBehavior2
{
TestScrollBehavior
(
this
.
extentMultiplier
);
final
double
extentMultiplier
;
@override
Widget
wrap
(
BuildContext
context
,
Widget
child
,
AxisDirection
axisDirection
)
=>
child
;
@override
ScrollPosition
createScrollPosition
(
BuildContext
context
,
Scrollable2State
state
,
ScrollPosition
oldPosition
)
{
return
new
TestScrollPosition
(
extentMultiplier
,
state
,
ViewportScrollBehavior
.
defaultScrollTolerances
,
oldPosition
);
}
@override
bool
shouldNotify
(
TestScrollBehavior
oldDelegate
)
{
return
extentMultiplier
!=
oldDelegate
.
extentMultiplier
;
}
}
void
main
(
)
{
testWidgets
(
'Changing the scroll behavior dynamically'
,
(
WidgetTester
tester
)
async
{
GlobalKey
<
Scrollable2State
>
key
=
new
GlobalKey
<
Scrollable2State
>();
await
tester
.
pumpWidget
(
new
Scrollable2
(
key:
key
,
scrollBehavior:
new
TestScrollBehavior
(
1.0
),
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
2000.0
)),
],
));
expect
(
key
.
currentState
.
position
.
getMetrics
().
extentInside
,
1.0
);
await
tester
.
pumpWidget
(
new
Scrollable2
(
key:
key
,
scrollBehavior:
new
TestScrollBehavior
(
2.0
),
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
2000.0
)),
],
));
expect
(
key
.
currentState
.
position
.
getMetrics
().
extentInside
,
2.0
);
});
}
\ No newline at end of file
packages/flutter/test/widgets/scrollable_test.dart
0 → 100644
View file @
63160b3d
// Copyright 2017 The Chromium 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
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
Future
<
Null
>
pumpTest
(
WidgetTester
tester
,
TargetPlatform
platform
)
async
{
await
tester
.
pumpWidget
(
new
MaterialApp
(
theme:
new
ThemeData
(
platform:
platform
,
),
home:
new
Scrollable2
(
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
2000.0
)),
],
),
));
await
tester
.
pump
(
const
Duration
(
seconds:
5
));
// to let the theme animate
return
null
;
}
const
double
dragOffset
=
200.0
;
double
getScrollOffset
(
WidgetTester
tester
)
{
RenderViewport2
viewport
=
tester
.
renderObject
(
find
.
byType
(
Viewport2
));
return
viewport
.
offset
.
pixels
;
}
void
resetScrollOffset
(
WidgetTester
tester
)
{
RenderViewport2
viewport
=
tester
.
renderObject
(
find
.
byType
(
Viewport2
));
AbsoluteScrollPosition
position
=
viewport
.
offset
;
position
.
jumpTo
(
0.0
);
}
void
main
(
)
{
testWidgets
(
'Flings on different platforms'
,
(
WidgetTester
tester
)
async
{
await
pumpTest
(
tester
,
TargetPlatform
.
android
);
await
tester
.
fling
(
find
.
byType
(
Viewport2
),
const
Offset
(
0.0
,
-
dragOffset
),
1000.0
);
expect
(
getScrollOffset
(
tester
),
dragOffset
);
await
tester
.
pump
();
// trigger fling
expect
(
getScrollOffset
(
tester
),
dragOffset
);
await
tester
.
pump
(
const
Duration
(
seconds:
5
));
final
double
result1
=
getScrollOffset
(
tester
);
resetScrollOffset
(
tester
);
await
pumpTest
(
tester
,
TargetPlatform
.
iOS
);
await
tester
.
fling
(
find
.
byType
(
Viewport2
),
const
Offset
(
0.0
,
-
dragOffset
),
1000.0
);
expect
(
getScrollOffset
(
tester
),
dragOffset
);
await
tester
.
pump
();
// trigger fling
expect
(
getScrollOffset
(
tester
),
dragOffset
);
await
tester
.
pump
(
const
Duration
(
seconds:
5
));
final
double
result2
=
getScrollOffset
(
tester
);
expect
(
result1
,
lessThan
(
result2
));
// iOS (result2) is slipperier than Android (result1)
});
testWidgets
(
'Flings on different platforms'
,
(
WidgetTester
tester
)
async
{
await
pumpTest
(
tester
,
TargetPlatform
.
iOS
);
await
tester
.
fling
(
find
.
byType
(
Viewport2
),
const
Offset
(
0.0
,
-
dragOffset
),
1000.0
);
expect
(
getScrollOffset
(
tester
),
dragOffset
);
await
tester
.
pump
();
// trigger fling
expect
(
getScrollOffset
(
tester
),
dragOffset
);
await
tester
.
pump
(
const
Duration
(
seconds:
5
));
final
double
result1
=
getScrollOffset
(
tester
);
resetScrollOffset
(
tester
);
await
pumpTest
(
tester
,
TargetPlatform
.
android
);
await
tester
.
fling
(
find
.
byType
(
Viewport2
),
const
Offset
(
0.0
,
-
dragOffset
),
1000.0
);
expect
(
getScrollOffset
(
tester
),
dragOffset
);
await
tester
.
pump
();
// trigger fling
expect
(
getScrollOffset
(
tester
),
dragOffset
);
await
tester
.
pump
(
const
Duration
(
seconds:
5
));
final
double
result2
=
getScrollOffset
(
tester
);
expect
(
result1
,
greaterThan
(
result2
));
// iOS (result1) is slipperier than Android (result2)
});
}
\ No newline at end of file
packages/flutter/test/widgets/slivers_protocol_test.dart
0 → 100644
View file @
63160b3d
// Copyright 2016 The Chromium 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:math'
as
math
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
void
verifyPaintPosition
(
GlobalKey
key
,
Offset
ideal
)
{
RenderObject
target
=
key
.
currentContext
.
findRenderObject
();
expect
(
target
.
parent
,
new
isInstanceOf
<
RenderViewport2
>());
SliverPhysicalParentData
parentData
=
target
.
parentData
;
Offset
actual
=
parentData
.
paintOffset
;
expect
(
actual
,
ideal
);
}
void
main
(
)
{
testWidgets
(
'Sliver protocol'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
Scrollable2State
>
scrollableKey
=
new
GlobalKey
<
Scrollable2State
>();
GlobalKey
key1
,
key2
,
key3
,
key4
,
key5
;
await
tester
.
pumpWidget
(
new
Scrollable2
(
key:
scrollableKey
,
axisDirection:
AxisDirection
.
down
,
children:
<
Widget
>[
new
BigSliver
(
key:
key1
=
new
GlobalKey
()),
new
OverlappingSliver
(
key:
key2
=
new
GlobalKey
()),
new
OverlappingSliver
(
key:
key3
=
new
GlobalKey
()),
new
BigSliver
(
key:
key4
=
new
GlobalKey
()),
new
BigSliver
(
key:
key5
=
new
GlobalKey
()),
],
),
);
AbsoluteScrollPosition
position
=
scrollableKey
.
currentState
.
position
;
final
double
max
=
RenderBigSliver
.
height
*
3.0
+
(
RenderOverlappingSliver
.
totalHeight
)
*
2.0
-
600.0
;
// 600 is the height of the test viewport
assert
(
max
<
10000.0
);
expect
(
max
,
1450.0
);
expect
(
position
.
pixels
,
0.0
);
expect
(
position
.
minScrollExtent
,
0.0
);
expect
(
position
.
maxScrollExtent
,
max
);
position
.
animate
(
to:
10000.0
,
curve:
Curves
.
linear
,
duration:
const
Duration
(
minutes:
1
));
await
tester
.
pumpUntilNoTransientCallbacks
(
const
Duration
(
milliseconds:
10
));
expect
(
position
.
pixels
,
max
);
expect
(
position
.
minScrollExtent
,
0.0
);
expect
(
position
.
maxScrollExtent
,
max
);
verifyPaintPosition
(
key1
,
new
Offset
(
0.0
,
0.0
));
verifyPaintPosition
(
key2
,
new
Offset
(
0.0
,
0.0
));
verifyPaintPosition
(
key3
,
new
Offset
(
0.0
,
0.0
));
verifyPaintPosition
(
key4
,
new
Offset
(
0.0
,
0.0
));
verifyPaintPosition
(
key5
,
new
Offset
(
0.0
,
50.0
));
});
}
class
RenderBigSliver
extends
RenderSliver
{
static
const
double
height
=
550.0
;
double
get
paintExtent
=>
(
height
-
constraints
.
scrollOffset
).
clamp
(
0.0
,
constraints
.
remainingPaintExtent
);
@override
void
performLayout
()
{
geometry
=
new
SliverGeometry
(
scrollExtent:
height
,
paintExtent:
paintExtent
,
maxPaintExtent:
height
,
);
}
}
class
BigSliver
extends
LeafRenderObjectWidget
{
BigSliver
({
Key
key
})
:
super
(
key:
key
);
@override
RenderBigSliver
createRenderObject
(
BuildContext
context
)
{
return
new
RenderBigSliver
();
}
}
class
RenderOverlappingSliver
extends
RenderSliver
{
static
const
double
totalHeight
=
200.0
;
static
const
double
fixedHeight
=
100.0
;
double
get
paintExtent
{
return
math
.
min
(
math
.
max
(
fixedHeight
,
totalHeight
-
constraints
.
scrollOffset
,
),
constraints
.
remainingPaintExtent
,
);
}
double
get
layoutExtent
{
return
(
totalHeight
-
constraints
.
scrollOffset
).
clamp
(
0.0
,
constraints
.
remainingPaintExtent
);
}
@override
void
performLayout
()
{
geometry
=
new
SliverGeometry
(
scrollExtent:
totalHeight
,
paintExtent:
paintExtent
,
layoutExtent:
layoutExtent
,
maxPaintExtent:
totalHeight
,
);
}
}
class
OverlappingSliver
extends
LeafRenderObjectWidget
{
OverlappingSliver
({
Key
key
})
:
super
(
key:
key
);
@override
RenderOverlappingSliver
createRenderObject
(
BuildContext
context
)
{
return
new
RenderOverlappingSliver
();
}
}
packages/flutter/test/widgets/slivers_test.dart
0 → 100644
View file @
63160b3d
// Copyright 2016 The Chromium 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
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/rendering.dart'
;
Future
<
Null
>
test
(
WidgetTester
tester
,
double
offset
,
{
double
anchor:
0.0
})
{
return
tester
.
pumpWidget
(
new
Viewport2
(
anchor:
anchor
/
600.0
,
offset:
new
ViewportOffset
.
fixed
(
offset
),
children:
<
Widget
>[
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
400.0
)),
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
400.0
)),
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
400.0
)),
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
400.0
)),
new
SliverToBoxAdapter
(
child:
new
SizedBox
(
height:
400.0
)),
],
));
}
void
verify
(
WidgetTester
tester
,
List
<
Point
>
idealPositions
,
List
<
bool
>
idealVisibles
)
{
List
<
Point
>
actualPositions
=
tester
.
renderObjectList
/*<RenderBox>*/
(
find
.
byType
(
SizedBox
)).
map
/*<Point>*/
(
(
RenderBox
target
)
=>
target
.
localToGlobal
(
const
Point
(
0.0
,
0.0
))
).
toList
();
List
<
bool
>
actualVisibles
=
tester
.
renderObjectList
/*<RenderSliverToBoxAdapter>*/
(
find
.
byType
(
SliverToBoxAdapter
)).
map
/*<bool>*/
(
(
RenderSliverToBoxAdapter
target
)
=>
target
.
geometry
.
visible
).
toList
();
expect
(
actualPositions
,
equals
(
idealPositions
));
expect
(
actualVisibles
,
equals
(
idealVisibles
));
}
void
main
(
)
{
testWidgets
(
'Viewport2 basic test'
,
(
WidgetTester
tester
)
async
{
await
test
(
tester
,
0.0
);
expect
(
tester
.
renderObject
/*<RenderBox>*/
(
find
.
byType
(
Viewport2
)).
size
,
equals
(
const
Size
(
800.0
,
600.0
)));
verify
(
tester
,
<
Point
>[
const
Point
(
0.0
,
0.0
),
const
Point
(
0.0
,
400.0
),
const
Point
(
0.0
,
600.0
),
const
Point
(
0.0
,
600.0
),
const
Point
(
0.0
,
600.0
),
],
<
bool
>[
true
,
true
,
false
,
false
,
false
]);
await
test
(
tester
,
200.0
);
verify
(
tester
,
<
Point
>[
const
Point
(
0.0
,
-
200.0
),
const
Point
(
0.0
,
200.0
),
const
Point
(
0.0
,
600.0
),
const
Point
(
0.0
,
600.0
),
const
Point
(
0.0
,
600.0
),
],
<
bool
>[
true
,
true
,
false
,
false
,
false
]);
await
test
(
tester
,
600.0
);
verify
(
tester
,
<
Point
>[
const
Point
(
0.0
,
-
600.0
),
const
Point
(
0.0
,
-
200.0
),
const
Point
(
0.0
,
200.0
),
const
Point
(
0.0
,
600.0
),
const
Point
(
0.0
,
600.0
),
],
<
bool
>[
false
,
true
,
true
,
false
,
false
]);
await
test
(
tester
,
900.0
);
verify
(
tester
,
<
Point
>[
const
Point
(
0.0
,
-
900.0
),
const
Point
(
0.0
,
-
500.0
),
const
Point
(
0.0
,
-
100.0
),
const
Point
(
0.0
,
300.0
),
const
Point
(
0.0
,
600.0
),
],
<
bool
>[
false
,
false
,
true
,
true
,
false
]);
});
testWidgets
(
'Viewport2 anchor test'
,
(
WidgetTester
tester
)
async
{
await
test
(
tester
,
0.0
,
anchor:
100.0
);
expect
(
tester
.
renderObject
/*<RenderBox>*/
(
find
.
byType
(
Viewport2
)).
size
,
equals
(
const
Size
(
800.0
,
600.0
)));
verify
(
tester
,
<
Point
>[
const
Point
(
0.0
,
100.0
),
const
Point
(
0.0
,
500.0
),
const
Point
(
0.0
,
600.0
),
const
Point
(
0.0
,
600.0
),
const
Point
(
0.0
,
600.0
),
],
<
bool
>[
true
,
true
,
false
,
false
,
false
]);
await
test
(
tester
,
200.0
,
anchor:
100.0
);
verify
(
tester
,
<
Point
>[
const
Point
(
0.0
,
-
100.0
),
const
Point
(
0.0
,
300.0
),
const
Point
(
0.0
,
600.0
),
const
Point
(
0.0
,
600.0
),
const
Point
(
0.0
,
600.0
),
],
<
bool
>[
true
,
true
,
false
,
false
,
false
]);
await
test
(
tester
,
600.0
,
anchor:
100.0
);
verify
(
tester
,
<
Point
>[
const
Point
(
0.0
,
-
500.0
),
const
Point
(
0.0
,
-
100.0
),
const
Point
(
0.0
,
300.0
),
const
Point
(
0.0
,
600.0
),
const
Point
(
0.0
,
600.0
),
],
<
bool
>[
false
,
true
,
true
,
false
,
false
]);
await
test
(
tester
,
900.0
,
anchor:
100.0
);
verify
(
tester
,
<
Point
>[
const
Point
(
0.0
,
-
800.0
),
const
Point
(
0.0
,
-
400.0
),
const
Point
(
0.0
,
0.0
),
const
Point
(
0.0
,
400.0
),
const
Point
(
0.0
,
600.0
),
],
<
bool
>[
false
,
false
,
true
,
true
,
false
]);
});
}
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