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
2656006c
Commit
2656006c
authored
Aug 02, 2016
by
Hans Muller
Committed by
GitHub
Aug 02, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
OverscrollIndicator tracks horizontal drag input, etc (#5183)
parent
64fa825b
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
134 additions
and
63 deletions
+134
-63
drag.dart
packages/flutter/lib/src/gestures/drag.dart
+7
-2
overscroll_indicator.dart
packages/flutter/lib/src/material/overscroll_indicator.dart
+56
-42
scrollable.dart
packages/flutter/lib/src/widgets/scrollable.dart
+58
-19
scroll_notification_test.dart
packages/flutter/test/widget/scroll_notification_test.dart
+13
-0
No files found.
packages/flutter/lib/src/gestures/drag.dart
View file @
2656006c
...
...
@@ -56,7 +56,8 @@ class DragUpdateDetails {
/// coordinates of [delta] and the other coordinate must be zero.
DragUpdateDetails
({
this
.
delta
:
Offset
.
zero
,
this
.
primaryDelta
this
.
primaryDelta
,
this
.
globalPosition
})
{
assert
(
primaryDelta
==
null
||
(
primaryDelta
==
delta
.
dx
&&
delta
.
dy
==
0.0
)
...
...
@@ -79,6 +80,9 @@ class DragUpdateDetails {
/// respectively). Otherwise, if the [GestureDragUpdateCallback] is for a
/// two-dimensional drag (e.g., a pan), then this value is null.
final
double
primaryDelta
;
/// The pointer's global position.
final
Point
globalPosition
;
}
/// Signature for when a pointer that is in contact with the screen and moving
...
...
@@ -184,7 +188,8 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
if
(
onUpdate
!=
null
)
{
onUpdate
(
new
DragUpdateDetails
(
delta:
_getDeltaForDetails
(
delta
),
primaryDelta:
_getPrimaryDeltaForDetails
(
delta
)
primaryDelta:
_getPrimaryDeltaForDetails
(
delta
),
globalPosition:
event
.
position
));
}
}
else
{
...
...
packages/flutter/lib/src/material/overscroll_indicator.dart
View file @
2656006c
...
...
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import
'dart:async'
show
Timer
;
import
'dart:math'
as
math
;
import
'package:flutter/widgets.dart'
;
...
...
@@ -12,67 +13,66 @@ const double _kMinIndicatorExtent = 0.0;
const
double
_kMaxIndicatorExtent
=
64.0
;
const
double
_kMinIndicatorOpacity
=
0.0
;
const
double
_kMaxIndicatorOpacity
=
0.25
;
const
Duration
_kIndicatorHideDuration
=
const
Duration
(
milliseconds:
200
);
const
Duration
_kIndicatorTimeoutDuration
=
const
Duration
(
milliseconds:
500
);
final
Tween
<
double
>
_kIndicatorOpacity
=
new
Tween
<
double
>(
begin:
0.0
,
end:
0.3
);
// If an overscroll gesture lasts longer than this the hide timer will
// cause the indicator to fade-out.
const
Duration
_kTimeoutDuration
=
const
Duration
(
milliseconds:
500
);
// Fade-out duration if the fade-out was triggered by the timer.
const
Duration
_kTimeoutHideDuration
=
const
Duration
(
milliseconds:
2000
);
// Fade-out duration if the fade-out was triggered by an input gesture.
const
Duration
_kNormalHideDuration
=
const
Duration
(
milliseconds:
600
);
class
_Painter
extends
CustomPainter
{
_Painter
({
this
.
scrollDirection
,
this
.
extent
,
// Indicator width or height, per scrollDirection.
this
.
dragPosition
,
this
.
isLeading
,
// Similarly true if the indicator appears at the top/left.
this
.
color
});
// See EdgeEffect setSize() in https://github.com/android
static
final
double
_kSizeToRadius
=
0.75
/
math
.
sin
(
math
.
PI
/
6.0
);
final
Axis
scrollDirection
;
final
double
extent
;
final
bool
isLeading
;
final
Color
color
;
final
Point
dragPosition
;
void
paintIndicator
(
Canvas
canvas
,
Size
size
)
{
final
double
rectBias
=
extent
/
2.0
;
final
double
arcBias
=
extent
*
0.66
;
final
Paint
paint
=
new
Paint
()..
color
=
color
;
final
double
width
=
size
.
width
;
final
double
height
=
size
.
height
;
final
Path
path
=
new
Path
();
switch
(
scrollDirection
)
{
case
Axis
.
vertical
:
final
double
width
=
size
.
width
;
if
(
isLeading
)
{
path
.
moveTo
(
0.0
,
0.0
);
path
.
relativeLineTo
(
width
,
0.0
);
path
.
relativeLineTo
(
0.0
,
rectBias
);
path
.
relativeCubicTo
(
width
*
-
0.25
,
arcBias
,
width
*
-
0.75
,
arcBias
,
-
width
,
0.0
);
}
else
{
path
.
moveTo
(
0.0
,
size
.
height
);
path
.
relativeLineTo
(
width
,
0.0
);
path
.
relativeLineTo
(
0.0
,
-
rectBias
);
path
.
relativeCubicTo
(
width
*
-
0.25
,
-
arcBias
,
width
*
-
0.75
,
-
arcBias
,
-
width
,
0.0
);
}
final
double
radius
=
width
*
_kSizeToRadius
;
final
double
centerX
=
width
/
2.0
;
final
double
centerY
=
isLeading
?
extent
-
radius
:
height
-
extent
+
radius
;
final
double
eventX
=
dragPosition
?.
x
??
0.0
;
final
double
biasX
=
(
0.5
-
(
1.0
-
eventX
/
width
))
*
centerX
;
canvas
.
drawCircle
(
new
Point
(
centerX
+
biasX
,
centerY
),
radius
,
paint
);
break
;
case
Axis
.
horizontal
:
final
double
height
=
size
.
height
;
if
(
isLeading
)
{
path
.
moveTo
(
0.0
,
0.0
);
path
.
relativeLineTo
(
0.0
,
height
);
path
.
relativeLineTo
(
rectBias
,
0.0
);
path
.
relativeCubicTo
(
arcBias
,
height
*
-
0.25
,
arcBias
,
height
*
-
0.75
,
0.0
,
-
height
);
}
else
{
path
.
moveTo
(
size
.
width
,
0.0
);
path
.
relativeLineTo
(
0.0
,
height
);
path
.
relativeLineTo
(-
rectBias
,
0.0
);
path
.
relativeCubicTo
(-
arcBias
,
height
*
-
0.25
,
-
arcBias
,
height
*
-
0.75
,
0.0
,
-
height
);
}
final
double
radius
=
height
*
_kSizeToRadius
;
final
double
centerX
=
isLeading
?
extent
-
radius
:
width
-
extent
+
radius
;
final
double
centerY
=
height
/
2.0
;
final
double
eventY
=
dragPosition
?.
y
??
0.0
;
final
double
biasY
=
(
0.5
-
(
1.0
-
eventY
/
height
))
*
centerY
;
canvas
.
drawCircle
(
new
Point
(
centerX
,
centerY
+
biasY
),
radius
,
paint
);
break
;
}
path
.
close
();
final
Paint
paint
=
new
Paint
()..
color
=
color
;
canvas
.
drawPath
(
path
,
paint
);
}
@override
void
paint
(
Canvas
canvas
,
Size
size
)
{
if
(
color
.
alpha
==
0
)
if
(
color
.
alpha
==
0
||
size
.
isEmpty
)
return
;
paintIndicator
(
canvas
,
size
);
}
...
...
@@ -120,19 +120,23 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> {
final
AnimationController
_extentAnimation
=
new
AnimationController
(
lowerBound:
_kMinIndicatorExtent
,
upperBound:
_kMaxIndicatorExtent
,
duration:
_k
Indicator
HideDuration
duration:
_k
Normal
HideDuration
);
bool
_scrollUnderway
=
false
;
Timer
_hideTimer
;
Axis
_scrollDirection
;
double
_scrollOffset
;
double
_minScrollOffset
;
double
_maxScrollOffset
;
Point
_dragPosition
;
void
_hide
()
{
void
_hide
([
Duration
duration
=
_kTimeoutHideDuration
])
{
_scrollUnderway
=
false
;
_hideTimer
?.
cancel
();
_hideTimer
=
null
;
if
(!
_extentAnimation
.
isAnimating
)
{
_extentAnimation
.
duration
=
duration
;
_extentAnimation
.
reverse
();
}
}
...
...
@@ -148,18 +152,24 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> {
}
void
_onScrollStarted
(
ScrollableState
scrollable
)
{
assert
(
_scrollUnderway
==
false
);
_scrollUnderway
=
true
;
_updateState
(
scrollable
);
}
void
_onScrollUpdated
(
ScrollableState
scrollable
)
{
void
_onScrollUpdated
(
ScrollableState
scrollable
,
DragUpdateDetails
details
)
{
if
(!
_scrollUnderway
)
// The hide timer has run.
return
;
final
double
value
=
scrollable
.
scrollOffset
;
if
(
_isOverscroll
(
value
))
{
_refreshHideTimer
();
// Hide the indicator as soon as user starts scrolling in the reverse direction of overscroll.
if
(
_isReverseScroll
(
value
))
{
_hide
();
_hide
(
_kNormalHideDuration
);
}
else
{
// Changing the animation's value causes an implicit setState().
_dragPosition
=
details
?.
globalPosition
??
Point
.
origin
;
_extentAnimation
.
value
=
value
<
_minScrollOffset
?
_minScrollOffset
-
value
:
value
-
_maxScrollOffset
;
}
}
...
...
@@ -167,13 +177,16 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> {
}
void
_onScrollEnded
(
ScrollableState
scrollable
)
{
if
(!
_scrollUnderway
)
// The hide timer has run.
return
;
_updateState
(
scrollable
);
_hide
();
_hide
(
_kNormalHideDuration
);
}
void
_refreshHideTimer
()
{
_hideTimer
?.
cancel
();
_hideTimer
=
new
Timer
(
_k
Indicator
TimeoutDuration
,
_hide
);
_hideTimer
=
new
Timer
(
_kTimeoutDuration
,
_hide
);
}
bool
_isOverscroll
(
double
scrollOffset
)
{
...
...
@@ -183,7 +196,7 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> {
bool
_isReverseScroll
(
double
scrollOffset
)
{
final
double
delta
=
_scrollOffset
-
scrollOffset
;
return
scrollOffset
<
_minScrollOffset
?
delta
<
0
:
delta
>
0
;
return
scrollOffset
<
_minScrollOffset
?
delta
<
0
.0
:
delta
>
0.
0
;
}
bool
_handleScrollNotification
(
ScrollNotification
notification
)
{
...
...
@@ -200,7 +213,7 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> {
_onScrollStarted
(
scrollable
);
break
;
case
ScrollNotificationKind
.
updated
:
_onScrollUpdated
(
scrollable
);
_onScrollUpdated
(
scrollable
,
notification
.
dragUpdateDetails
);
break
;
case
ScrollNotificationKind
.
ended
:
_onScrollEnded
(
scrollable
);
...
...
@@ -236,6 +249,7 @@ class _OverscrollIndicatorState extends State<OverscrollIndicator> {
foregroundPainter:
_scrollDirection
==
null
?
null
:
new
_Painter
(
scrollDirection:
_scrollDirection
,
extent:
_extentAnimation
.
value
,
dragPosition:
_dragPosition
,
isLeading:
_scrollOffset
<
_minScrollOffset
,
color:
_indicatorColor
),
...
...
packages/flutter/lib/src/widgets/scrollable.dart
View file @
2656006c
...
...
@@ -365,7 +365,7 @@ class ScrollableState<T extends Scrollable> extends State<T> {
_setScrollOffset
(
_controller
.
value
);
}
void
_setScrollOffset
(
double
newScrollOffset
)
{
void
_setScrollOffset
(
double
newScrollOffset
,
{
DragUpdateDetails
details
}
)
{
if
(
_scrollOffset
==
newScrollOffset
)
return
;
setState
(()
{
...
...
@@ -374,6 +374,11 @@ class ScrollableState<T extends Scrollable> extends State<T> {
PageStorage
.
of
(
context
)?.
writeState
(
context
,
_scrollOffset
);
_startScroll
();
dispatchOnScroll
();
new
ScrollNotification
(
scrollable:
this
,
kind:
ScrollNotificationKind
.
updated
,
details:
details
).
dispatch
(
context
);
_endScroll
();
}
...
...
@@ -381,9 +386,13 @@ class ScrollableState<T extends Scrollable> extends State<T> {
///
/// If a non-null [duration] is provided, the widget will animate to the new
/// scroll offset over the given duration with the given curve.
Future
<
Null
>
scrollBy
(
double
scrollDelta
,
{
Duration
duration
,
Curve
curve:
Curves
.
ease
})
{
Future
<
Null
>
scrollBy
(
double
scrollDelta
,
{
Duration
duration
,
Curve
curve:
Curves
.
ease
,
DragUpdateDetails
details
})
{
double
newScrollOffset
=
scrollBehavior
.
applyCurve
(
_scrollOffset
,
scrollDelta
);
return
scrollTo
(
newScrollOffset
,
duration:
duration
,
curve:
curve
);
return
scrollTo
(
newScrollOffset
,
duration:
duration
,
curve:
curve
,
details:
details
);
}
/// Scroll this widget to the given scroll offset.
...
...
@@ -394,13 +403,17 @@ class ScrollableState<T extends Scrollable> extends State<T> {
/// This function does not accept a zero duration. To jump-scroll to
/// the new offset, do not provide a duration, rather than providing
/// a zero duration.
Future
<
Null
>
scrollTo
(
double
newScrollOffset
,
{
Duration
duration
,
Curve
curve:
Curves
.
ease
})
{
Future
<
Null
>
scrollTo
(
double
newScrollOffset
,
{
Duration
duration
,
Curve
curve:
Curves
.
ease
,
DragUpdateDetails
details
})
{
if
(
newScrollOffset
==
_scrollOffset
)
return
new
Future
<
Null
>.
value
();
if
(
duration
==
null
)
{
_stop
();
_setScrollOffset
(
newScrollOffset
);
_setScrollOffset
(
newScrollOffset
,
details:
details
);
return
new
Future
<
Null
>.
value
();
}
...
...
@@ -412,7 +425,9 @@ class ScrollableState<T extends Scrollable> extends State<T> {
_stop
();
_controller
.
value
=
scrollOffset
;
_startScroll
();
return
_controller
.
animateTo
(
newScrollOffset
,
duration:
duration
,
curve:
curve
).
then
(
_endScroll
);
return
_controller
.
animateTo
(
newScrollOffset
,
duration:
duration
,
curve:
curve
).
then
((
Null
_
)
{
_endScroll
();
});
}
/// Update any in-progress scrolling physics to account for new scroll behavior.
...
...
@@ -481,7 +496,9 @@ class ScrollableState<T extends Scrollable> extends State<T> {
if
(
_simulation
==
null
)
return
new
Future
<
Null
>.
value
();
_startScroll
();
return
_controller
.
animateWith
(
_simulation
).
then
(
_endScroll
);
return
_controller
.
animateWith
(
_simulation
).
then
((
Null
_
)
{
_endScroll
();
});
}
/// Whether this scrollable should attempt to snap scroll offsets.
...
...
@@ -549,7 +566,6 @@ class ScrollableState<T extends Scrollable> extends State<T> {
assert
(
_numberOfInProgressScrolls
>
0
);
if
(
config
.
onScroll
!=
null
)
config
.
onScroll
(
_scrollOffset
);
new
ScrollNotification
(
this
,
ScrollNotificationKind
.
updated
).
dispatch
(
context
);
}
void
_handleDragDown
(
_
)
{
...
...
@@ -563,13 +579,19 @@ class ScrollableState<T extends Scrollable> extends State<T> {
}
void
_handleDragStart
(
DragStartDetails
details
)
{
_startScroll
();
_startScroll
(
details:
details
);
}
void
_startScroll
()
{
void
_startScroll
(
{
DragStartDetails
details
}
)
{
_numberOfInProgressScrolls
+=
1
;
if
(
_numberOfInProgressScrolls
==
1
)
if
(
_numberOfInProgressScrolls
==
1
)
{
dispatchOnScrollStart
();
new
ScrollNotification
(
scrollable:
this
,
kind:
ScrollNotificationKind
.
started
,
details:
details
).
dispatch
(
context
);
}
}
/// Calls the onScrollStart callback.
...
...
@@ -579,25 +601,32 @@ class ScrollableState<T extends Scrollable> extends State<T> {
assert
(
_numberOfInProgressScrolls
==
1
);
if
(
config
.
onScrollStart
!=
null
)
config
.
onScrollStart
(
_scrollOffset
);
new
ScrollNotification
(
this
,
ScrollNotificationKind
.
started
).
dispatch
(
context
);
}
void
_handleDragUpdate
(
DragUpdateDetails
details
)
{
scrollBy
(
pixelOffsetToScrollOffset
(
details
.
primaryDelta
));
scrollBy
(
pixelOffsetToScrollOffset
(
details
.
primaryDelta
)
,
details:
details
);
}
void
_handleDragEnd
(
DragEndDetails
details
)
{
final
double
scrollVelocity
=
pixelDeltaToScrollOffset
(
details
.
velocity
.
pixelsPerSecond
);
fling
(
scrollVelocity
).
then
(
_endScroll
);
fling
(
scrollVelocity
).
then
((
Null
_
)
{
_endScroll
(
details:
details
);
});
}
Null
_endScroll
([
Null
_
]
)
{
void
_endScroll
({
DragEndDetails
details
}
)
{
_numberOfInProgressScrolls
-=
1
;
if
(
_numberOfInProgressScrolls
==
0
)
{
_simulation
=
null
;
dispatchOnScrollEnd
();
if
(
mounted
)
{
new
ScrollNotification
(
scrollable:
this
,
kind:
ScrollNotificationKind
.
ended
,
details:
details
).
dispatch
(
context
);
}
}
return
null
;
}
/// Calls the dispatchOnScrollEnd callback.
...
...
@@ -607,8 +636,6 @@ class ScrollableState<T extends Scrollable> extends State<T> {
assert
(
_numberOfInProgressScrolls
==
0
);
if
(
config
.
onScrollEnd
!=
null
)
config
.
onScrollEnd
(
_scrollOffset
);
if
(
mounted
)
new
ScrollNotification
(
this
,
ScrollNotificationKind
.
ended
).
dispatch
(
context
);
}
final
GlobalKey
<
RawGestureDetectorState
>
_gestureDetectorKey
=
new
GlobalKey
<
RawGestureDetectorState
>();
...
...
@@ -716,7 +743,14 @@ enum ScrollNotificationKind {
/// * [NotificationListener]
class
ScrollNotification
extends
Notification
{
/// Creates a notification about scrolling.
ScrollNotification
(
this
.
scrollable
,
this
.
kind
);
ScrollNotification
({
this
.
scrollable
,
this
.
kind
,
dynamic
details
})
:
_details
=
details
{
assert
(
scrollable
!=
null
);
assert
(
kind
!=
null
);
assert
(
details
==
null
||
(
kind
==
ScrollNotificationKind
.
started
&&
details
is
DragStartDetails
)
||
(
kind
==
ScrollNotificationKind
.
updated
&&
details
is
DragUpdateDetails
)
||
(
kind
==
ScrollNotificationKind
.
ended
&&
details
is
DragEndDetails
));
}
/// Indicates if we're at the start, middle, or end of a scroll.
final
ScrollNotificationKind
kind
;
...
...
@@ -724,6 +758,11 @@ class ScrollNotification extends Notification {
/// The scrollable that scrolled.
final
ScrollableState
scrollable
;
DragStartDetails
get
dragStartDetails
=>
kind
==
ScrollNotificationKind
.
started
?
_details
:
null
;
DragUpdateDetails
get
dragUpdateDetails
=>
kind
==
ScrollNotificationKind
.
updated
?
_details
:
null
;
DragEndDetails
get
dragEndDetails
=>
kind
==
ScrollNotificationKind
.
ended
?
_details
:
null
;
final
dynamic
_details
;
/// The number of scrollable widgets that have already received this
/// notification. Typically listeners only respond to notifications
/// with depth = 0.
...
...
packages/flutter/test/widget/scroll_notification_test.dart
View file @
2656006c
...
...
@@ -23,16 +23,29 @@ void main() {
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
notification
.
kind
,
equals
(
ScrollNotificationKind
.
started
));
expect
(
notification
.
depth
,
equals
(
0
));
expect
(
notification
.
dragStartDetails
,
isNotNull
);
expect
(
notification
.
dragStartDetails
.
globalPosition
,
equals
(
new
Point
(
100.0
,
100.0
)));
expect
(
notification
.
dragUpdateDetails
,
isNull
);
expect
(
notification
.
dragEndDetails
,
isNull
);
await
gesture
.
moveBy
(
new
Offset
(-
10.0
,
-
10.0
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
notification
.
kind
,
equals
(
ScrollNotificationKind
.
updated
));
expect
(
notification
.
depth
,
equals
(
0
));
expect
(
notification
.
dragStartDetails
,
isNull
);
expect
(
notification
.
dragUpdateDetails
,
isNotNull
);
expect
(
notification
.
dragUpdateDetails
.
globalPosition
,
equals
(
new
Point
(
90.0
,
90.0
)));
expect
(
notification
.
dragUpdateDetails
.
delta
,
equals
(
new
Offset
(
0.0
,
-
10.0
)));
expect
(
notification
.
dragEndDetails
,
isNull
);
await
gesture
.
up
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
notification
.
kind
,
equals
(
ScrollNotificationKind
.
ended
));
expect
(
notification
.
depth
,
equals
(
0
));
expect
(
notification
.
dragStartDetails
,
isNull
);
expect
(
notification
.
dragUpdateDetails
,
isNull
);
expect
(
notification
.
dragEndDetails
,
isNotNull
);
expect
(
notification
.
dragEndDetails
.
velocity
,
equals
(
Velocity
.
zero
));
});
testWidgets
(
'Scroll notifcation depth'
,
(
WidgetTester
tester
)
async
{
...
...
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