diff --git a/packages/flutter/lib/src/cupertino/segmented_control.dart b/packages/flutter/lib/src/cupertino/segmented_control.dart
index 1340a92473f..64f0b0f897e 100644
--- a/packages/flutter/lib/src/cupertino/segmented_control.dart
+++ b/packages/flutter/lib/src/cupertino/segmented_control.dart
@@ -18,11 +18,6 @@ const EdgeInsets _kHorizontalItemPadding = const EdgeInsets.symmetric(horizontal
// 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);
-
// The duration of the fade animation used to transition when a new widget
// is selected.
const Duration _kFadeDuration = const Duration(milliseconds: 165);
@@ -58,13 +53,20 @@ const Duration _kFadeDuration = const Duration(milliseconds: 165);
/// [children] will then be expanded to fill the calculated space, so each
/// widget will appear to have the same dimensions.
///
+/// A segmented control may optionally be created with custom colors. The
+/// [unselectedColor], [selectedColor], [borderColor], and [pressedColor]
+/// arguments can be used to change the segmented control's colors from
+/// [CupertinoColors.activeBlue] and [CupertinoColors.white] to a custom
+/// configuration.
+///
/// See also:
///
/// *
class SegmentedControl extends StatefulWidget {
/// Creates an iOS-style segmented control bar.
///
- /// The [children] and [onValueChanged] arguments must not be null. The
+ /// The [children], [onValueChanged], [unselectedColor], [selectedColor],
+ /// [borderColor], and [pressedColor] 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.
///
@@ -82,10 +84,18 @@ class SegmentedControl extends StatefulWidget {
@required this.children,
@required this.onValueChanged,
this.groupValue,
+ this.unselectedColor = CupertinoColors.white,
+ this.selectedColor = CupertinoColors.activeBlue,
+ this.borderColor = CupertinoColors.activeBlue,
+ this.pressedColor = const Color(0x33007AFF),
}) : assert(children != null),
assert(children.length >= 2),
assert(onValueChanged != null),
assert(groupValue == null || children.keys.any((T child) => child == groupValue)),
+ assert(unselectedColor != null),
+ assert(selectedColor != null),
+ assert(borderColor != null),
+ assert(pressedColor != null),
super(key: key);
/// The identifying keys and corresponding widget values in the
@@ -147,6 +157,41 @@ class SegmentedControl extends StatefulWidget {
/// ```
final ValueChanged onValueChanged;
+ /// The color used to fill the backgrounds of unselected widgets and as the
+ /// text color of the selected widget.
+ ///
+ /// This attribute must not be null.
+ ///
+ /// If this attribute is unspecified, this color will default to
+ /// [CupertinoColors.white].
+ final Color unselectedColor;
+
+ /// The color used to fill the background of the selected widget and as the text
+ /// color of unselected widgets.
+ ///
+ /// This attribute must not be null.
+ ///
+ /// If this attribute is unspecified, this color will default to
+ /// [CupertinoColors.activeBlue].
+ final Color selectedColor;
+
+ /// The color used as the border around each widget.
+ ///
+ /// This attribute must not be null.
+ ///
+ /// If this attribute is unspecified, this color will default to
+ /// [CupertinoColors.activeBlue].
+ final Color borderColor;
+
+ /// The color used to fill the background of the widget the user is
+ /// temporarily interacting with through a long press or drag.
+ ///
+ /// This attribute must not be null.
+ ///
+ /// If this attribute is unspecified, this color will default to
+ /// 'Color(0x33007AFF)', a light, partially-transparent blue color.
+ final Color pressedColor;
+
@override
_SegmentedControlState createState() => _SegmentedControlState();
}
@@ -158,31 +203,33 @@ class _SegmentedControlState extends State>
final List _selectionControllers = [];
final List _childTweens = [];
- static final ColorTween forwardBackgroundColorTween = new ColorTween(
- begin: _kPressedBackground,
- end: CupertinoColors.activeBlue,
- );
-
- static final ColorTween reverseBackgroundColorTween = new ColorTween(
- begin: CupertinoColors.white,
- end: CupertinoColors.activeBlue,
- );
-
- static final ColorTween textColorTween = new ColorTween(
- begin: CupertinoColors.activeBlue,
- end: CupertinoColors.white,
- );
+ ColorTween _forwardBackgroundColorTween;
+ ColorTween _reverseBackgroundColorTween;
+ ColorTween _textColorTween;
@override
void initState() {
super.initState();
+ _forwardBackgroundColorTween = new ColorTween(
+ begin: widget.pressedColor,
+ end: widget.selectedColor,
+ );
+ _reverseBackgroundColorTween = new ColorTween(
+ begin: widget.unselectedColor,
+ end: widget.selectedColor,
+ );
+ _textColorTween = new ColorTween(
+ begin: widget.selectedColor,
+ end: widget.unselectedColor,
+ );
+
for (T key in widget.children.keys) {
final AnimationController animationController = createAnimationController();
if (widget.groupValue == key) {
- _childTweens.add(reverseBackgroundColorTween);
+ _childTweens.add(_reverseBackgroundColorTween);
animationController.value = 1.0;
} else {
- _childTweens.add(forwardBackgroundColorTween);
+ _childTweens.add(_forwardBackgroundColorTween);
}
_selectionControllers.add(animationController);
}
@@ -230,20 +277,20 @@ class _SegmentedControlState extends State>
Color getTextColor(int index, T currentKey) {
if (_selectionControllers[index].isAnimating)
- return textColorTween.evaluate(_selectionControllers[index]);
+ return _textColorTween.evaluate(_selectionControllers[index]);
if (widget.groupValue == currentKey)
- return CupertinoColors.white;
- return CupertinoColors.activeBlue;
+ return widget.unselectedColor;
+ return widget.selectedColor;
}
Color getBackgroundColor(int index, T currentKey) {
if (_selectionControllers[index].isAnimating)
return _childTweens[index].evaluate(_selectionControllers[index]);
if (widget.groupValue == currentKey)
- return CupertinoColors.activeBlue;
+ return widget.selectedColor;
if (_pressedKey == currentKey)
- return _kPressedBackground;
- return CupertinoColors.white;
+ return widget.pressedColor;
+ return widget.unselectedColor;
}
void updateAnimationControllers() {
@@ -253,7 +300,7 @@ class _SegmentedControlState extends State>
} else {
for (int index = _selectionControllers.length; index < widget.children.length; index += 1) {
_selectionControllers.add(createAnimationController());
- _childTweens.add(reverseBackgroundColorTween);
+ _childTweens.add(_reverseBackgroundColorTween);
}
}
}
@@ -270,10 +317,10 @@ class _SegmentedControlState extends State>
int index = 0;
for (T key in widget.children.keys) {
if (widget.groupValue == key) {
- _childTweens[index] = forwardBackgroundColorTween;
+ _childTweens[index] = _forwardBackgroundColorTween;
_selectionControllers[index].forward();
} else {
- _childTweens[index] = reverseBackgroundColorTween;
+ _childTweens[index] = _reverseBackgroundColorTween;
_selectionControllers[index].reverse();
}
index += 1;
@@ -332,6 +379,7 @@ class _SegmentedControlState extends State>
selectedIndex: selectedIndex,
pressedIndex: pressedIndex,
backgroundColors: _backgroundColors,
+ borderColor: widget.borderColor,
);
return new Padding(
@@ -351,6 +399,7 @@ class _SegmentedControlRenderWidget extends MultiChildRenderObjectWidget {
@required this.selectedIndex,
@required this.pressedIndex,
@required this.backgroundColors,
+ @required this.borderColor,
}) : super(
key: key,
children: children,
@@ -359,6 +408,7 @@ class _SegmentedControlRenderWidget extends MultiChildRenderObjectWidget {
final int selectedIndex;
final int pressedIndex;
final List backgroundColors;
+ final Color borderColor;
@override
RenderObject createRenderObject(BuildContext context) {
@@ -367,6 +417,7 @@ class _SegmentedControlRenderWidget extends MultiChildRenderObjectWidget {
selectedIndex: selectedIndex,
pressedIndex: pressedIndex,
backgroundColors: backgroundColors,
+ borderColor: borderColor,
);
}
@@ -376,7 +427,8 @@ class _SegmentedControlRenderWidget extends MultiChildRenderObjectWidget {
..textDirection = Directionality.of(context)
..selectedIndex = selectedIndex
..pressedIndex = pressedIndex
- ..backgroundColors = backgroundColors;
+ ..backgroundColors = backgroundColors
+ ..borderColor = borderColor;
}
}
@@ -395,11 +447,13 @@ class _RenderSegmentedControl extends RenderBox
@required int pressedIndex,
@required TextDirection textDirection,
@required List backgroundColors,
+ @required Color borderColor,
}) : assert(textDirection != null),
_textDirection = textDirection,
_selectedIndex = selectedIndex,
_pressedIndex = pressedIndex,
- _backgroundColors = backgroundColors {
+ _backgroundColors = backgroundColors,
+ _borderColor = borderColor {
addAll(children);
}
@@ -443,10 +497,15 @@ class _RenderSegmentedControl extends RenderBox
markNeedsPaint();
}
- final Paint _outlinePaint = new Paint()
- ..color = CupertinoColors.activeBlue
- ..strokeWidth = 1.0
- ..style = PaintingStyle.stroke;
+ Color get borderColor => _borderColor;
+ Color _borderColor;
+ set borderColor(Color value) {
+ if (_borderColor == value) {
+ return;
+ }
+ _borderColor = value;
+ markNeedsPaint();
+ }
@override
double computeMinIntrinsicWidth(double height) {
@@ -614,7 +673,10 @@ class _RenderSegmentedControl extends RenderBox
);
context.canvas.drawRRect(
childParentData.surroundingRect.shift(offset),
- _outlinePaint,
+ new Paint()
+ ..color = borderColor
+ ..strokeWidth = 1.0
+ ..style = PaintingStyle.stroke,
);
context.paintChild(child, childParentData.offset + offset);
diff --git a/packages/flutter/test/cupertino/segmented_control_test.dart b/packages/flutter/test/cupertino/segmented_control_test.dart
index 502724e0592..40c9f236874 100644
--- a/packages/flutter/test/cupertino/segmented_control_test.dart
+++ b/packages/flutter/test/cupertino/segmented_control_test.dart
@@ -142,7 +142,8 @@ void main() {
}
});
- testWidgets('Children and onValueChanged can not be null', (WidgetTester tester) async {
+ testWidgets('Children, onValueChanged, and color arguments can not be null',
+ (WidgetTester tester) async {
try {
await tester.pumpWidget(
boilerplate(
@@ -174,6 +175,21 @@ void main() {
} on AssertionError catch (e) {
expect(e.toString(), contains('onValueChanged'));
}
+
+ try {
+ await tester.pumpWidget(
+ boilerplate(
+ child: new SegmentedControl(
+ children: children,
+ onValueChanged: (int newValue) {},
+ unselectedColor: null,
+ ),
+ ),
+ );
+ fail('Should not be possible to create segmented control with null unselectedColor');
+ } on AssertionError catch (e) {
+ expect(e.toString(), contains('unselectedColor'));
+ }
});
testWidgets('Widgets have correct default text/icon styles, change correctly on selection',
@@ -220,6 +236,66 @@ void main() {
expect(iconTheme.data.color, CupertinoColors.white);
});
+ testWidgets('SegmentedControl is correct when user provides custom colors',
+ (WidgetTester tester) async {
+ final Map children = {};
+ 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(
+ children: children,
+ onValueChanged: (int newValue) {
+ setState(() {
+ sharedValue = newValue;
+ });
+ },
+ groupValue: sharedValue,
+ unselectedColor: CupertinoColors.lightBackgroundGray,
+ selectedColor: CupertinoColors.activeGreen,
+ borderColor: CupertinoColors.black,
+ pressedColor: const Color(0x638CFC7B),
+ ),
+ );
+ },
+ ),
+ );
+
+ await tester.pumpAndSettle();
+
+ DefaultTextStyle textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1'));
+ IconTheme iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
+
+ expect(getRenderSegmentedControl(tester).borderColor, CupertinoColors.black);
+ expect(textStyle.style.color, CupertinoColors.lightBackgroundGray);
+ expect(iconTheme.data.color, CupertinoColors.activeGreen);
+ expect(getBackgroundColor(tester, 0), CupertinoColors.activeGreen);
+ expect(getBackgroundColor(tester, 1), CupertinoColors.lightBackgroundGray);
+
+ await tester.tap(find.widgetWithIcon(IconTheme, const IconData(1)));
+ await tester.pumpAndSettle();
+
+ textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1'));
+ iconTheme = tester.widget(find.widgetWithIcon(IconTheme, const IconData(1)));
+
+ expect(textStyle.style.color, CupertinoColors.activeGreen);
+ expect(iconTheme.data.color, CupertinoColors.lightBackgroundGray);
+ expect(getBackgroundColor(tester, 0), CupertinoColors.lightBackgroundGray);
+ expect(getBackgroundColor(tester, 1), CupertinoColors.activeGreen);
+
+ final Offset center = tester.getCenter(find.text('Child 1'));
+ await tester.startGesture(center);
+ await tester.pumpAndSettle();
+
+ expect(getBackgroundColor(tester, 0), const Color(0x638CFC7B));
+ expect(getBackgroundColor(tester, 1), CupertinoColors.activeGreen);
+ });
+
testWidgets('Tap calls onValueChanged', (WidgetTester tester) async {
final Map children = {};
children[0] = const Text('Child 1');