Update CupertinoSlidingSegmentedControl control/feedback mechanism (#43932)

This commit is contained in:
LongCatIsLooong 2019-10-31 19:35:52 -07:00 committed by GitHub
parent a192e29603
commit 3cd8c3142c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 313 additions and 376 deletions

View file

@ -94,56 +94,12 @@ class _FontWeightTween extends Tween<FontWeight> {
/// argument must be an ordered [Map] such as a [LinkedHashMap], 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 changes the
/// [controller]'s value to the map key associated with the newly selected widget,
/// causing all of its listeners to be notified.
///
/// {@tool dartpad --template=stateful_widget_material}
///
/// This sample shows two [CupertinoSlidingSegmentedControl]s that mirror each other.
///
/// ```dart
/// final Map<int, Widget> children = const <int, Widget>{
/// 0: Text('Child 1'),
/// 1: Text('Child 2'),
/// 2: Text('Child 3'),
/// };
///
/// // No segment is initially selected because the controller's value is null.
/// final ValueNotifier<int> controller = ValueNotifier<int>(null);
///
/// @override
/// void initState() {
/// super.initState();
/// // Prints a message whenever the currently selected widget changes.
/// controller.addListener(() { print('selected: ${controller.value}'); });
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return Center(
/// child: Column(
/// children: <Widget>[
/// CupertinoSlidingSegmentedControl<int>(
/// children: children,
/// controller: controller,
/// ),
/// CupertinoSlidingSegmentedControl<int>(
/// children: children,
/// controller: controller,
/// ),
/// ],
/// ),
/// );
/// }
///
/// @override
/// void dispose() {
/// controller.dispose();
/// super.dispose();
/// }
/// ```
/// {@end-tool}
/// 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
@ -166,33 +122,34 @@ class _FontWeightTween extends Tween<FontWeight> {
class CupertinoSlidingSegmentedControl<T> extends StatefulWidget {
/// Creates an iOS-style segmented control bar.
///
/// The [children] and [controller] 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.
/// 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 [Map] key
/// of type [T] that uniquely identifies this widget. This key will become the
/// [controller]'s new value, when the corresponding child widget from the
/// [children] map is selected.
/// 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 [controller]'s [ValueNotifier.value] is the currently selected value for
/// the segmented control. If it is null, no widget will appear as selected. The
/// [controller]'s value must be either null or 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. The [groupValue] must be either null or one of the keys
/// in the [children] map.
CupertinoSlidingSegmentedControl({
Key key,
@required this.children,
@required this.controller,
@required this.onValueChanged,
this.groupValue,
this.thumbColor = _kThumbColor,
this.padding = _kHorizontalItemPadding,
this.backgroundColor = CupertinoColors.tertiarySystemFill,
}) : assert(children != null),
assert(children.length >= 2),
assert(padding != null),
assert(controller != null),
assert(onValueChanged != null),
assert(
controller.value == null || children.keys.any((T child) => child == controller.value),
"The controller's value must be either null or one of the keys in the children map.",
groupValue == null || children.keys.contains(groupValue),
'The groupValue must be either null or one of the keys in the children map.',
),
super(key: key);
@ -203,16 +160,58 @@ class CupertinoSlidingSegmentedControl<T> extends StatefulWidget {
/// This attribute must be an ordered [Map] such as a [LinkedHashMap].
final Map<T, Widget> children;
/// A [ValueNotifier]<[T]> that controls the currently selected child.
/// The identifier of the widget that is currently selected.
///
/// Its value must be one of the keys in the [Map] of [children], or null, in
/// which case no widget will be 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.
///
/// The [controller]'s value changes when the user drags the thumb to a different
/// child widget, or taps on a different child widget. Its value can also be
/// changed programmatically, in which case all sliding animations will play as
/// if the new selected child widget was tapped on.
final ValueNotifier<T> controller;
/// 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:
///
/// {@tool sample}
///
/// ```dart
/// class SegmentedControlExample extends StatefulWidget {
/// @override
/// State createState() => SegmentedControlExampleState();
/// }
///
/// class SegmentedControlExampleState extends State<SegmentedControlExample> {
/// final Map<int, Widget> children = const {
/// 0: Text('Child 1'),
/// 1: Text('Child 2'),
/// };
///
/// int currentValue;
///
/// @override
/// Widget build(BuildContext context) {
/// return Container(
/// child: CupertinoSlidingSegmentedControl<int>(
/// children: children,
/// onValueChanged: (int newValue) {
/// setState(() {
/// currentValue = newValue;
/// });
/// },
/// groupValue: currentValue,
/// ),
/// );
/// }
/// }
/// ```
/// {@end-tool}
final ValueChanged<T> onValueChanged;
/// The color used to paint the rounded rect behind the [children] and the separators.
///
@ -240,7 +239,7 @@ class _SegmentedControlState<T> extends State<CupertinoSlidingSegmentedControl<T
with TickerProviderStateMixin<CupertinoSlidingSegmentedControl<T>> {
final Map<T, AnimationController> _highlightControllers = <T, AnimationController>{};
final Tween<FontWeight> _highlightTween = _FontWeightTween(begin: FontWeight.normal, end: FontWeight.w600);
final Tween<FontWeight> _highlightTween = _FontWeightTween(begin: FontWeight.normal, end: FontWeight.w500);
final Map<T, AnimationController> _pressControllers = <T, AnimationController>{};
final Tween<double> _pressTween = Tween<double>(begin: 1, end: 0.2);
@ -255,8 +254,6 @@ class _SegmentedControlState<T> extends State<CupertinoSlidingSegmentedControl<T
final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer();
final LongPressGestureRecognizer longPress = LongPressGestureRecognizer();
ValueNotifier<T> controller;
AnimationController _createHighlightAnimationController({ bool isCompleted = false }) {
return AnimationController(
duration: _kHighlightAnimationDuration,
@ -284,9 +281,7 @@ class _SegmentedControlState<T> extends State<CupertinoSlidingSegmentedControl<T
drag.team = team;
team.captain = drag;
controller = widget.controller;
controller.addListener(_didChangeControllerValue);
_highlighted = controller.value;
_highlighted = widget.groupValue;
thumbController = AnimationController(
duration: _kSpringAnimationDuration,
@ -308,7 +303,7 @@ class _SegmentedControlState<T> extends State<CupertinoSlidingSegmentedControl<T
for (T currentKey in widget.children.keys) {
_highlightControllers[currentKey] = _createHighlightAnimationController(
isCompleted: currentKey == controller.value, // Highlight the current selection.
isCompleted: currentKey == widget.groupValue, // Highlight the current selection.
);
_pressControllers[currentKey] = _createFadeoutAnimationController();
}
@ -336,15 +331,7 @@ class _SegmentedControlState<T> extends State<CupertinoSlidingSegmentedControl<T
}
}
if (controller != widget.controller) {
controller.removeListener(_didChangeControllerValue);
controller = widget.controller;
controller.addListener(_didChangeControllerValue);
}
if (controller.value != oldWidget.controller.value) {
highlighted = widget.controller.value;
}
highlighted = widget.groupValue;
}
@override
@ -368,18 +355,6 @@ class _SegmentedControlState<T> extends State<CupertinoSlidingSegmentedControl<T
super.dispose();
}
void _didChangeControllerValue() {
assert(
controller.value == null || widget.children.keys.contains(controller.value),
"The controller's value ${controller.value} must be either null "
'or one of the keys in the children map: ${widget.children.keys}',
);
setState(() {
// Mark the state as dirty.
});
}
// Play highlight animation for the child located at _highlightControllers[at].
void _animateHighlightController({ T at, bool forward }) {
if (at == null)
@ -413,7 +388,7 @@ class _SegmentedControlState<T> extends State<CupertinoSlidingSegmentedControl<T
}
void didChangeSelectedViaGesture() {
controller.value = _highlighted;
widget.onValueChanged(_highlighted);
}
T indexToKey(int index) => index == null ? null : keys[index];
@ -447,9 +422,9 @@ class _SegmentedControlState<T> extends State<CupertinoSlidingSegmentedControl<T
style: textStyle,
child: Semantics(
button: true,
onTap: () { controller.value = currentKey; },
onTap: () { widget.onValueChanged(currentKey); },
inMutuallyExclusiveGroup: true,
selected: controller.value == currentKey,
selected: widget.groupValue == currentKey,
child: Opacity(
opacity: _pressTween.evaluate(_pressControllers[currentKey]),
// Expand the hitTest area to be as large as the Opacity widget.
@ -464,7 +439,7 @@ class _SegmentedControlState<T> extends State<CupertinoSlidingSegmentedControl<T
children.add(child);
}
final int selectedIndex = controller.value == null ? null : keys.indexOf(controller.value);
final int selectedIndex = widget.groupValue == null ? null : keys.indexOf(widget.groupValue);
final Widget box = _SegmentedControlRenderWidget<T>(
children: children,

View file

@ -36,32 +36,51 @@ Widget setupSimpleSegmentedControl() {
0: Text('Child 1'),
1: Text('Child 2'),
};
final ValueNotifier<int> controller = ValueNotifier<int>(0);
return boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
children: children,
controller: controller,
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
);
}
StateSetter setState;
int groupValue = 0;
void defaultCallback(int newValue) {
setState(() { groupValue = newValue; });
}
Widget boilerplate({ WidgetBuilder builder }) {
return Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setter) {
setState = setter;
return builder(context);
}),
),
);
}
Widget boilerplate({ Widget child }) {
return Directionality(
textDirection: TextDirection.ltr,
child: Center(child: child),
);
}
void main() {
testWidgets('Children and controller and padding arguments can not be null', (WidgetTester tester) async {
setUp(() {
setState = null;
groupValue = 0;
});
testWidgets('Children and onValueChanged and padding arguments can not be null', (WidgetTester tester) async {
groupValue = null;
try {
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
children: null,
controller: ValueNotifier<int>(null),
),
CupertinoSlidingSegmentedControl<int>(
children: null,
groupValue: groupValue,
onValueChanged: defaultCallback,
),
);
fail('Should not be possible to create segmented control with null children');
@ -76,26 +95,24 @@ void main() {
try {
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
children: children,
controller: null,
),
CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: null,
),
);
fail('Should not be possible to create segmented control without a controller');
fail('Should not be possible to create segmented control without an onValueChanged');
} on AssertionError catch (e) {
expect(e.toString(), contains('controller'));
expect(e.toString(), contains('onValueChanged'));
}
try {
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
children: children,
controller: ValueNotifier<int>(null),
padding: null,
),
CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
padding: null,
),
);
fail('Should not be possible to create segmented control with null padding');
@ -106,13 +123,13 @@ void main() {
testWidgets('Need at least 2 children', (WidgetTester tester) async {
final Map<int, Widget> children = <int, Widget>{};
groupValue = null;
try {
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
children: children,
controller: ValueNotifier<int>(null),
),
CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
),
);
fail('Should not be possible to create a segmented control with no children');
@ -123,11 +140,10 @@ void main() {
children[0] = const Text('Child 1');
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
children: children,
controller: ValueNotifier<int>(null),
),
CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
),
);
fail('Should not be possible to create a segmented control with just one child');
@ -135,20 +151,20 @@ void main() {
expect(e.toString(), contains('children.length'));
}
groupValue = -1;
try {
children[1] = const Text('Child 2');
children[2] = const Text('Child 3');
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
children: children,
controller: ValueNotifier<int>(-1),
),
CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
),
);
fail('Should not be possible to create a segmented control with a controller pointing to a non-existent child');
fail('Should not be possible to create a segmented control with a groupValue pointing to a non-existent child');
} on AssertionError catch (e) {
expect(e.toString(), contains('value must be either null or one of the keys in the children map'));
expect(e.toString(), contains('groupValue must be either null or one of the keys in the children map'));
}
});
@ -185,11 +201,14 @@ void main() {
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
key: key,
children: children,
controller: ValueNotifier<int>(null),
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
key: key,
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
);
@ -204,12 +223,15 @@ void main() {
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
key: key,
padding: const EdgeInsets.fromLTRB(1, 3, 5, 7),
children: children,
controller: ValueNotifier<int>(null),
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
key: key,
padding: const EdgeInsets.fromLTRB(1, 3, 5, 7),
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
);
@ -230,119 +252,29 @@ void main() {
2: Text('Child 3'),
};
final ValueNotifier<int> controller = ValueNotifier<int>(0);
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
controller: controller,
),
),
);
expect(controller.value, 0);
await tester.tap(find.text('Child 2'));
expect(controller.value, 1);
// Tapping the currently selected item should not change controller's value.
bool valueChanged = false;
controller.addListener(() { valueChanged = true; });
await tester.tap(find.text('Child 2'));
expect(valueChanged, isFalse);
expect(controller.value, 1);
});
testWidgets('Changing controller works', (WidgetTester tester) async {
const Map<int, Widget> children = <int, Widget>{
0: Text('Child 1'),
1: Text('Child 2'),
2: Text('Child 3'),
};
final ValueNotifier<int> controller = ValueNotifier<int>(0);
final ValueNotifier<int> newControlelr = ValueNotifier<int>(null);
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
controller: controller,
),
),
);
expect(
currentUnscaledThumbRect(tester, useGlobalCoordinate: true).center,
offsetMoreOrLessEquals(tester.getCenter(find.text('Child 1'))),
);
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
controller: newControlelr,
),
),
);
expect(
currentUnscaledThumbRect(tester, useGlobalCoordinate: true),
isNull,
);
});
testWidgets('Can change controller value in build method', (WidgetTester tester) async {
const Map<int, Widget> children = <int, Widget>{
0: Text('Child 1'),
1: Text('Child 2'),
2: Text('Child 3'),
};
int currentIndex = 0;
StateSetter setState;
final ValueNotifier<int> controller = ValueNotifier<int>(currentIndex);
await tester.pumpWidget(
StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
setState = setter;
if (controller.value != currentIndex)
controller.value = currentIndex;
return boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
controller: controller,
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
);
expect(
currentUnscaledThumbRect(tester, useGlobalCoordinate: true).center,
offsetMoreOrLessEquals(tester.getCenter(find.text('Child 1'))),
);
expect(groupValue, 0);
setState(() {
currentIndex = 2;
});
await tester.tap(find.text('Child 2'));
await tester.pump();
await tester.pumpAndSettle();
expect(groupValue, 1);
expect(
currentUnscaledThumbRect(tester, useGlobalCoordinate: true).center,
offsetMoreOrLessEquals(tester.getCenter(find.text('Child 3')), epsilon: 0.01),
);
// Tapping the currently selected item should not change groupValue.
await tester.tap(find.text('Child 2'));
expect(groupValue, 1);
});
testWidgets(
@ -353,16 +285,15 @@ void main() {
1: Icon(IconData(1)),
};
final ValueNotifier<int> controller = ValueNotifier<int>(0);
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.dark),
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
home: boilerplate(
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
children: children,
controller: controller,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
@ -371,7 +302,7 @@ void main() {
DefaultTextStyle textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1').first);
expect(textStyle.style.fontWeight, FontWeight.w600);
expect(textStyle.style.fontWeight, FontWeight.w500);
await tester.tap(find.byIcon(const IconData(1)));
await tester.pump();
@ -379,6 +310,7 @@ void main() {
textStyle = tester.widget(find.widgetWithText(DefaultTextStyle, 'Child 1').first);
expect(groupValue, 1);
expect(textStyle.style.fontWeight, FontWeight.normal);
},
);
@ -389,7 +321,6 @@ void main() {
1: Icon(IconData(1)),
};
final ValueNotifier<int> controller = ValueNotifier<int>(0);
Brightness brightness = Brightness.light;
StateSetter setState;
@ -400,12 +331,15 @@ void main() {
return MediaQuery(
data: MediaQueryData(platformBrightness: brightness),
child: boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
children: children,
controller: controller,
thumbColor: CupertinoColors.systemGreen,
backgroundColor: CupertinoColors.systemRed,
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
thumbColor: CupertinoColors.systemGreen,
backgroundColor: CupertinoColors.systemRed,
);
},
),
);
},
@ -443,14 +377,15 @@ void main() {
2: Placeholder(),
};
final ValueNotifier<int> controller = ValueNotifier<int>(0);
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
children: children,
controller: controller,
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
);
},
@ -468,16 +403,18 @@ void main() {
1: Text('Child 2'),
};
final ValueNotifier<int> controller = ValueNotifier<int>(null);
groupValue = null;
await tester.pumpWidget(
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
children: children,
controller: controller,
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
);
},
),
@ -496,14 +433,17 @@ void main() {
};
// Child 3 is intially selected.
final ValueNotifier<int> controller = ValueNotifier<int>(2);
groupValue = 2;
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
children: children,
controller: controller,
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
);
@ -571,15 +511,16 @@ void main() {
2: Container(constraints: const BoxConstraints.tightFor(height: 200.0)),
};
final ValueNotifier<int> controller = ValueNotifier<int>(null);
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
controller: controller,
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
);
@ -600,14 +541,16 @@ void main() {
2: Container(constraints: const BoxConstraints.tightFor(width: 200.0)),
};
final ValueNotifier<int> controller = ValueNotifier<int>(null);
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
controller: controller,
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
);
@ -628,19 +571,20 @@ void main() {
1: SizedBox(width: 70),
};
final ValueNotifier<int> controller = ValueNotifier<int>(null);
await tester.pumpWidget(
boilerplate(
child: Row(
children: <Widget>[
CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
controller: controller,
),
],
),
builder: (BuildContext context) {
return Row(
children: <Widget>[
CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
),
],
);
},
),
);
@ -660,14 +604,19 @@ void main() {
1: Text('Child 2'),
};
final ValueNotifier<int> controller = ValueNotifier<int>(null);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: Center(
child: CupertinoSlidingSegmentedControl<int>(
children: children,
controller: controller,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
setState = setter;
return CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
),
),
@ -682,16 +631,19 @@ void main() {
0: Text('Child 1'),
1: Text('Child 2'),
};
final ValueNotifier<int> controller = ValueNotifier<int>(0);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: Center(
child: CupertinoSlidingSegmentedControl<int>(
children: children,
controller: controller,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
setState = setter;
return CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
),
),
@ -718,17 +670,15 @@ void main() {
1: Text('Child 2'),
};
final ValueNotifier<int> controller = ValueNotifier<int>(0);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: CupertinoSlidingSegmentedControl<int>(
boilerplate(
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
children: children,
controller: controller,
),
),
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
);
@ -810,25 +760,26 @@ void main() {
children[0] = const Text('Child 1');
children[1] = const SizedBox();
final ValueNotifier<int> controller = ValueNotifier<int>(0);
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
controller: controller,
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
);
expect(controller.value, 0);
expect(groupValue, 0);
final Offset centerOfTwo = tester.getCenter(find.byWidget(children[1]));
// Tap just inside segment bounds
await tester.tapAt(centerOfTwo + const Offset(10, 0));
expect(controller.value, 1);
expect(groupValue, 1);
});
testWidgets('Thumb animation is correct when the selected segment changes', (WidgetTester tester) async {
@ -917,20 +868,23 @@ void main() {
1: Text('B'),
2: Text('C'),
};
final ValueNotifier<int> controller = ValueNotifier<int>(0);
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
controller: controller,
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
);
await tester.tap(find.text('B'));
await tester.pump();
await tester.pump();
await tester.pump(const Duration(milliseconds: 40));
// Between A and B.
@ -965,15 +919,16 @@ void main() {
children[2] = const Text('C');
children[3] = const Text('D');
final ValueNotifier<int> controller = ValueNotifier<int>(0);
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
controller: controller,
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
);
@ -984,11 +939,14 @@ void main() {
children[1] = const Text('B');
await tester.pumpWidget(
boilerplate(
child: CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
controller: controller,
),
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
key: const ValueKey<String>('Segmented Control'),
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
);
@ -1005,7 +963,6 @@ void main() {
0: Text('Child 1'),
1: Text('Child 2'),
};
final ValueNotifier<int> controller = ValueNotifier<int>(0);
final ScrollController scrollController = ScrollController();
await tester.pumpWidget(
@ -1015,9 +972,14 @@ void main() {
controller: scrollController,
children: <Widget>[
const SizedBox(height: 100),
CupertinoSlidingSegmentedControl<int>(
children: children,
controller: controller,
boilerplate(
builder: (BuildContext context) {
return CupertinoSlidingSegmentedControl<int>(
children: children,
groupValue: groupValue,
onValueChanged: defaultCallback,
);
},
),
const SizedBox(height: 1000),
],
@ -1029,7 +991,7 @@ void main() {
await tester.tap(find.text('Child 2'));
await tester.pump();
expect(controller.value, 1);
expect(groupValue, 1);
// Vertical drag works for the scroll view.
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Child 1')));
@ -1043,14 +1005,14 @@ void main() {
expect(scrollController.offset, 100);
// Does not affect the segmented control.
expect(controller.value, 1);
expect(groupValue, 1);
await gesture.moveBy(const Offset(0, 100));
await gesture.up();
await tester.pump();
expect(scrollController.offset, 0);
expect(controller.value, 1);
expect(groupValue, 1);
// Long press vertical drag is recognized by the segmented control.
await gesture.down(tester.getCenter(find.text('Child 1')));
@ -1061,7 +1023,7 @@ void main() {
// Should not scroll.
expect(scrollController.offset, 0);
expect(controller.value, 1);
expect(groupValue, 1);
await gesture.moveBy(const Offset(0, 100));
await gesture.moveBy(const Offset(0, 100));
@ -1069,7 +1031,7 @@ void main() {
await tester.pump();
expect(scrollController.offset, 0);
expect(controller.value, 0);
expect(groupValue, 0);
// Horizontal drag is recognized by the segmentedControl.
await gesture.down(tester.getCenter(find.text('Child 1')));
@ -1079,6 +1041,6 @@ void main() {
await tester.pump();
expect(scrollController.offset, 0);
expect(controller.value, 1);
expect(groupValue, 1);
});
}