mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
Adding segmented control (#18373)
This commit is contained in:
parent
9da80217fc
commit
c35e484c95
|
@ -1 +1 @@
|
|||
568342373b14fab81fd665d397c8c00db3d46fca
|
||||
e4920845c80ca52c8891c023f137f1c9a651523e
|
||||
|
|
|
@ -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';
|
||||
|
|
510
packages/flutter/lib/src/cupertino/segmented_control.dart
Normal file
510
packages/flutter/lib/src/cupertino/segmented_control.dart
Normal file
|
@ -0,0 +1,510 @@
|
|||
// 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);
|
||||
}
|
||||
}
|
930
packages/flutter/test/cupertino/segmented_control_test.dart
Normal file
930
packages/flutter/test/cupertino/segmented_control_test.dart
Normal file
|
@ -0,0 +1,930 @@
|
|||
// 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'));
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue