"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/cupertino/segmented_control.dart" (13 Nov 2020, 24152 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // Copyright 2014 The Flutter Authors. All rights reserved.
    2 // Use of this source code is governed by a BSD-style license that can be
    3 // found in the LICENSE file.
    4 
    5 // @dart = 2.8
    6 
    7 import 'dart:collection';
    8 import 'dart:math' as math;
    9 
   10 import 'package:flutter/foundation.dart';
   11 import 'package:flutter/rendering.dart';
   12 import 'package:flutter/widgets.dart';
   13 
   14 import 'theme.dart';
   15 
   16 // Minimum padding from edges of the segmented control to edges of
   17 // encompassing widget.
   18 const EdgeInsetsGeometry _kHorizontalItemPadding = EdgeInsets.symmetric(horizontal: 16.0);
   19 
   20 // Minimum height of the segmented control.
   21 const double _kMinSegmentedControlHeight = 28.0;
   22 
   23 // The duration of the fade animation used to transition when a new widget
   24 // is selected.
   25 const Duration _kFadeDuration = Duration(milliseconds: 165);
   26 
   27 /// An iOS-style segmented control.
   28 ///
   29 /// Displays the widgets provided in the [Map] of [children] in a
   30 /// horizontal list. Used to select between a number of mutually exclusive
   31 /// options. When one option in the segmented control is selected, the other
   32 /// options in the segmented control cease to be selected.
   33 ///
   34 /// A segmented control can feature any [Widget] as one of the values in its
   35 /// [Map] of [children]. The type T is the type of the keys used
   36 /// to identify each widget and determine which widget is selected. As
   37 /// required by the [Map] class, keys must be of consistent types
   38 /// and must be comparable. The ordering of the keys will determine the order
   39 /// of the widgets in the segmented control.
   40 ///
   41 /// When the state of the segmented control changes, the widget calls the
   42 /// [onValueChanged] callback. The map key associated with the newly selected
   43 /// widget is returned in the [onValueChanged] callback. Typically, widgets
   44 /// that use a segmented control will listen for the [onValueChanged] callback
   45 /// and rebuild the segmented control with a new [groupValue] to update which
   46 /// option is currently selected.
   47 ///
   48 /// The [children] will be displayed in the order of the keys in the [Map].
   49 /// The height of the segmented control is determined by the height of the
   50 /// tallest widget provided as a value in the [Map] of [children].
   51 /// The width of each child in the segmented control will be equal to the width
   52 /// of widest child, unless the combined width of the children is wider than
   53 /// the available horizontal space. In this case, the available horizontal space
   54 /// is divided by the number of provided [children] to determine the width of
   55 /// each widget. The selection area for each of the widgets in the [Map] of
   56 /// [children] will then be expanded to fill the calculated space, so each
   57 /// widget will appear to have the same dimensions.
   58 ///
   59 /// A segmented control may optionally be created with custom colors. The
   60 /// [unselectedColor], [selectedColor], [borderColor], and [pressedColor]
   61 /// arguments can be used to override the segmented control's colors from
   62 /// [CupertinoTheme] defaults.
   63 ///
   64 /// See also:
   65 ///
   66 ///  * [CupertinoSegmentedControl], a segmented control widget in the style used
   67 ///    up until iOS 13.
   68 ///  * <https://developer.apple.com/design/human-interface-guidelines/ios/controls/segmented-controls/>
   69 class CupertinoSegmentedControl<T> extends StatefulWidget {
   70   /// Creates an iOS-style segmented control bar.
   71   ///
   72   /// The [children] and [onValueChanged] arguments must not be null. The
   73   /// [children] argument must be an ordered [Map] such as a [LinkedHashMap].
   74   /// Further, the length of the [children] list must be greater than one.
   75   ///
   76   /// Each widget value in the map of [children] must have an associated key
   77   /// that uniquely identifies this widget. This key is what will be returned
   78   /// in the [onValueChanged] callback when a new value from the [children] map
   79   /// is selected.
   80   ///
   81   /// The [groupValue] is the currently selected value for the segmented control.
   82   /// If no [groupValue] is provided, or the [groupValue] is null, no widget will
   83   /// appear as selected. The [groupValue] must be either null or one of the keys
   84   /// in the [children] map.
   85   CupertinoSegmentedControl({
   86     Key key,
   87     @required this.children,
   88     @required this.onValueChanged,
   89     this.groupValue,
   90     this.unselectedColor,
   91     this.selectedColor,
   92     this.borderColor,
   93     this.pressedColor,
   94     this.padding,
   95   }) : assert(children != null),
   96        assert(children.length >= 2),
   97        assert(onValueChanged != null),
   98        assert(
   99          groupValue == null || children.keys.any((T child) => child == groupValue),
  100          'The groupValue must be either null or one of the keys in the children map.',
  101        ),
  102        super(key: key);
  103 
  104   /// The identifying keys and corresponding widget values in the
  105   /// segmented control.
  106   ///
  107   /// The map must have more than one entry.
  108   /// This attribute must be an ordered [Map] such as a [LinkedHashMap].
  109   final Map<T, Widget> children;
  110 
  111   /// The identifier of the widget that is currently selected.
  112   ///
  113   /// This must be one of the keys in the [Map] of [children].
  114   /// If this attribute is null, no widget will be initially selected.
  115   final T groupValue;
  116 
  117   /// The callback that is called when a new option is tapped.
  118   ///
  119   /// This attribute must not be null.
  120   ///
  121   /// The segmented control passes the newly selected widget's associated key
  122   /// to the callback but does not actually change state until the parent
  123   /// widget rebuilds the segmented control with the new [groupValue].
  124   ///
  125   /// The callback provided to [onValueChanged] should update the state of
  126   /// the parent [StatefulWidget] using the [State.setState] method, so that
  127   /// the parent gets rebuilt; for example:
  128   ///
  129   /// {@tool snippet}
  130   ///
  131   /// ```dart
  132   /// class SegmentedControlExample extends StatefulWidget {
  133   ///   @override
  134   ///   State createState() => SegmentedControlExampleState();
  135   /// }
  136   ///
  137   /// class SegmentedControlExampleState extends State<SegmentedControlExample> {
  138   ///   final Map<int, Widget> children = const {
  139   ///     0: Text('Child 1'),
  140   ///     1: Text('Child 2'),
  141   ///   };
  142   ///
  143   ///   int currentValue;
  144   ///
  145   ///   @override
  146   ///   Widget build(BuildContext context) {
  147   ///     return Container(
  148   ///       child: CupertinoSegmentedControl<int>(
  149   ///         children: children,
  150   ///         onValueChanged: (int newValue) {
  151   ///           setState(() {
  152   ///             currentValue = newValue;
  153   ///           });
  154   ///         },
  155   ///         groupValue: currentValue,
  156   ///       ),
  157   ///     );
  158   ///   }
  159   /// }
  160   /// ```
  161   /// {@end-tool}
  162   final ValueChanged<T> onValueChanged;
  163 
  164   /// The color used to fill the backgrounds of unselected widgets and as the
  165   /// text color of the selected widget.
  166   ///
  167   /// Defaults to [CupertinoTheme]'s `primaryContrastingColor` if null.
  168   final Color unselectedColor;
  169 
  170   /// The color used to fill the background of the selected widget and as the text
  171   /// color of unselected widgets.
  172   ///
  173   /// Defaults to [CupertinoTheme]'s `primaryColor` if null.
  174   final Color selectedColor;
  175 
  176   /// The color used as the border around each widget.
  177   ///
  178   /// Defaults to [CupertinoTheme]'s `primaryColor` if null.
  179   final Color borderColor;
  180 
  181   /// The color used to fill the background of the widget the user is
  182   /// temporarily interacting with through a long press or drag.
  183   ///
  184   /// Defaults to the selectedColor at 20% opacity if null.
  185   final Color pressedColor;
  186 
  187   /// The CupertinoSegmentedControl will be placed inside this padding.
  188   ///
  189   /// Defaults to EdgeInsets.symmetric(horizontal: 16.0)
  190   final EdgeInsetsGeometry padding;
  191 
  192   @override
  193   _SegmentedControlState<T> createState() => _SegmentedControlState<T>();
  194 }
  195 
  196 class _SegmentedControlState<T> extends State<CupertinoSegmentedControl<T>>
  197     with TickerProviderStateMixin<CupertinoSegmentedControl<T>> {
  198   T _pressedKey;
  199 
  200   final List<AnimationController> _selectionControllers = <AnimationController>[];
  201   final List<ColorTween> _childTweens = <ColorTween>[];
  202 
  203   ColorTween _forwardBackgroundColorTween;
  204   ColorTween _reverseBackgroundColorTween;
  205   ColorTween _textColorTween;
  206 
  207   Color _selectedColor;
  208   Color _unselectedColor;
  209   Color _borderColor;
  210   Color _pressedColor;
  211 
  212   AnimationController createAnimationController() {
  213     return AnimationController(
  214       duration: _kFadeDuration,
  215       vsync: this,
  216     )..addListener(() {
  217       setState(() {
  218         // State of background/text colors has changed
  219       });
  220     });
  221   }
  222 
  223   bool _updateColors() {
  224     assert(mounted, 'This should only be called after didUpdateDependencies');
  225     bool changed = false;
  226     final Color selectedColor = widget.selectedColor ?? CupertinoTheme.of(context).primaryColor;
  227     if (_selectedColor != selectedColor) {
  228       changed = true;
  229       _selectedColor = selectedColor;
  230     }
  231     final Color unselectedColor = widget.unselectedColor ?? CupertinoTheme.of(context).primaryContrastingColor;
  232     if (_unselectedColor != unselectedColor) {
  233       changed = true;
  234       _unselectedColor = unselectedColor;
  235     }
  236     final Color borderColor = widget.borderColor ?? CupertinoTheme.of(context).primaryColor;
  237     if (_borderColor != borderColor) {
  238       changed = true;
  239       _borderColor = borderColor;
  240     }
  241     final Color pressedColor = widget.pressedColor ?? CupertinoTheme.of(context).primaryColor.withOpacity(0.2);
  242     if (_pressedColor != pressedColor) {
  243       changed = true;
  244       _pressedColor = pressedColor;
  245     }
  246 
  247     _forwardBackgroundColorTween = ColorTween(
  248       begin: _pressedColor,
  249       end: _selectedColor,
  250     );
  251     _reverseBackgroundColorTween = ColorTween(
  252       begin: _unselectedColor,
  253       end: _selectedColor,
  254     );
  255     _textColorTween = ColorTween(
  256       begin: _selectedColor,
  257       end: _unselectedColor,
  258     );
  259     return changed;
  260   }
  261 
  262   void _updateAnimationControllers() {
  263     assert(mounted, 'This should only be called after didUpdateDependencies');
  264     for (final AnimationController controller in _selectionControllers) {
  265       controller.dispose();
  266     }
  267     _selectionControllers.clear();
  268     _childTweens.clear();
  269 
  270     for (final T key in widget.children.keys) {
  271       final AnimationController animationController = createAnimationController();
  272       if (widget.groupValue == key) {
  273         _childTweens.add(_reverseBackgroundColorTween);
  274         animationController.value = 1.0;
  275       } else {
  276         _childTweens.add(_forwardBackgroundColorTween);
  277       }
  278       _selectionControllers.add(animationController);
  279     }
  280   }
  281 
  282   @override
  283   void didChangeDependencies() {
  284     super.didChangeDependencies();
  285 
  286     if (_updateColors()) {
  287       _updateAnimationControllers();
  288     }
  289   }
  290 
  291   @override
  292   void didUpdateWidget(CupertinoSegmentedControl<T> oldWidget) {
  293     super.didUpdateWidget(oldWidget);
  294 
  295     if (_updateColors() || oldWidget.children.length != widget.children.length) {
  296       _updateAnimationControllers();
  297     }
  298 
  299     if (oldWidget.groupValue != widget.groupValue) {
  300       int index = 0;
  301       for (final T key in widget.children.keys) {
  302         if (widget.groupValue == key) {
  303           _childTweens[index] = _forwardBackgroundColorTween;
  304           _selectionControllers[index].forward();
  305         } else {
  306           _childTweens[index] = _reverseBackgroundColorTween;
  307           _selectionControllers[index].reverse();
  308         }
  309         index += 1;
  310       }
  311     }
  312   }
  313 
  314   @override
  315   void dispose() {
  316     for (final AnimationController animationController in _selectionControllers) {
  317       animationController.dispose();
  318     }
  319     super.dispose();
  320   }
  321 
  322 
  323   void _onTapDown(T currentKey) {
  324     if (_pressedKey == null && currentKey != widget.groupValue) {
  325       setState(() {
  326         _pressedKey = currentKey;
  327       });
  328     }
  329   }
  330 
  331   void _onTapCancel() {
  332     setState(() {
  333       _pressedKey = null;
  334     });
  335   }
  336 
  337   void _onTap(T currentKey) {
  338     if (currentKey != _pressedKey)
  339       return;
  340     if (currentKey != widget.groupValue) {
  341       widget.onValueChanged(currentKey);
  342     }
  343     _pressedKey = null;
  344   }
  345 
  346   Color getTextColor(int index, T currentKey) {
  347     if (_selectionControllers[index].isAnimating)
  348       return _textColorTween.evaluate(_selectionControllers[index]);
  349     if (widget.groupValue == currentKey)
  350       return _unselectedColor;
  351     return _selectedColor;
  352   }
  353 
  354   Color getBackgroundColor(int index, T currentKey) {
  355     if (_selectionControllers[index].isAnimating)
  356       return _childTweens[index].evaluate(_selectionControllers[index]);
  357     if (widget.groupValue == currentKey)
  358       return _selectedColor;
  359     if (_pressedKey == currentKey)
  360       return _pressedColor;
  361     return _unselectedColor;
  362   }
  363 
  364   @override
  365   Widget build(BuildContext context) {
  366     final List<Widget> _gestureChildren = <Widget>[];
  367     final List<Color> _backgroundColors = <Color>[];
  368     int index = 0;
  369     int selectedIndex;
  370     int pressedIndex;
  371     for (final T currentKey in widget.children.keys) {
  372       selectedIndex = (widget.groupValue == currentKey) ? index : selectedIndex;
  373       pressedIndex = (_pressedKey == currentKey) ? index : pressedIndex;
  374 
  375       final TextStyle textStyle = DefaultTextStyle.of(context).style.copyWith(
  376         color: getTextColor(index, currentKey),
  377       );
  378       final IconThemeData iconTheme = IconThemeData(
  379         color: getTextColor(index, currentKey),
  380       );
  381 
  382       Widget child = Center(
  383         child: widget.children[currentKey],
  384       );
  385 
  386       child = GestureDetector(
  387         behavior: HitTestBehavior.opaque,
  388         onTapDown: (TapDownDetails event) {
  389           _onTapDown(currentKey);
  390         },
  391         onTapCancel: _onTapCancel,
  392         onTap: () {
  393           _onTap(currentKey);
  394         },
  395         child: IconTheme(
  396           data: iconTheme,
  397           child: DefaultTextStyle(
  398             style: textStyle,
  399             child: Semantics(
  400               button: true,
  401               inMutuallyExclusiveGroup: true,
  402               selected: widget.groupValue == currentKey,
  403               child: child,
  404             ),
  405           ),
  406         ),
  407       );
  408 
  409       _backgroundColors.add(getBackgroundColor(index, currentKey));
  410       _gestureChildren.add(child);
  411       index += 1;
  412     }
  413 
  414     final Widget box = _SegmentedControlRenderWidget<T>(
  415       children: _gestureChildren,
  416       selectedIndex: selectedIndex,
  417       pressedIndex: pressedIndex,
  418       backgroundColors: _backgroundColors,
  419       borderColor: _borderColor,
  420     );
  421 
  422     return Padding(
  423       padding: widget.padding ?? _kHorizontalItemPadding,
  424       child: UnconstrainedBox(
  425         constrainedAxis: Axis.horizontal,
  426         child: box,
  427       ),
  428     );
  429   }
  430 }
  431 
  432 class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget {
  433   _SegmentedControlRenderWidget({
  434     Key key,
  435     List<Widget> children = const <Widget>[],
  436     @required this.selectedIndex,
  437     @required this.pressedIndex,
  438     @required this.backgroundColors,
  439     @required this.borderColor,
  440   }) : super(
  441           key: key,
  442           children: children,
  443         );
  444 
  445   final int selectedIndex;
  446   final int pressedIndex;
  447   final List<Color> backgroundColors;
  448   final Color borderColor;
  449 
  450   @override
  451   RenderObject createRenderObject(BuildContext context) {
  452     return _RenderSegmentedControl<T>(
  453       textDirection: Directionality.of(context),
  454       selectedIndex: selectedIndex,
  455       pressedIndex: pressedIndex,
  456       backgroundColors: backgroundColors,
  457       borderColor: borderColor,
  458     );
  459   }
  460 
  461   @override
  462   void updateRenderObject(BuildContext context, _RenderSegmentedControl<T> renderObject) {
  463     renderObject
  464       ..textDirection = Directionality.of(context)
  465       ..selectedIndex = selectedIndex
  466       ..pressedIndex = pressedIndex
  467       ..backgroundColors = backgroundColors
  468       ..borderColor = borderColor;
  469   }
  470 }
  471 
  472 class _SegmentedControlContainerBoxParentData extends ContainerBoxParentData<RenderBox> {
  473   RRect surroundingRect;
  474 }
  475 
  476 typedef _NextChild = RenderBox Function(RenderBox child);
  477 
  478 class _RenderSegmentedControl<T> extends RenderBox
  479     with ContainerRenderObjectMixin<RenderBox, ContainerBoxParentData<RenderBox>>,
  480         RenderBoxContainerDefaultsMixin<RenderBox, ContainerBoxParentData<RenderBox>> {
  481   _RenderSegmentedControl({
  482     @required int selectedIndex,
  483     @required int pressedIndex,
  484     @required TextDirection textDirection,
  485     @required List<Color> backgroundColors,
  486     @required Color borderColor,
  487   }) : assert(textDirection != null),
  488        _textDirection = textDirection,
  489        _selectedIndex = selectedIndex,
  490        _pressedIndex = pressedIndex,
  491        _backgroundColors = backgroundColors,
  492        _borderColor = borderColor;
  493 
  494   int get selectedIndex => _selectedIndex;
  495   int _selectedIndex;
  496   set selectedIndex(int value) {
  497     if (_selectedIndex == value) {
  498       return;
  499     }
  500     _selectedIndex = value;
  501     markNeedsPaint();
  502   }
  503 
  504   int get pressedIndex => _pressedIndex;
  505   int _pressedIndex;
  506   set pressedIndex(int value) {
  507     if (_pressedIndex == value) {
  508       return;
  509     }
  510     _pressedIndex = value;
  511     markNeedsPaint();
  512   }
  513 
  514   TextDirection get textDirection => _textDirection;
  515   TextDirection _textDirection;
  516   set textDirection(TextDirection value) {
  517     if (_textDirection == value) {
  518       return;
  519     }
  520     _textDirection = value;
  521     markNeedsLayout();
  522   }
  523 
  524   List<Color> get backgroundColors => _backgroundColors;
  525   List<Color> _backgroundColors;
  526   set backgroundColors(List<Color> value) {
  527     if (_backgroundColors == value) {
  528       return;
  529     }
  530     _backgroundColors = value;
  531     markNeedsPaint();
  532   }
  533 
  534   Color get borderColor => _borderColor;
  535   Color _borderColor;
  536   set borderColor(Color value) {
  537     if (_borderColor == value) {
  538       return;
  539     }
  540     _borderColor = value;
  541     markNeedsPaint();
  542   }
  543 
  544   @override
  545   double computeMinIntrinsicWidth(double height) {
  546     RenderBox child = firstChild;
  547     double minWidth = 0.0;
  548     while (child != null) {
  549       final _SegmentedControlContainerBoxParentData childParentData = child.parentData as _SegmentedControlContainerBoxParentData;
  550       final double childWidth = child.getMinIntrinsicWidth(height);
  551       minWidth = math.max(minWidth, childWidth);
  552       child = childParentData.nextSibling;
  553     }
  554     return minWidth * childCount;
  555   }
  556 
  557   @override
  558   double computeMaxIntrinsicWidth(double height) {
  559     RenderBox child = firstChild;
  560     double maxWidth = 0.0;
  561     while (child != null) {
  562       final _SegmentedControlContainerBoxParentData childParentData = child.parentData as _SegmentedControlContainerBoxParentData;
  563       final double childWidth = child.getMaxIntrinsicWidth(height);
  564       maxWidth = math.max(maxWidth, childWidth);
  565       child = childParentData.nextSibling;
  566     }
  567     return maxWidth * childCount;
  568   }
  569 
  570   @override
  571   double computeMinIntrinsicHeight(double width) {
  572     RenderBox child = firstChild;
  573     double minHeight = 0.0;
  574     while (child != null) {
  575       final _SegmentedControlContainerBoxParentData childParentData = child.parentData as _SegmentedControlContainerBoxParentData;
  576       final double childHeight = child.getMinIntrinsicHeight(width);
  577       minHeight = math.max(minHeight, childHeight);
  578       child = childParentData.nextSibling;
  579     }
  580     return minHeight;
  581   }
  582 
  583   @override
  584   double computeMaxIntrinsicHeight(double width) {
  585     RenderBox child = firstChild;
  586     double maxHeight = 0.0;
  587     while (child != null) {
  588       final _SegmentedControlContainerBoxParentData childParentData = child.parentData as _SegmentedControlContainerBoxParentData;
  589       final double childHeight = child.getMaxIntrinsicHeight(width);
  590       maxHeight = math.max(maxHeight, childHeight);
  591       child = childParentData.nextSibling;
  592     }
  593     return maxHeight;
  594   }
  595 
  596   @override
  597   double computeDistanceToActualBaseline(TextBaseline baseline) {
  598     return defaultComputeDistanceToHighestActualBaseline(baseline);
  599   }
  600 
  601   @override
  602   void setupParentData(RenderBox child) {
  603     if (child.parentData is! _SegmentedControlContainerBoxParentData) {
  604       child.parentData = _SegmentedControlContainerBoxParentData();
  605     }
  606   }
  607 
  608   void _layoutRects(_NextChild nextChild, RenderBox leftChild, RenderBox rightChild) {
  609     RenderBox child = leftChild;
  610     double start = 0.0;
  611     while (child != null) {
  612       final _SegmentedControlContainerBoxParentData childParentData = child.parentData as _SegmentedControlContainerBoxParentData;
  613       final Offset childOffset = Offset(start, 0.0);
  614       childParentData.offset = childOffset;
  615       final Rect childRect = Rect.fromLTWH(start, 0.0, child.size.width, child.size.height);
  616       RRect rChildRect;
  617       if (child == leftChild) {
  618         rChildRect = RRect.fromRectAndCorners(childRect, topLeft: const Radius.circular(3.0),
  619             bottomLeft: const Radius.circular(3.0));
  620       } else if (child == rightChild) {
  621         rChildRect = RRect.fromRectAndCorners(childRect, topRight: const Radius.circular(3.0),
  622             bottomRight: const Radius.circular(3.0));
  623       } else {
  624         rChildRect = RRect.fromRectAndCorners(childRect);
  625       }
  626       childParentData.surroundingRect = rChildRect;
  627       start += child.size.width;
  628       child = nextChild(child);
  629     }
  630   }
  631 
  632   @override
  633   void performLayout() {
  634     final BoxConstraints constraints = this.constraints;
  635     double maxHeight = _kMinSegmentedControlHeight;
  636 
  637     double childWidth = constraints.minWidth / childCount;
  638     for (final RenderBox child in getChildrenAsList()) {
  639       childWidth = math.max(childWidth, child.getMaxIntrinsicWidth(double.infinity));
  640     }
  641     childWidth = math.min(childWidth, constraints.maxWidth / childCount);
  642 
  643     RenderBox child = firstChild;
  644     while (child != null) {
  645       final double boxHeight = child.getMaxIntrinsicHeight(childWidth);
  646       maxHeight = math.max(maxHeight, boxHeight);
  647       child = childAfter(child);
  648     }
  649 
  650     constraints.constrainHeight(maxHeight);
  651 
  652     final BoxConstraints childConstraints = BoxConstraints.tightFor(
  653       width: childWidth,
  654       height: maxHeight,
  655     );
  656 
  657     child = firstChild;
  658     while (child != null) {
  659       child.layout(childConstraints, parentUsesSize: true);
  660       child = childAfter(child);
  661     }
  662 
  663     switch (textDirection) {
  664       case TextDirection.rtl:
  665         _layoutRects(
  666           childBefore,
  667           lastChild,
  668           firstChild,
  669         );
  670         break;
  671       case TextDirection.ltr:
  672         _layoutRects(
  673           childAfter,
  674           firstChild,
  675           lastChild,
  676         );
  677         break;
  678     }
  679 
  680     size = constraints.constrain(Size(childWidth * childCount, maxHeight));
  681   }
  682 
  683   @override
  684   void paint(PaintingContext context, Offset offset) {
  685     RenderBox child = firstChild;
  686     int index = 0;
  687     while (child != null) {
  688       _paintChild(context, offset, child, index);
  689       child = childAfter(child);
  690       index += 1;
  691     }
  692   }
  693 
  694   void _paintChild(PaintingContext context, Offset offset, RenderBox child, int childIndex) {
  695     assert(child != null);
  696 
  697     final _SegmentedControlContainerBoxParentData childParentData = child.parentData as _SegmentedControlContainerBoxParentData;
  698 
  699     context.canvas.drawRRect(
  700       childParentData.surroundingRect.shift(offset),
  701       Paint()
  702         ..color = backgroundColors[childIndex]
  703         ..style = PaintingStyle.fill,
  704     );
  705     context.canvas.drawRRect(
  706       childParentData.surroundingRect.shift(offset),
  707       Paint()
  708         ..color = borderColor
  709         ..strokeWidth = 1.0
  710         ..style = PaintingStyle.stroke,
  711     );
  712 
  713     context.paintChild(child, childParentData.offset + offset);
  714   }
  715 
  716   @override
  717   bool hitTestChildren(BoxHitTestResult result, { @required Offset position }) {
  718     assert(position != null);
  719     RenderBox child = lastChild;
  720     while (child != null) {
  721       final _SegmentedControlContainerBoxParentData childParentData = child.parentData as _SegmentedControlContainerBoxParentData;
  722       if (childParentData.surroundingRect.contains(position)) {
  723         return result.addWithPaintOffset(
  724           offset: childParentData.offset,
  725           position: position,
  726           hitTest: (BoxHitTestResult result, Offset localOffset) {
  727             assert(localOffset == position - childParentData.offset);
  728             return child.hitTest(result, position: localOffset);
  729           },
  730         );
  731       }
  732       child = childParentData.previousSibling;
  733     }
  734     return false;
  735   }
  736 }