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
629395f7
Unverified
Commit
629395f7
authored
Jul 23, 2020
by
Pierre-Louis
Committed by
GitHub
Jul 23, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add per thumb Range Slider semantics (#61439)
parent
6d303af9
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
186 additions
and
49 deletions
+186
-49
range_slider.dart
packages/flutter/lib/src/material/range_slider.dart
+126
-40
slider.dart
packages/flutter/lib/src/material/slider.dart
+0
-7
slider_theme.dart
packages/flutter/lib/src/material/slider_theme.dart
+3
-2
range_slider_test.dart
packages/flutter/test/material/range_slider_test.dart
+57
-0
No files found.
packages/flutter/lib/src/material/range_slider.dart
View file @
629395f7
...
@@ -365,13 +365,13 @@ class RangeSlider extends StatefulWidget {
...
@@ -365,13 +365,13 @@ class RangeSlider extends StatefulWidget {
/// _dollarsRange = newValues;
/// _dollarsRange = newValues;
/// });
/// });
/// },
/// },
/// semanticFormatterCallback: (
RangeValues rangeValues
) {
/// semanticFormatterCallback: (
double newValue
) {
/// return '${
rangeValues.start.round()} - ${rangeValues.end
.round()} dollars';
/// return '${
newValue
.round()} dollars';
/// }
/// }
/// )
/// )
/// ```
/// ```
/// {@end-tool}
/// {@end-tool}
final
Range
SemanticFormatterCallback
semanticFormatterCallback
;
final
SemanticFormatterCallback
semanticFormatterCallback
;
// Touch width for the tap boundary of the slider thumbs.
// Touch width for the tap boundary of the slider thumbs.
static
const
double
_minTouchTargetWidth
=
kMinInteractiveDimension
;
static
const
double
_minTouchTargetWidth
=
kMinInteractiveDimension
;
...
@@ -394,7 +394,7 @@ class RangeSlider extends StatefulWidget {
...
@@ -394,7 +394,7 @@ class RangeSlider extends StatefulWidget {
properties
.
add
(
StringProperty
(
'labelEnd'
,
labels
?.
end
));
properties
.
add
(
StringProperty
(
'labelEnd'
,
labels
?.
end
));
properties
.
add
(
ColorProperty
(
'activeColor'
,
activeColor
));
properties
.
add
(
ColorProperty
(
'activeColor'
,
activeColor
));
properties
.
add
(
ColorProperty
(
'inactiveColor'
,
inactiveColor
));
properties
.
add
(
ColorProperty
(
'inactiveColor'
,
inactiveColor
));
properties
.
add
(
ObjectFlagProperty
<
ValueChanged
<
RangeValues
>>.
has
(
'semanticFormatterCallback'
,
semanticFormatterCallback
));
properties
.
add
(
ObjectFlagProperty
<
ValueChanged
<
double
>>.
has
(
'semanticFormatterCallback'
,
semanticFormatterCallback
));
}
}
}
}
...
@@ -703,7 +703,7 @@ class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
...
@@ -703,7 +703,7 @@ class _RangeSliderRenderObjectWidget extends LeafRenderObjectWidget {
final
ValueChanged
<
RangeValues
>
onChanged
;
final
ValueChanged
<
RangeValues
>
onChanged
;
final
ValueChanged
<
RangeValues
>
onChangeStart
;
final
ValueChanged
<
RangeValues
>
onChangeStart
;
final
ValueChanged
<
RangeValues
>
onChangeEnd
;
final
ValueChanged
<
RangeValues
>
onChangeEnd
;
final
Range
SemanticFormatterCallback
semanticFormatterCallback
;
final
SemanticFormatterCallback
semanticFormatterCallback
;
final
_RangeSliderState
state
;
final
_RangeSliderState
state
;
@override
@override
...
@@ -756,7 +756,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
...
@@ -756,7 +756,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
Size
screenSize
,
Size
screenSize
,
TargetPlatform
platform
,
TargetPlatform
platform
,
ValueChanged
<
RangeValues
>
onChanged
,
ValueChanged
<
RangeValues
>
onChanged
,
Range
SemanticFormatterCallback
semanticFormatterCallback
,
SemanticFormatterCallback
semanticFormatterCallback
,
this
.
onChangeStart
,
this
.
onChangeStart
,
this
.
onChangeEnd
,
this
.
onChangeEnd
,
@required
_RangeSliderState
state
,
@required
_RangeSliderState
state
,
...
@@ -858,6 +858,8 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
...
@@ -858,6 +858,8 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
bool
get
isDiscrete
=>
divisions
!=
null
&&
divisions
>
0
;
bool
get
isDiscrete
=>
divisions
!=
null
&&
divisions
>
0
;
double
get
_minThumbSeparationValue
=>
isDiscrete
?
0
:
sliderTheme
.
minThumbSeparation
/
_trackRect
.
width
;
RangeValues
get
values
=>
_values
;
RangeValues
get
values
=>
_values
;
RangeValues
_values
;
RangeValues
_values
;
set
values
(
RangeValues
newValues
)
{
set
values
(
RangeValues
newValues
)
{
...
@@ -897,9 +899,9 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
...
@@ -897,9 +899,9 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
markNeedsSemanticsUpdate
();
markNeedsSemanticsUpdate
();
}
}
Range
SemanticFormatterCallback
_semanticFormatterCallback
;
SemanticFormatterCallback
_semanticFormatterCallback
;
Range
SemanticFormatterCallback
get
semanticFormatterCallback
=>
_semanticFormatterCallback
;
SemanticFormatterCallback
get
semanticFormatterCallback
=>
_semanticFormatterCallback
;
set
semanticFormatterCallback
(
Range
SemanticFormatterCallback
value
)
{
set
semanticFormatterCallback
(
SemanticFormatterCallback
value
)
{
if
(
_semanticFormatterCallback
==
value
)
if
(
_semanticFormatterCallback
==
value
)
return
;
return
;
_semanticFormatterCallback
=
value
;
_semanticFormatterCallback
=
value
;
...
@@ -1189,11 +1191,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
...
@@ -1189,11 +1191,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
}
}
final
double
currentDragValue
=
_discretize
(
dragValue
);
final
double
currentDragValue
=
_discretize
(
dragValue
);
final
double
minThumbSeparationValue
=
isDiscrete
?
0
:
sliderTheme
.
minThumbSeparation
/
_trackRect
.
width
;
if
(
_lastThumbSelection
==
Thumb
.
start
)
{
if
(
_lastThumbSelection
==
Thumb
.
start
)
{
_newValues
=
RangeValues
(
math
.
min
(
currentDragValue
,
currentValues
.
end
-
minThumbSeparationValue
),
currentValues
.
end
);
_newValues
=
RangeValues
(
math
.
min
(
currentDragValue
,
currentValues
.
end
-
_
minThumbSeparationValue
),
currentValues
.
end
);
}
else
if
(
_lastThumbSelection
==
Thumb
.
end
)
{
}
else
if
(
_lastThumbSelection
==
Thumb
.
end
)
{
_newValues
=
RangeValues
(
currentValues
.
start
,
math
.
max
(
currentDragValue
,
currentValues
.
start
+
minThumbSeparationValue
));
_newValues
=
RangeValues
(
currentValues
.
start
,
math
.
max
(
currentDragValue
,
currentValues
.
start
+
_
minThumbSeparationValue
));
}
}
onChanged
(
_newValues
);
onChanged
(
_newValues
);
}
}
...
@@ -1513,64 +1514,149 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
...
@@ -1513,64 +1514,149 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix
);
);
}
}
@override
/// Describe the semantics of the start thumb.
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
SemanticsNode
_startSemanticsNode
=
SemanticsNode
();
super
.
describeSemanticsConfiguration
(
config
);
config
.
isSemanticBoundary
=
isEnabled
;
/// Describe the semantics of the end thumb.
if
(
isEnabled
)
{
SemanticsNode
_endSemanticsNode
=
SemanticsNode
();
// Create the semantics configuration for a single value.
SemanticsConfiguration
_createSemanticsConfiguration
(
double
value
,
double
increasedValue
,
double
decreasedValue
,
String
label
,
VoidCallback
increaseAction
,
VoidCallback
decreaseAction
,
)
{
final
SemanticsConfiguration
config
=
SemanticsConfiguration
();
config
.
isEnabled
=
isEnabled
;
config
.
textDirection
=
textDirection
;
config
.
textDirection
=
textDirection
;
config
.
customSemanticsActions
=
<
CustomSemanticsAction
,
VoidCallback
>{
if
(
isEnabled
)
{
_decreaseStart:
_decreaseStartAction
,
config
.
onIncrease
=
increaseAction
;
_increaseStart:
_increaseStartAction
,
config
.
onDecrease
=
decreaseAction
;
_decreaseEnd:
_decreaseEndAction
,
}
_increaseEnd:
_increaseEndAction
,
config
.
label
=
label
??
''
;
};
if
(
semanticFormatterCallback
!=
null
)
{
if
(
semanticFormatterCallback
!=
null
)
{
config
.
value
=
semanticFormatterCallback
(
_state
.
_lerpRangeValues
(
values
));
config
.
value
=
semanticFormatterCallback
(
_state
.
_lerp
(
value
));
config
.
increasedValue
=
semanticFormatterCallback
(
_state
.
_lerp
(
increasedValue
));
config
.
decreasedValue
=
semanticFormatterCallback
(
_state
.
_lerp
(
decreasedValue
));
}
else
{
}
else
{
config
.
value
=
values
.
toString
();
config
.
value
=
'
${(value * 100).round()}
%'
;
config
.
increasedValue
=
'
${(increasedValue * 100).round()}
%'
;
config
.
decreasedValue
=
'
${(decreasedValue * 100).round()}
%'
;
}
return
config
;
}
@override
void
assembleSemanticsNode
(
SemanticsNode
node
,
SemanticsConfiguration
config
,
Iterable
<
SemanticsNode
>
children
,
)
{
assert
(
children
.
isEmpty
);
final
SemanticsConfiguration
startSemanticsConfiguration
=
_createSemanticsConfiguration
(
values
.
start
,
_increasedStartValue
,
_decreasedStartValue
,
labels
?.
start
,
_increaseStartAction
,
_decreaseStartAction
,
);
final
SemanticsConfiguration
endSemanticsConfiguration
=
_createSemanticsConfiguration
(
values
.
end
,
_increasedEndValue
,
_decreasedEndValue
,
labels
?.
end
,
_increaseEndAction
,
_decreaseEndAction
,
);
// Split the semantics node area between the start and end nodes.
final
Rect
leftRect
=
Rect
.
fromPoints
(
node
.
rect
.
topLeft
,
node
.
rect
.
bottomCenter
);
final
Rect
rightRect
=
Rect
.
fromPoints
(
node
.
rect
.
topCenter
,
node
.
rect
.
bottomRight
);
switch
(
textDirection
)
{
case
TextDirection
.
ltr
:
_startSemanticsNode
.
rect
=
leftRect
;
_endSemanticsNode
.
rect
=
rightRect
;
break
;
case
TextDirection
.
rtl
:
_startSemanticsNode
.
rect
=
rightRect
;
_endSemanticsNode
.
rect
=
leftRect
;
break
;
}
}
_startSemanticsNode
.
updateWith
(
config:
startSemanticsConfiguration
);
_endSemanticsNode
.
updateWith
(
config:
endSemanticsConfiguration
);
final
List
<
SemanticsNode
>
finalChildren
=
<
SemanticsNode
>[
_startSemanticsNode
,
_endSemanticsNode
,
];
node
.
updateWith
(
config:
config
,
childrenInInversePaintOrder:
finalChildren
);
}
}
@override
void
clearSemantics
()
{
super
.
clearSemantics
();
_startSemanticsNode
=
null
;
_endSemanticsNode
=
null
;
}
}
final
CustomSemanticsAction
_decreaseStart
=
const
CustomSemanticsAction
(
label:
'Decrease Min'
);
@override
final
CustomSemanticsAction
_increaseStart
=
const
CustomSemanticsAction
(
label:
'Increase Min'
);
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
final
CustomSemanticsAction
_decreaseEnd
=
const
CustomSemanticsAction
(
label:
'Decrease Max'
);
super
.
describeSemanticsConfiguration
(
config
);
final
CustomSemanticsAction
_increaseEnd
=
const
CustomSemanticsAction
(
label:
'Increase Max'
);
config
.
isSemanticBoundary
=
true
;
}
double
get
_semanticActionUnit
=>
divisions
!=
null
?
1.0
/
divisions
:
_adjustmentUnit
;
double
get
_semanticActionUnit
=>
divisions
!=
null
?
1.0
/
divisions
:
_adjustmentUnit
;
void
_increaseStartAction
()
{
void
_increaseStartAction
()
{
if
(
isEnabled
)
{
if
(
isEnabled
)
{
onChanged
(
RangeValues
(
_increaseValue
(
values
.
start
)
,
values
.
end
));
onChanged
(
RangeValues
(
_increasedStartValue
,
values
.
end
));
}
}
}
}
void
_decreaseStartAction
()
{
void
_decreaseStartAction
()
{
if
(
isEnabled
)
{
if
(
isEnabled
)
{
onChanged
(
RangeValues
(
_decrease
Value
(
values
.
start
)
,
values
.
end
));
onChanged
(
RangeValues
(
_decrease
dStartValue
,
values
.
end
));
}
}
}
}
void
_increaseEndAction
()
{
void
_increaseEndAction
()
{
if
(
isEnabled
)
{
if
(
isEnabled
)
{
onChanged
(
RangeValues
(
values
.
start
,
_increase
Value
(
values
.
end
)
));
onChanged
(
RangeValues
(
values
.
start
,
_increase
dEndValue
));
}
}
}
}
void
_decreaseEndAction
()
{
void
_decreaseEndAction
()
{
if
(
isEnabled
)
{
if
(
isEnabled
)
{
onChanged
(
RangeValues
(
values
.
start
,
_decrease
Value
(
values
.
end
)
));
onChanged
(
RangeValues
(
values
.
start
,
_decrease
dEndValue
));
}
}
}
}
double
_increaseValue
(
double
value
)
{
double
get
_increasedStartValue
{
return
(
value
+
_semanticActionUnit
).
clamp
(
0.0
,
1.0
)
as
double
;
// Due to floating-point operations, this value can actually be greater than
// expected (e.g. 0.4 + 0.2 = 0.600000000001), so we limit to 2 decimal points.
final
double
increasedStartValue
=
double
.
parse
((
values
.
start
+
_semanticActionUnit
).
toStringAsFixed
(
2
));
return
increasedStartValue
<=
values
.
end
-
_minThumbSeparationValue
?
increasedStartValue
:
values
.
start
;
}
double
get
_decreasedStartValue
{
return
(
values
.
start
-
_semanticActionUnit
).
clamp
(
0.0
,
1.0
)
as
double
;
}
double
get
_increasedEndValue
{
return
(
values
.
end
+
_semanticActionUnit
).
clamp
(
0.0
,
1.0
)
as
double
;
}
}
double
_decreaseValue
(
double
value
)
{
double
get
_decreasedEndValue
{
return
(
value
-
_semanticActionUnit
).
clamp
(
0.0
,
1.0
)
as
double
;
final
double
decreasedEndValue
=
values
.
end
-
_semanticActionUnit
;
return
decreasedEndValue
>=
values
.
start
+
_minThumbSeparationValue
?
decreasedEndValue
:
values
.
end
;
}
}
}
}
...
...
packages/flutter/lib/src/material/slider.dart
View file @
629395f7
...
@@ -28,13 +28,6 @@ import 'theme.dart';
...
@@ -28,13 +28,6 @@ import 'theme.dart';
// int _duelCommandment = 1;
// int _duelCommandment = 1;
// void setState(VoidCallback fn) { }
// void setState(VoidCallback fn) { }
/// A callback that formats a numeric value from a [Slider] widget.
///
/// See also:
///
/// * [Slider.semanticFormatterCallback], which shows an example use case.
typedef
SemanticFormatterCallback
=
String
Function
(
double
value
);
/// [Slider] uses this callback to paint the value indicator on the overlay.
/// [Slider] uses this callback to paint the value indicator on the overlay.
///
///
/// Since the value indicator is painted on the Overlay; this method paints the
/// Since the value indicator is painted on the Overlay; this method paints the
...
...
packages/flutter/lib/src/material/slider_theme.dart
View file @
629395f7
...
@@ -3307,12 +3307,13 @@ class _PaddleSliderValueIndicatorPathPainter {
...
@@ -3307,12 +3307,13 @@ class _PaddleSliderValueIndicatorPathPainter {
}
}
}
}
/// A callback that formats
the numeric values from a [Range
Slider] widget.
/// A callback that formats
a numeric value from a [Slider] or [Ranger
Slider] widget.
///
///
/// See also:
/// See also:
///
///
/// * [Slider.semanticFormatterCallback], which shows an example use case.
/// * [RangeSlider.semanticFormatterCallback], which shows an example use case.
/// * [RangeSlider.semanticFormatterCallback], which shows an example use case.
typedef
RangeSemanticFormatterCallback
=
String
Function
(
RangeValues
values
);
typedef
SemanticFormatterCallback
=
String
Function
(
double
value
);
/// Decides which thumbs (if any) should be selected.
/// Decides which thumbs (if any) should be selected.
///
///
...
...
packages/flutter/test/material/range_slider_test.dart
View file @
629395f7
...
@@ -1742,6 +1742,63 @@ void main() {
...
@@ -1742,6 +1742,63 @@ void main() {
);
);
});
});
testWidgets
(
'Range Slider Semantics'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Theme
(
data:
ThemeData
.
light
(),
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MediaQuery
(
data:
MediaQueryData
.
fromWindow
(
window
),
child:
Material
(
child:
RangeSlider
(
values:
const
RangeValues
(
10.0
,
12.0
),
min:
0.0
,
max:
100.0
,
onChanged:
(
RangeValues
v
)
{
},
),
),
),
),
),
)
);
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSemantics
(
find
.
byType
(
RangeSlider
)),
matchesSemantics
(
scopesRoute:
true
,
children:
<
Matcher
>[
matchesSemantics
(
children:
<
Matcher
>[
matchesSemantics
(
isEnabled:
true
,
hasEnabledState:
true
,
hasIncreaseAction:
true
,
hasDecreaseAction:
true
,
value:
'10%'
,
increasedValue:
'10%'
,
decreasedValue:
'5%'
,
),
matchesSemantics
(
isEnabled:
true
,
hasEnabledState:
true
,
hasIncreaseAction:
true
,
hasDecreaseAction:
true
,
value:
'12%'
,
increasedValue:
'17%'
,
decreasedValue:
'12%'
,
),
],
),
],
),
);
});
testWidgets
(
'Range Slider implements debugFillProperties'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Range Slider implements debugFillProperties'
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
...
...
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