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
400136b0
Unverified
Commit
400136b0
authored
Oct 25, 2022
by
Taha Tesser
Committed by
GitHub
Oct 25, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix `Slider` overlay and value indicator interactive behavior on desktop. (#113543)
parent
2a59bd52
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
249 additions
and
9 deletions
+249
-9
slider.dart
packages/flutter/lib/src/material/slider.dart
+42
-6
slider_test.dart
packages/flutter/test/material/slider_test.dart
+207
-3
No files found.
packages/flutter/lib/src/material/slider.dart
View file @
400136b0
...
...
@@ -609,6 +609,7 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
final
double
lerpValue
=
_lerp
(
value
);
if
(
lerpValue
!=
widget
.
value
)
{
widget
.
onChanged
!(
lerpValue
);
_focusNode
?.
requestFocus
();
}
}
...
...
@@ -1090,6 +1091,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
late
TapGestureRecognizer
_tap
;
bool
_active
=
false
;
double
_currentDragValue
=
0.0
;
Rect
?
overlayRect
;
// This rect is used in gesture calculations, where the gesture coordinates
// are relative to the sliders origin. Therefore, the offset is passed as
...
...
@@ -1258,7 +1260,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
return
;
}
_hasFocus
=
value
;
_updateForFocus
OrHover
(
_hasFocus
);
_updateForFocus
(
_hasFocus
);
markNeedsSemanticsUpdate
();
}
...
...
@@ -1271,11 +1273,24 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
return
;
}
_hovering
=
value
;
_updateFor
FocusOr
Hover
(
_hovering
);
_updateForHover
(
_hovering
);
}
void
_updateForFocusOrHover
(
bool
hasFocusOrIsHovering
)
{
if
(
hasFocusOrIsHovering
)
{
/// True if the slider is interactive and the slider thumb is being
/// hovered over by a pointer.
bool
_hoveringThumb
=
false
;
bool
get
hoveringThumb
=>
_hoveringThumb
;
set
hoveringThumb
(
bool
value
)
{
assert
(
value
!=
null
);
if
(
value
==
_hoveringThumb
)
{
return
;
}
_hoveringThumb
=
value
;
_updateForHover
(
_hovering
);
}
void
_updateForFocus
(
bool
focused
)
{
if
(
focused
)
{
_state
.
overlayController
.
forward
();
if
(
showValueIndicator
)
{
_state
.
valueIndicatorController
.
forward
();
...
...
@@ -1288,6 +1303,18 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
}
}
void
_updateForHover
(
bool
hovered
)
{
// Only show overlay when pointer is hovering the thumb.
if
(
hovered
&&
hoveringThumb
)
{
_state
.
overlayController
.
forward
();
}
else
{
// Only remove overlay when Slider is unfocused.
if
(!
hasFocus
)
{
_state
.
overlayController
.
reverse
();
}
}
}
bool
get
showValueIndicator
{
switch
(
_sliderTheme
.
showValueIndicator
!)
{
case
ShowValueIndicator
.
onlyForDiscrete
:
...
...
@@ -1404,7 +1431,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_state
.
interactionTimer
?.
cancel
();
_state
.
interactionTimer
=
Timer
(
_minimumInteractionTime
*
timeDilation
,
()
{
_state
.
interactionTimer
=
null
;
if
(!
_active
&&
if
(!
_active
&&
!
hasFocus
&&
_state
.
valueIndicatorController
.
status
==
AnimationStatus
.
completed
)
{
_state
.
valueIndicatorController
.
reverse
();
}
...
...
@@ -1422,7 +1449,9 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
onChangeEnd
?.
call
(
_discretize
(
_currentDragValue
));
_active
=
false
;
_currentDragValue
=
0.0
;
_state
.
overlayController
.
reverse
();
if
(!
hasFocus
)
{
_state
.
overlayController
.
reverse
();
}
if
(
showValueIndicator
&&
_state
.
interactionTimer
==
null
)
{
_state
.
valueIndicatorController
.
reverse
();
...
...
@@ -1476,6 +1505,9 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_drag
.
addPointer
(
event
);
_tap
.
addPointer
(
event
);
}
if
(
isInteractive
&&
overlayRect
!=
null
)
{
hoveringThumb
=
overlayRect
!.
contains
(
event
.
localPosition
);
}
}
@override
...
...
@@ -1529,6 +1561,10 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
isDiscrete:
isDiscrete
,
);
final
Offset
thumbCenter
=
Offset
(
trackRect
.
left
+
visualPosition
*
trackRect
.
width
,
trackRect
.
center
.
dy
);
if
(
isInteractive
)
{
final
Size
overlaySize
=
sliderTheme
.
overlayShape
!.
getPreferredSize
(
isInteractive
,
false
);
overlayRect
=
Rect
.
fromCircle
(
center:
thumbCenter
,
radius:
overlaySize
.
width
/
2.0
);
}
final
Offset
?
secondaryOffset
=
(
secondaryVisualPosition
!=
null
)
?
Offset
(
trackRect
.
left
+
secondaryVisualPosition
*
trackRect
.
width
,
trackRect
.
center
.
dy
)
:
null
;
_sliderTheme
.
trackShape
!.
paint
(
...
...
packages/flutter/test/material/slider_test.dart
View file @
400136b0
...
...
@@ -1748,7 +1748,7 @@ void main() {
paints
..
circle
(
color:
Colors
.
orange
[
500
]),
);
// Check that the overlay does not show when focused and disabled.
// Check that the overlay does not show when
un
focused and disabled.
await
tester
.
pumpWidget
(
buildApp
(
enabled:
false
));
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasPrimaryFocus
,
isFalse
);
...
...
@@ -1990,10 +1990,10 @@ void main() {
await
drag
.
up
();
await
tester
.
pumpAndSettle
();
// Slider
does not have an
overlay when stopped dragging.
// Slider
still has
overlay when stopped dragging.
expect
(
Material
.
of
(
tester
.
element
(
find
.
byType
(
Slider
))),
isNot
(
paints
..
circle
(
color:
Colors
.
lime
[
500
])
),
paints
..
circle
(
color:
Colors
.
lime
[
500
]
),
);
});
...
...
@@ -3177,4 +3177,208 @@ void main() {
expect
(
sliderEnd
,
true
);
expect
(
dragStarted
,
false
);
});
testWidgets
(
'Overlay appear only when hovered on the thumb on desktop'
,
(
WidgetTester
tester
)
async
{
double
value
=
0.5
;
const
Color
overlayColor
=
Color
(
0xffff0000
);
Widget
buildApp
({
bool
enabled
=
true
})
{
return
MaterialApp
(
home:
Material
(
child:
Center
(
child:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
Slider
(
value:
value
,
overlayColor:
const
MaterialStatePropertyAll
<
Color
?>(
overlayColor
),
onChanged:
enabled
?
(
double
newValue
)
{
setState
(()
{
value
=
newValue
;
});
}
:
null
,
);
}),
),
),
);
}
await
tester
.
pumpWidget
(
buildApp
());
// Slider does not have overlay when enabled and not hovered.
await
tester
.
pumpAndSettle
();
expect
(
Material
.
of
(
tester
.
element
(
find
.
byType
(
Slider
))),
isNot
(
paints
..
circle
(
color:
overlayColor
)),
);
// Hover on the slider but outside the thumb.
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
tester
.
getTopLeft
(
find
.
byType
(
Slider
)));
await
tester
.
pumpWidget
(
buildApp
());
await
tester
.
pumpAndSettle
();
expect
(
Material
.
of
(
tester
.
element
(
find
.
byType
(
Slider
))),
isNot
(
paints
..
circle
(
color:
overlayColor
)),
);
// Hover on the thumb.
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
byType
(
Slider
)));
await
tester
.
pumpAndSettle
();
expect
(
Material
.
of
(
tester
.
element
(
find
.
byType
(
Slider
))),
paints
..
circle
(
color:
overlayColor
),
);
// Hover on the slider but outside the thumb.
await
gesture
.
moveTo
(
tester
.
getBottomRight
(
find
.
byType
(
Slider
)));
await
tester
.
pumpAndSettle
();
expect
(
Material
.
of
(
tester
.
element
(
find
.
byType
(
Slider
))),
isNot
(
paints
..
circle
(
color:
overlayColor
)),
);
},
variant:
TargetPlatformVariant
.
desktop
());
testWidgets
(
'Overlay remains when Slider is in focus on desktop'
,
(
WidgetTester
tester
)
async
{
double
value
=
0.5
;
const
Color
overlayColor
=
Color
(
0xffff0000
);
final
FocusNode
focusNode
=
FocusNode
();
Widget
buildApp
({
bool
enabled
=
true
})
{
return
MaterialApp
(
home:
Material
(
child:
Center
(
child:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
Slider
(
value:
value
,
focusNode:
focusNode
,
overlayColor:
const
MaterialStatePropertyAll
<
Color
?>(
overlayColor
),
onChanged:
enabled
?
(
double
newValue
)
{
setState
(()
{
value
=
newValue
;
});
}
:
null
,
);
}),
),
),
);
}
await
tester
.
pumpWidget
(
buildApp
());
// Slider does not have overlay when enabled and not tapped.
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasFocus
,
false
);
expect
(
Material
.
of
(
tester
.
element
(
find
.
byType
(
Slider
))),
isNot
(
paints
..
circle
(
color:
overlayColor
)),
);
final
Offset
sliderCenter
=
tester
.
getCenter
(
find
.
byType
(
Slider
));
Offset
tapLocation
=
Offset
(
sliderCenter
.
dx
+
50
,
sliderCenter
.
dy
);
// Tap somewhere to bring overlay.
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
();
await
gesture
.
down
(
tapLocation
);
await
gesture
.
up
();
focusNode
.
requestFocus
();
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasFocus
,
true
);
expect
(
Material
.
of
(
tester
.
element
(
find
.
byType
(
Slider
))),
paints
..
circle
(
color:
overlayColor
),
);
tapLocation
=
Offset
(
sliderCenter
.
dx
-
50
,
sliderCenter
.
dy
);
await
gesture
.
down
(
tapLocation
);
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasFocus
,
true
);
expect
(
Material
.
of
(
tester
.
element
(
find
.
byType
(
Slider
))),
paints
..
circle
(
color:
overlayColor
),
);
focusNode
.
unfocus
();
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasFocus
,
false
);
expect
(
Material
.
of
(
tester
.
element
(
find
.
byType
(
Slider
))),
isNot
(
paints
..
circle
(
color:
overlayColor
)),
);
},
variant:
TargetPlatformVariant
.
desktop
());
testWidgets
(
'Value indicator remains when Slider is in focus on desktop'
,
(
WidgetTester
tester
)
async
{
double
value
=
0.5
;
final
FocusNode
focusNode
=
FocusNode
();
Widget
buildApp
({
bool
enabled
=
true
})
{
return
MaterialApp
(
theme:
ThemeData
(
sliderTheme:
const
SliderThemeData
(
showValueIndicator:
ShowValueIndicator
.
always
,
),
),
home:
Material
(
child:
Center
(
child:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
Slider
(
value:
value
,
focusNode:
focusNode
,
divisions:
5
,
label:
value
.
toStringAsFixed
(
1
),
onChanged:
enabled
?
(
double
newValue
)
{
setState
(()
{
value
=
newValue
;
});
}
:
null
,
);
}),
),
),
);
}
await
tester
.
pumpWidget
(
buildApp
());
// Slider does not show value indicator without focus.
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasFocus
,
false
);
RenderBox
valueIndicatorBox
=
tester
.
renderObject
(
find
.
byType
(
Overlay
));
expect
(
valueIndicatorBox
,
isNot
(
paints
..
path
(
color:
const
Color
(
0xff000000
))..
paragraph
()),
);
final
Offset
sliderCenter
=
tester
.
getCenter
(
find
.
byType
(
Slider
));
final
Offset
tapLocation
=
Offset
(
sliderCenter
.
dx
+
50
,
sliderCenter
.
dy
);
// Tap somewhere to bring value indicator.
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
();
await
gesture
.
down
(
tapLocation
);
await
gesture
.
up
();
focusNode
.
requestFocus
();
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasFocus
,
true
);
valueIndicatorBox
=
tester
.
renderObject
(
find
.
byType
(
Overlay
));
expect
(
valueIndicatorBox
,
paints
..
path
(
color:
const
Color
(
0xff000000
))..
paragraph
(),
);
focusNode
.
unfocus
();
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasFocus
,
false
);
expect
(
valueIndicatorBox
,
isNot
(
paints
..
path
(
color:
const
Color
(
0xff000000
))..
paragraph
()),
);
},
variant:
TargetPlatformVariant
.
desktop
());
}
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