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
c35e484c
Unverified
Commit
c35e484c
authored
Jun 20, 2018
by
Natalie Sampsell
Committed by
GitHub
Jun 20, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Adding segmented control (#18373)
parent
9da80217
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
1442 additions
and
1 deletion
+1442
-1
goldens.version
bin/internal/goldens.version
+1
-1
cupertino.dart
packages/flutter/lib/cupertino.dart
+1
-0
segmented_control.dart
packages/flutter/lib/src/cupertino/segmented_control.dart
+510
-0
segmented_control_test.dart
packages/flutter/test/cupertino/segmented_control_test.dart
+930
-0
No files found.
bin/internal/goldens.version
View file @
c35e484c
568342373b14fab81fd665d397c8c00db3d46fca
e4920845c80ca52c8891c023f137f1c9a651523e
packages/flutter/lib/cupertino.dart
View file @
c35e484c
...
...
@@ -19,6 +19,7 @@ export 'src/cupertino/picker.dart';
export
'src/cupertino/refresh.dart'
;
export
'src/cupertino/route.dart'
;
export
'src/cupertino/scrollbar.dart'
;
export
'src/cupertino/segmented_control.dart'
;
export
'src/cupertino/slider.dart'
;
export
'src/cupertino/switch.dart'
;
export
'src/cupertino/tab_scaffold.dart'
;
...
...
packages/flutter/lib/src/cupertino/segmented_control.dart
0 → 100644
View file @
c35e484c
// Copyright 2018 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:collection'
;
import
'dart:math'
as
math
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'colors.dart'
;
// Minimum padding from horizontal edges of segmented control to edges of
// encompassing widget.
const
EdgeInsets
_kHorizontalItemPadding
=
const
EdgeInsets
.
symmetric
(
horizontal:
16.0
);
// Minimum height of the segmented control.
const
double
_kMinSegmentedControlHeight
=
28.0
;
// Light, partially-transparent blue color. Used to fill the background of
// a child option the user is temporarily interacting with through a long
// press or drag.
const
Color
_kPressedBackground
=
const
Color
(
0x33007aff
);
/// An iOS-style segmented control.
///
/// Displays the widgets provided in the [Map] of [children] in a
/// horizontal list. Used to select between a number of mutually exclusive
/// options. When one option in the segmented control is selected, the other
/// options in the segmented control cease to be selected.
///
/// A segmented control can feature any [Widget] as one of the values in its
/// [Map] of [children]. The type T is the type of the keys used
/// to identify each widget and determine which widget is selected. As
/// required by the [Map] class, keys must be of consistent types
/// and must be comparable. The ordering of the keys will determine the order
/// of the widgets in the segmented control.
///
/// When the state of the segmented control changes, the widget calls the
/// [onValueChanged] callback. The map key associated with the newly selected
/// widget is returned in the [onValueChanged] callback. Typically, widgets
/// that use a segmented control will listen for the [onValueChanged] callback
/// and rebuild the segmented control with a new [groupValue] to update which
/// option is currently selected.
///
/// The [children] will be displayed in the order of the keys in the [Map].
/// The height of the segmented control is determined by the height of the
/// tallest widget provided as a value in the [Map] of [children].
/// The width of the segmented control is determined by the horizontal
/// constraints on its parent. The available horizontal space is divided by
/// the number of provided [children] to determine the width of each widget.
/// The selection area for each of the widgets in the [Map] of
/// [children] will then be expanded to fill the calculated space, so each
/// widget will appear to have the same dimensions.
///
/// See also:
///
/// * <https://developer.apple.com/design/human-interface-guidelines/ios/controls/segmented-controls/>
class
SegmentedControl
<
T
>
extends
StatefulWidget
{
/// Creates an iOS-style segmented control bar.
///
/// The [children] and [onValueChanged] arguments must not be null. The
/// [children] argument must be an ordered [Map] such as a [LinkedHashMap].
/// Further, the length of the [children] list must be greater than one.
///
/// Each widget value in the map of [children] must have an associated key
/// that uniquely identifies this widget. This key is what will be returned
/// in the [onValueChanged] callback when a new value from the [children] map
/// is selected.
///
/// The [groupValue] must be one of the keys in the [children] map.
/// The [groupValue] is the currently selected value for the segmented control.
/// If no [groupValue] is provided, or the [groupValue] is null, no widget will
/// appear as selected.
SegmentedControl
({
Key
key
,
@required
this
.
children
,
@required
this
.
onValueChanged
,
this
.
groupValue
,
})
:
assert
(
children
!=
null
),
assert
(
children
.
length
>=
2
),
assert
(
onValueChanged
!=
null
),
assert
(
groupValue
==
null
||
children
.
keys
.
any
((
T
child
)
=>
child
==
groupValue
)),
super
(
key:
key
);
/// The identifying keys and corresponding widget values in the
/// segmented control.
///
/// The map must have more than one entry.
/// This attribute must be an ordered [Map] such as a [LinkedHashMap].
final
Map
<
T
,
Widget
>
children
;
/// The identifier of the widget that is currently selected.
///
/// This must be one of the keys in the [Map] of [children].
/// If this attribute is null, no widget will be initially selected.
final
T
groupValue
;
/// The callback that is called when a new option is tapped.
///
/// This attribute must not be null.
///
/// The segmented control passes the newly selected widget's associated key
/// to the callback but does not actually change state until the parent
/// widget rebuilds the segmented control with the new [groupValue].
///
/// The callback provided to [onValueChanged] should update the state of
/// the parent [StatefulWidget] using the [State.setState] method, so that
/// the parent gets rebuilt; for example:
///
/// ## Sample code
///
/// ```dart
/// class SegmentedControlExample extends StatefulWidget {
/// @override
/// State createState() => new SegmentedControlExampleState();
/// }
///
/// class SegmentedControlExampleState extends State<SegmentedControlExample> {
/// final Map<int, Widget> children = const {
/// 0: const Text('Child 1'),
/// 1: const Text('Child 2'),
/// };
///
/// int currentValue;
///
/// @override
/// Widget build(BuildContext context) {
/// return new Container(
/// child: new SegmentedControl<int>(
/// children: children,
/// onValueChanged: (int newValue) {
/// setState(() {
/// currentValue = newValue;
/// });
/// },
/// groupValue: currentValue,
/// ),
/// );
/// }
/// }
/// ```
final
ValueChanged
<
T
>
onValueChanged
;
@override
_SegmentedControlState
<
T
>
createState
()
=>
_SegmentedControlState
<
T
>();
}
class
_SegmentedControlState
<
T
>
extends
State
<
SegmentedControl
<
T
>>
{
T
_pressedKey
;
void
_onTapDown
(
T
currentKey
)
{
setState
(()
{
_pressedKey
=
currentKey
;
});
}
void
_onTapUp
(
TapUpDetails
event
)
{
setState
(()
{
_pressedKey
=
null
;
});
}
void
_onTapCancel
()
{
setState
(()
{
_pressedKey
=
null
;
});
}
void
_onTap
(
T
currentKey
)
{
if
(
currentKey
!=
widget
.
groupValue
)
{
widget
.
onValueChanged
(
currentKey
);
}
}
@override
Widget
build
(
BuildContext
context
)
{
final
List
<
Widget
>
gestureChildren
=
<
Widget
>[];
int
index
=
0
;
int
selectedIndex
;
int
pressedIndex
;
for
(
T
currentKey
in
widget
.
children
.
keys
)
{
selectedIndex
=
(
widget
.
groupValue
==
currentKey
)
?
index
:
selectedIndex
;
pressedIndex
=
(
_pressedKey
==
currentKey
)
?
index
:
pressedIndex
;
final
TextStyle
textStyle
=
DefaultTextStyle
.
of
(
context
).
style
.
copyWith
(
color:
(
widget
.
groupValue
==
currentKey
)
?
CupertinoColors
.
white
:
CupertinoColors
.
activeBlue
,
);
final
IconThemeData
iconTheme
=
new
IconThemeData
(
color:
(
widget
.
groupValue
==
currentKey
)
?
CupertinoColors
.
white
:
CupertinoColors
.
activeBlue
,
);
Widget
child
=
widget
.
children
[
currentKey
];
child
=
new
GestureDetector
(
onTapDown:
(
TapDownDetails
event
)
{
_onTapDown
(
currentKey
);
},
onTapUp:
_onTapUp
,
onTapCancel:
_onTapCancel
,
onTap:
()
{
_onTap
(
currentKey
);
},
child:
new
IconTheme
(
data:
iconTheme
,
child:
new
DefaultTextStyle
(
style:
textStyle
,
child:
new
Semantics
(
inMutuallyExclusiveGroup:
true
,
selected:
widget
.
groupValue
==
currentKey
,
child:
child
,
),
),
),
);
gestureChildren
.
add
(
child
);
index
+=
1
;
}
final
Widget
box
=
new
_SegmentedControlRenderWidget
<
T
>(
children:
gestureChildren
,
selectedIndex:
selectedIndex
,
pressedIndex:
pressedIndex
,
);
return
new
Padding
(
padding:
_kHorizontalItemPadding
.
resolve
(
Directionality
.
of
(
context
)),
child:
new
UnconstrainedBox
(
constrainedAxis:
Axis
.
horizontal
,
child:
box
,
),
);
}
}
class
_SegmentedControlRenderWidget
<
T
>
extends
MultiChildRenderObjectWidget
{
_SegmentedControlRenderWidget
({
Key
key
,
List
<
Widget
>
children
=
const
<
Widget
>[],
@required
this
.
selectedIndex
,
@required
this
.
pressedIndex
,
})
:
super
(
key:
key
,
children:
children
,
);
final
int
selectedIndex
;
final
int
pressedIndex
;
@override
RenderObject
createRenderObject
(
BuildContext
context
)
{
return
new
_RenderSegmentedControl
<
T
>(
textDirection:
Directionality
.
of
(
context
),
selectedIndex:
selectedIndex
,
pressedIndex:
pressedIndex
,
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
_RenderSegmentedControl
<
T
>
renderObject
)
{
renderObject
..
textDirection
=
Directionality
.
of
(
context
)
..
selectedIndex
=
selectedIndex
..
pressedIndex
=
pressedIndex
;
}
}
class
_SegmentedControlContainerBoxParentData
extends
ContainerBoxParentData
<
RenderBox
>
{
RRect
surroundingRect
;
}
typedef
RenderBox
_NextChild
(
RenderBox
child
);
class
_RenderSegmentedControl
<
T
>
extends
RenderBox
with
ContainerRenderObjectMixin
<
RenderBox
,
ContainerBoxParentData
<
RenderBox
>>,
RenderBoxContainerDefaultsMixin
<
RenderBox
,
ContainerBoxParentData
<
RenderBox
>>
{
_RenderSegmentedControl
({
List
<
RenderBox
>
children
,
@required
int
selectedIndex
,
@required
int
pressedIndex
,
@required
TextDirection
textDirection
,
})
:
assert
(
textDirection
!=
null
),
_textDirection
=
textDirection
,
_selectedIndex
=
selectedIndex
,
_pressedIndex
=
pressedIndex
{
addAll
(
children
);
}
int
get
selectedIndex
=>
_selectedIndex
;
int
_selectedIndex
;
set
selectedIndex
(
int
value
)
{
if
(
_selectedIndex
==
value
)
{
return
;
}
_selectedIndex
=
value
;
markNeedsPaint
();
}
int
get
pressedIndex
=>
_pressedIndex
;
int
_pressedIndex
;
set
pressedIndex
(
int
value
)
{
if
(
_pressedIndex
==
value
)
{
return
;
}
_pressedIndex
=
value
;
markNeedsPaint
();
}
TextDirection
get
textDirection
=>
_textDirection
;
TextDirection
_textDirection
;
set
textDirection
(
TextDirection
value
)
{
if
(
_textDirection
==
value
)
{
return
;
}
_textDirection
=
value
;
markNeedsLayout
();
}
final
Paint
_outlinePaint
=
new
Paint
()
..
color
=
CupertinoColors
.
activeBlue
..
strokeWidth
=
1.0
..
style
=
PaintingStyle
.
stroke
;
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
RenderBox
child
=
firstChild
;
double
minWidth
=
0.0
;
while
(
child
!=
null
)
{
final
_SegmentedControlContainerBoxParentData
childParentData
=
child
.
parentData
;
final
double
childWidth
=
child
.
computeMinIntrinsicWidth
(
height
);
minWidth
=
math
.
max
(
minWidth
,
childWidth
);
child
=
childParentData
.
nextSibling
;
}
return
minWidth
*
childCount
;
}
@override
double
computeMaxIntrinsicWidth
(
double
height
)
{
RenderBox
child
=
firstChild
;
double
maxWidth
=
0.0
;
while
(
child
!=
null
)
{
final
_SegmentedControlContainerBoxParentData
childParentData
=
child
.
parentData
;
final
double
childWidth
=
child
.
computeMaxIntrinsicWidth
(
height
);
maxWidth
=
math
.
max
(
maxWidth
,
childWidth
);
child
=
childParentData
.
nextSibling
;
}
return
maxWidth
*
childCount
;
}
@override
double
computeMinIntrinsicHeight
(
double
width
)
{
RenderBox
child
=
firstChild
;
double
minHeight
=
0.0
;
while
(
child
!=
null
)
{
final
_SegmentedControlContainerBoxParentData
childParentData
=
child
.
parentData
;
final
double
childHeight
=
child
.
computeMinIntrinsicHeight
(
width
);
minHeight
=
math
.
max
(
minHeight
,
childHeight
);
child
=
childParentData
.
nextSibling
;
}
return
minHeight
;
}
@override
double
computeMaxIntrinsicHeight
(
double
width
)
{
RenderBox
child
=
firstChild
;
double
maxHeight
=
0.0
;
while
(
child
!=
null
)
{
final
_SegmentedControlContainerBoxParentData
childParentData
=
child
.
parentData
;
final
double
childHeight
=
child
.
computeMaxIntrinsicHeight
(
width
);
maxHeight
=
math
.
max
(
maxHeight
,
childHeight
);
child
=
childParentData
.
nextSibling
;
}
return
maxHeight
;
}
@override
double
computeDistanceToActualBaseline
(
TextBaseline
baseline
)
{
return
defaultComputeDistanceToHighestActualBaseline
(
baseline
);
}
@override
void
setupParentData
(
RenderBox
child
)
{
if
(
child
.
parentData
is
!
_SegmentedControlContainerBoxParentData
)
{
child
.
parentData
=
new
_SegmentedControlContainerBoxParentData
();
}
}
void
_layoutRects
(
_NextChild
nextChild
,
RenderBox
leftChild
,
RenderBox
rightChild
)
{
RenderBox
child
=
leftChild
;
double
start
=
0.0
;
while
(
child
!=
null
)
{
final
_SegmentedControlContainerBoxParentData
childParentData
=
child
.
parentData
;
final
Offset
childOffset
=
new
Offset
(
start
,
0.0
);
childParentData
.
offset
=
childOffset
;
final
Rect
childRect
=
new
Rect
.
fromLTWH
(
start
,
0.0
,
child
.
size
.
width
,
child
.
size
.
height
);
RRect
rChildRect
;
if
(
child
==
leftChild
)
{
rChildRect
=
new
RRect
.
fromRectAndCorners
(
childRect
,
topLeft:
const
Radius
.
circular
(
3.0
),
bottomLeft:
const
Radius
.
circular
(
3.0
));
}
else
if
(
child
==
rightChild
)
{
rChildRect
=
new
RRect
.
fromRectAndCorners
(
childRect
,
topRight:
const
Radius
.
circular
(
3.0
),
bottomRight:
const
Radius
.
circular
(
3.0
));
}
else
{
rChildRect
=
new
RRect
.
fromRectAndCorners
(
childRect
);
}
childParentData
.
surroundingRect
=
rChildRect
;
start
+=
child
.
size
.
width
;
child
=
nextChild
(
child
);
}
}
@override
void
performLayout
()
{
double
maxHeight
=
_kMinSegmentedControlHeight
;
double
childWidth
;
if
(
constraints
.
maxWidth
.
isFinite
)
{
childWidth
=
constraints
.
maxWidth
/
childCount
;
}
else
{
childWidth
=
constraints
.
minWidth
/
childCount
;
for
(
RenderBox
child
in
getChildrenAsList
())
{
childWidth
=
math
.
max
(
childWidth
,
child
.
getMaxIntrinsicWidth
(
double
.
infinity
));
}
}
RenderBox
child
=
firstChild
;
while
(
child
!=
null
)
{
final
double
boxHeight
=
child
.
getMaxIntrinsicHeight
(
childWidth
);
maxHeight
=
math
.
max
(
maxHeight
,
boxHeight
);
child
=
childAfter
(
child
);
}
constraints
.
constrainHeight
(
maxHeight
);
final
BoxConstraints
childConstraints
=
new
BoxConstraints
.
tightFor
(
width:
childWidth
,
height:
maxHeight
,
);
child
=
firstChild
;
while
(
child
!=
null
)
{
child
.
layout
(
childConstraints
,
parentUsesSize:
true
);
child
=
childAfter
(
child
);
}
switch
(
textDirection
)
{
case
TextDirection
.
rtl
:
_layoutRects
(
childBefore
,
lastChild
,
firstChild
,
);
break
;
case
TextDirection
.
ltr
:
_layoutRects
(
childAfter
,
firstChild
,
lastChild
,
);
break
;
}
size
=
constraints
.
constrain
(
new
Size
(
childWidth
*
childCount
,
maxHeight
));
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
RenderBox
child
=
firstChild
;
int
index
=
0
;
while
(
child
!=
null
)
{
_paintChild
(
context
,
offset
,
child
,
index
);
child
=
childAfter
(
child
);
index
+=
1
;
}
}
void
_paintChild
(
PaintingContext
context
,
Offset
offset
,
RenderBox
child
,
int
childIndex
)
{
assert
(
child
!=
null
);
final
_SegmentedControlContainerBoxParentData
childParentData
=
child
.
parentData
;
Color
color
=
CupertinoColors
.
white
;
if
(
selectedIndex
!=
null
&&
selectedIndex
==
childIndex
)
{
color
=
CupertinoColors
.
activeBlue
;
}
else
if
(
pressedIndex
!=
null
&&
pressedIndex
==
childIndex
)
{
color
=
_kPressedBackground
;
}
context
.
canvas
.
drawRRect
(
childParentData
.
surroundingRect
.
shift
(
offset
),
new
Paint
()
..
color
=
color
..
style
=
PaintingStyle
.
fill
,
);
context
.
canvas
.
drawRRect
(
childParentData
.
surroundingRect
.
shift
(
offset
),
_outlinePaint
,
);
context
.
paintChild
(
child
,
childParentData
.
offset
+
offset
);
}
@override
bool
hitTestChildren
(
HitTestResult
result
,
{
@required
Offset
position
})
{
assert
(
position
!=
null
);
return
defaultHitTestChildren
(
result
,
position:
position
);
}
}
packages/flutter/test/cupertino/segmented_control_test.dart
0 → 100644
View file @
c35e484c
// Copyright 2018 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/widgets.dart'
;
import
'package:flutter/cupertino.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'../rendering/mock_canvas.dart'
;
import
'../widgets/semantics_tester.dart'
;
dynamic
getRenderSegmentedControl
(
WidgetTester
tester
)
{
return
tester
.
allRenderObjects
.
firstWhere
(
(
RenderObject
currentObject
)
{
return
currentObject
.
toStringShort
().
contains
(
'_RenderSegmentedControl'
);
},
);
}
StatefulBuilder
setupSimpleSegmentedControl
(
)
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
const
Text
(
'Child 2'
);
int
sharedValue
=
0
;
return
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
boilerplate
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
(
int
newValue
)
{
setState
(()
{
sharedValue
=
newValue
;
});
},
groupValue:
sharedValue
,
),
);
},
);
}
Widget
boilerplate
(
{
Widget
child
})
{
return
new
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
new
Center
(
child:
child
),
);
}
void
main
(
)
{
testWidgets
(
'Tap changes toggle state'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
const
Text
(
'Child 2'
);
children
[
2
]
=
const
Text
(
'Child 3'
);
int
sharedValue
=
0
;
await
tester
.
pumpWidget
(
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
boilerplate
(
child:
new
SegmentedControl
<
int
>(
key:
const
ValueKey
<
String
>(
'Segmented Control'
),
children:
children
,
onValueChanged:
(
int
newValue
)
{
setState
(()
{
sharedValue
=
newValue
;
});
},
groupValue:
sharedValue
,
),
);
},
),
);
expect
(
sharedValue
,
0
);
await
tester
.
tap
(
find
.
byKey
(
const
ValueKey
<
String
>(
'Segmented Control'
)));
expect
(
sharedValue
,
1
);
});
testWidgets
(
'Need at least 2 children'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
try
{
await
tester
.
pumpWidget
(
boilerplate
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
(
int
newValue
)
{},
),
),
);
fail
(
'Should not be possible to create a segmented control with no children'
);
}
on
AssertionError
catch
(
e
)
{
expect
(
e
.
toString
(),
contains
(
'children.length'
));
}
try
{
children
[
0
]
=
const
Text
(
'Child 1'
);
await
tester
.
pumpWidget
(
boilerplate
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
(
int
newValue
)
{},
),
),
);
fail
(
'Should not be possible to create a segmented control with just one child'
);
}
on
AssertionError
catch
(
e
)
{
expect
(
e
.
toString
(),
contains
(
'children.length'
));
}
});
testWidgets
(
'Value attribute must be the key of one of the children widgets'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
const
Text
(
'Child 2'
);
try
{
await
tester
.
pumpWidget
(
boilerplate
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
(
int
newValue
)
{},
groupValue:
2
,
),
),
);
fail
(
'Should not be possible to create segmented control in which '
'value is not the key of one of the children widgets'
);
}
on
AssertionError
catch
(
e
)
{
expect
(
e
.
toString
(),
contains
(
'children'
));
}
});
testWidgets
(
'Children and onValueChanged can not be null'
,
(
WidgetTester
tester
)
async
{
try
{
await
tester
.
pumpWidget
(
boilerplate
(
child:
new
SegmentedControl
<
int
>(
children:
null
,
onValueChanged:
(
int
newValue
)
{},
),
),
);
fail
(
'Should not be possible to create segmented control with null children'
);
}
on
AssertionError
catch
(
e
)
{
expect
(
e
.
toString
(),
contains
(
'children'
));
}
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
const
Text
(
'Child 2'
);
try
{
await
tester
.
pumpWidget
(
boilerplate
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
null
,
),
),
);
fail
(
'Should not be possible to create segmented control with null onValueChanged'
);
}
on
AssertionError
catch
(
e
)
{
expect
(
e
.
toString
(),
contains
(
'onValueChanged'
));
}
});
testWidgets
(
'Widgets have correct default text/icon styles, change correctly on selection'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
const
Icon
(
IconData
(
1
));
int
sharedValue
=
0
;
await
tester
.
pumpWidget
(
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
boilerplate
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
(
int
newValue
)
{
setState
(()
{
sharedValue
=
newValue
;
});
},
groupValue:
sharedValue
,
),
);
},
),
);
DefaultTextStyle
textStyle
=
tester
.
widget
(
find
.
widgetWithText
(
DefaultTextStyle
,
'Child 1'
));
IconTheme
iconTheme
=
tester
.
widget
(
find
.
widgetWithIcon
(
IconTheme
,
const
IconData
(
1
)));
expect
(
textStyle
.
style
.
color
,
CupertinoColors
.
white
);
expect
(
iconTheme
.
data
.
color
,
CupertinoColors
.
activeBlue
);
await
tester
.
tap
(
find
.
widgetWithIcon
(
IconTheme
,
const
IconData
(
1
)));
await
tester
.
pump
();
textStyle
=
tester
.
widget
(
find
.
widgetWithText
(
DefaultTextStyle
,
'Child 1'
));
iconTheme
=
tester
.
widget
(
find
.
widgetWithIcon
(
IconTheme
,
const
IconData
(
1
)));
expect
(
textStyle
.
style
.
color
,
CupertinoColors
.
activeBlue
);
expect
(
iconTheme
.
data
.
color
,
CupertinoColors
.
white
);
});
testWidgets
(
'Tap calls onValueChanged'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
const
Text
(
'Child 2'
);
bool
value
=
false
;
await
tester
.
pumpWidget
(
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
boilerplate
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
(
int
newValue
)
{
value
=
true
;
},
),
);
},
),
);
expect
(
value
,
isFalse
);
await
tester
.
tap
(
find
.
text
(
'Child 2'
));
expect
(
value
,
isTrue
);
});
testWidgets
(
'State does not change if onValueChanged does not call setState()'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
const
Text
(
'Child 2'
);
const
int
sharedValue
=
0
;
await
tester
.
pumpWidget
(
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
boilerplate
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
(
int
newValue
)
{},
groupValue:
sharedValue
,
),
);
},
),
);
final
dynamic
childList
=
getRenderSegmentedControl
(
tester
).
getChildrenAsList
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
(
rrect:
childList
.
elementAt
(
0
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
activeBlue
,
),
);
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
white
,
),
);
await
tester
.
tap
(
find
.
text
(
'Child 2'
));
await
tester
.
pump
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
(
rrect:
childList
.
elementAt
(
0
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
activeBlue
,
),
);
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
white
,
),
);
});
testWidgets
(
'Background color of child should change on selection, '
'and should not change when tapped again'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
setupSimpleSegmentedControl
());
final
dynamic
childList
=
getRenderSegmentedControl
(
tester
).
getChildrenAsList
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
white
,
),
);
await
tester
.
tap
(
find
.
text
(
'Child 2'
));
await
tester
.
pump
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
activeBlue
,
),
);
await
tester
.
tap
(
find
.
text
(
'Child 2'
));
await
tester
.
pump
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
activeBlue
,
),
);
});
testWidgets
(
'Children can be non-Text or Icon widgets (in this case, '
'a Container or Placeholder widget)'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
new
Container
(
constraints:
const
BoxConstraints
.
tightFor
(
width:
50.0
,
height:
50.0
),
);
children
[
2
]
=
const
Placeholder
();
int
sharedValue
=
0
;
await
tester
.
pumpWidget
(
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
boilerplate
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
(
int
newValue
)
{
setState
(()
{
sharedValue
=
newValue
;
});
},
groupValue:
sharedValue
,
),
);
},
),
);
},
);
testWidgets
(
'Passed in value is child initially selected'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
setupSimpleSegmentedControl
());
expect
(
getRenderSegmentedControl
(
tester
).
selectedIndex
,
0
);
final
dynamic
childList
=
getRenderSegmentedControl
(
tester
).
getChildrenAsList
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
(
rrect:
childList
.
elementAt
(
0
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
activeBlue
,
),
);
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
white
,
),
);
});
testWidgets
(
'Null input for value results in no child initially selected'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
const
Text
(
'Child 2'
);
int
sharedValue
;
await
tester
.
pumpWidget
(
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
boilerplate
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
(
int
newValue
)
{
setState
(()
{
sharedValue
=
newValue
;
});
},
groupValue:
sharedValue
,
),
);
},
),
);
expect
(
getRenderSegmentedControl
(
tester
).
selectedIndex
,
null
);
final
dynamic
childList
=
getRenderSegmentedControl
(
tester
).
getChildrenAsList
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
(
rrect:
childList
.
elementAt
(
0
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
white
,
),
);
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
white
,
),
);
});
testWidgets
(
'Long press changes background color of not-selected child'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
setupSimpleSegmentedControl
());
final
dynamic
childList
=
getRenderSegmentedControl
(
tester
).
getChildrenAsList
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
(
rrect:
childList
.
elementAt
(
0
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
activeBlue
,
),
);
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
white
,
),
);
final
Offset
center
=
tester
.
getCenter
(
find
.
text
(
'Child 2'
));
await
tester
.
startGesture
(
center
);
await
tester
.
pumpAndSettle
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
(
rrect:
childList
.
elementAt
(
0
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
activeBlue
,
),
);
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
const
Color
(
0x33007aff
),
),
);
});
testWidgets
(
'Long press does not change background color of currently-selected child'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
setupSimpleSegmentedControl
());
final
dynamic
childList
=
getRenderSegmentedControl
(
tester
).
getChildrenAsList
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
(
rrect:
childList
.
elementAt
(
0
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
activeBlue
,
),
);
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
white
,
),
);
final
Offset
center
=
tester
.
getCenter
(
find
.
text
(
'Child 1'
));
await
tester
.
startGesture
(
center
);
await
tester
.
pumpAndSettle
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
(
rrect:
childList
.
elementAt
(
0
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
activeBlue
,
),
);
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
white
,
),
);
});
testWidgets
(
'Height of segmented control is determined by tallest widget'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
new
Container
(
constraints:
const
BoxConstraints
.
tightFor
(
height:
100.0
),
);
children
[
1
]
=
new
Container
(
constraints:
const
BoxConstraints
.
tightFor
(
height:
400.0
),
);
children
[
2
]
=
new
Container
(
constraints:
const
BoxConstraints
.
tightFor
(
height:
200.0
),
);
await
tester
.
pumpWidget
(
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
boilerplate
(
child:
new
SegmentedControl
<
int
>(
key:
const
ValueKey
<
String
>(
'Segmented Control'
),
children:
children
,
onValueChanged:
(
int
newValue
)
{},
),
);
},
),
);
final
RenderBox
buttonBox
=
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
String
>(
'Segmented Control'
)));
// Default height of Placeholder is 400.0px, which is greater than heights
// of other child widgets.
expect
(
buttonBox
.
size
.
height
,
400.0
);
});
testWidgets
(
'Width of each child widget is the same'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
new
Container
();
children
[
1
]
=
const
Placeholder
();
children
[
2
]
=
new
Container
();
await
tester
.
pumpWidget
(
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
boilerplate
(
child:
new
SegmentedControl
<
int
>(
key:
const
ValueKey
<
String
>(
'Segmented Control'
),
children:
children
,
onValueChanged:
(
int
newValue
)
{},
),
);
},
),
);
final
RenderBox
segmentedControl
=
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
String
>(
'Segmented Control'
)));
// Subtract the 16.0px from each side. Remaining width should be allocated
// to each child equally.
final
double
childWidth
=
(
segmentedControl
.
size
.
width
-
32.0
)
/
3
;
final
dynamic
childList
=
getRenderSegmentedControl
(
tester
).
getChildrenAsList
();
for
(
dynamic
child
in
childList
)
{
expect
(
childWidth
,
child
.
parentData
.
surroundingRect
.
width
);
}
});
testWidgets
(
'Width is finite in unbounded space'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
const
Text
(
'Child 2'
);
await
tester
.
pumpWidget
(
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
boilerplate
(
child:
Row
(
children:
<
Widget
>[
new
SegmentedControl
<
int
>(
key:
const
ValueKey
<
String
>(
'Segmented Control'
),
children:
children
,
onValueChanged:
(
int
newValue
)
{},
),
],
),
);
},
),
);
final
RenderBox
segmentedControl
=
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
String
>(
'Segmented Control'
)));
expect
(
segmentedControl
.
size
.
width
.
isFinite
,
isTrue
);
});
testWidgets
(
'Directionality test - RTL should reverse order of widgets'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
const
Text
(
'Child 2'
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
rtl
,
child:
new
Center
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
(
int
newValue
)
{},
),
),
),
);
expect
(
tester
.
getTopRight
(
find
.
text
(
'Child 1'
)).
dx
>
tester
.
getTopRight
(
find
.
text
(
'Child 2'
)).
dx
,
isTrue
);
});
testWidgets
(
'Correct initial selection and toggling behavior - RTL'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
const
Text
(
'Child 2'
);
int
sharedValue
=
0
;
await
tester
.
pumpWidget
(
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
Directionality
(
textDirection:
TextDirection
.
rtl
,
child:
new
Center
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
(
int
newValue
)
{
setState
(()
{
sharedValue
=
newValue
;
});
},
groupValue:
sharedValue
,
),
),
);
},
),
);
final
dynamic
childList
=
getRenderSegmentedControl
(
tester
).
getChildrenAsList
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
(
rrect:
childList
.
elementAt
(
0
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
activeBlue
,
),
);
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
white
,
),
);
await
tester
.
tap
(
find
.
text
(
'Child 2'
));
await
tester
.
pump
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
(
rrect:
childList
.
elementAt
(
0
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
white
,
),
);
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
activeBlue
,
),
);
await
tester
.
tap
(
find
.
text
(
'Child 2'
));
await
tester
.
pump
();
expect
(
getRenderSegmentedControl
(
tester
),
paints
..
rrect
()
..
rrect
()
..
rrect
(
rrect:
childList
.
elementAt
(
1
).
parentData
.
surroundingRect
,
color:
CupertinoColors
.
activeBlue
,
),
);
});
testWidgets
(
'Segmented control semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
new
SemanticsTester
(
tester
);
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'Child 1'
);
children
[
1
]
=
const
Text
(
'Child 2'
);
int
sharedValue
=
0
;
await
tester
.
pumpWidget
(
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
new
Center
(
child:
new
SegmentedControl
<
int
>(
children:
children
,
onValueChanged:
(
int
newValue
)
{
setState
(()
{
sharedValue
=
newValue
;
});
},
groupValue:
sharedValue
,
),
),
);
},
),
);
expect
(
semantics
,
hasSemantics
(
new
TestSemantics
.
root
(
children:
<
TestSemantics
>[
new
TestSemantics
.
rootChild
(
label:
'Child 1'
,
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isInMutuallyExclusiveGroup
,
SemanticsFlag
.
isSelected
,
],
actions:
<
SemanticsAction
>[
SemanticsAction
.
tap
,
],
),
new
TestSemantics
.
rootChild
(
label:
'Child 2'
,
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isInMutuallyExclusiveGroup
,
],
actions:
<
SemanticsAction
>[
SemanticsAction
.
tap
,
],
),
],
),
ignoreId:
true
,
ignoreRect:
true
,
ignoreTransform:
true
,
));
await
tester
.
tap
(
find
.
text
(
'Child 2'
));
await
tester
.
pump
();
expect
(
semantics
,
hasSemantics
(
new
TestSemantics
.
root
(
children:
<
TestSemantics
>[
new
TestSemantics
.
rootChild
(
label:
'Child 1'
,
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isInMutuallyExclusiveGroup
,
],
actions:
<
SemanticsAction
>[
SemanticsAction
.
tap
,
],
),
new
TestSemantics
.
rootChild
(
label:
'Child 2'
,
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isInMutuallyExclusiveGroup
,
SemanticsFlag
.
isSelected
,
],
actions:
<
SemanticsAction
>[
SemanticsAction
.
tap
,
],
),
],
),
ignoreId:
true
,
ignoreRect:
true
,
ignoreTransform:
true
,
));
semantics
.
dispose
();
});
testWidgets
(
'Golden Test Placeholder Widget'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
new
Container
();
children
[
1
]
=
const
Placeholder
();
children
[
2
]
=
new
Container
();
const
int
currentValue
=
0
;
await
tester
.
pumpWidget
(
new
RepaintBoundary
(
child:
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
boilerplate
(
child:
new
SegmentedControl
<
int
>(
key:
const
ValueKey
<
String
>(
'Segmented Control'
),
children:
children
,
onValueChanged:
(
int
newValue
)
{},
groupValue:
currentValue
,
),
);
},
),
),
);
await
expectLater
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'segmented_control_test.0.0.png'
));
});
testWidgets
(
'Golden Test Pressed State'
,
(
WidgetTester
tester
)
async
{
final
Map
<
int
,
Widget
>
children
=
<
int
,
Widget
>{};
children
[
0
]
=
const
Text
(
'A'
);
children
[
1
]
=
const
Text
(
'B'
);
children
[
2
]
=
const
Text
(
'C'
);
const
int
currentValue
=
0
;
await
tester
.
pumpWidget
(
new
RepaintBoundary
(
child:
new
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
boilerplate
(
child:
new
SegmentedControl
<
int
>(
key:
const
ValueKey
<
String
>(
'Segmented Control'
),
children:
children
,
onValueChanged:
(
int
newValue
)
{},
groupValue:
currentValue
,
),
);
},
),
),
);
final
Offset
center
=
tester
.
getCenter
(
find
.
text
(
'B'
));
await
tester
.
startGesture
(
center
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
RepaintBoundary
),
matchesGoldenFile
(
'segmented_control_test.1.0.png'
));
});
}
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