`CupertinoSlidingSegmentedControl` is able to have proportional layout based on segment content (#153125)

diff --git a/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart b/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart
index f760f68..3d56616 100644
--- a/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart
+++ b/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart
@@ -6,7 +6,9 @@
 library;
 
 import 'dart:math' as math;
+import 'dart:math';
 
+import 'package:collection/collection.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/physics.dart';
@@ -323,6 +325,7 @@
     this.thumbColor = _kThumbColor,
     this.padding = _kHorizontalItemPadding,
     this.backgroundColor = CupertinoColors.tertiarySystemFill,
+    this.proportionalWidth = false,
   }) : assert(children.length >= 2),
        assert(
          groupValue == null || children.keys.contains(groupValue),
@@ -395,6 +398,21 @@
   /// will not be painted if null is specified.
   final Color backgroundColor;
 
+  /// Determine whether segments have proportional widths based on their content.
+  ///
+  /// If false, all segments will have the same width, determined by the longest
+  /// segment. If true, each segment's width will be determined by its individual
+  /// content.
+  ///
+  /// If the max width of parent constraints is smaller than the width that the
+  /// segmented control needs, The segment widths will scale down proportionally
+  /// to ensure the segment control fits within the boundaries; similarly, if
+  /// the min width of parent constraints is larger, the segment width will scales
+  /// up to meet the min width requirement.
+  ///
+  /// Defaults to false.
+  final bool proportionalWidth;
+
   /// The color used to paint the interior of the thumb that appears behind the
   /// currently selected item.
   ///
@@ -422,10 +440,12 @@
   final TapGestureRecognizer tap = TapGestureRecognizer();
   final HorizontalDragGestureRecognizer drag = HorizontalDragGestureRecognizer();
   final LongPressGestureRecognizer longPress = LongPressGestureRecognizer();
+  final GlobalKey segmentedControlRenderWidgetKey = GlobalKey();
 
   @override
   void initState() {
     super.initState();
+
     // If the long press or horizontal drag recognizer gets accepted, we know for
     // sure the gesture is meant for the segmented control. Hand everything to
     // the drag gesture recognizer.
@@ -485,23 +505,24 @@
   // them from interfering with the active drag gesture.
   bool get isThumbDragging => _startedOnSelectedSegment ?? false;
 
-  // Converts local coordinate to segments. This method assumes each segment has
-  // the same width.
+  // Converts local coordinate to segments.
   T segmentForXPosition(double dx) {
-    final RenderBox renderBox = context.findRenderObject()! as RenderBox;
+    final BuildContext currentContext = segmentedControlRenderWidgetKey.currentContext!;
+    final _RenderSegmentedControl<T> renderBox = currentContext.findRenderObject()! as _RenderSegmentedControl<T>;
+
     final int numOfChildren = widget.children.length;
     assert(renderBox.hasSize);
     assert(numOfChildren >= 2);
-    int index = (dx ~/ (renderBox.size.width / numOfChildren)).clamp(0, numOfChildren - 1);
+
+    int segmentIndex = renderBox.getClosestSegmentIndex(dx);
 
     switch (Directionality.of(context)) {
       case TextDirection.ltr:
         break;
       case TextDirection.rtl:
-        index = numOfChildren - 1 - index;
+        segmentIndex = numOfChildren - 1 - segmentIndex;
     }
-
-    return widget.children.keys.elementAt(index);
+    return widget.children.keys.elementAt(segmentIndex);
   }
 
   bool _hasDraggedTooFar(DragUpdateDetails details) {
@@ -696,9 +717,11 @@
           animation: thumbScaleAnimation,
           builder: (BuildContext context, Widget? child) {
             return _SegmentedControlRenderWidget<T>(
+              key: segmentedControlRenderWidgetKey,
               highlightedIndex: highlightedIndex,
               thumbColor: CupertinoDynamicColor.resolve(widget.thumbColor, context),
               thumbScale: thumbScaleAnimation.value,
+              proportionalWidth: widget.proportionalWidth,
               state: this,
               children: children,
             );
@@ -716,12 +739,14 @@
     required this.highlightedIndex,
     required this.thumbColor,
     required this.thumbScale,
+    required this.proportionalWidth,
     required this.state,
   });
 
   final int? highlightedIndex;
   final Color thumbColor;
   final double thumbScale;
+  final bool proportionalWidth;
   final _SegmentedControlState<T> state;
 
   @override
@@ -730,6 +755,7 @@
       highlightedIndex: highlightedIndex,
       thumbColor: thumbColor,
       thumbScale: thumbScale,
+      proportionalWidth: proportionalWidth,
       state: state,
     );
   }
@@ -740,7 +766,8 @@
     renderObject
       ..thumbColor = thumbColor
       ..thumbScale = thumbScale
-      ..highlightedIndex = highlightedIndex;
+      ..highlightedIndex = highlightedIndex
+      ..proportionalWidth = proportionalWidth;
   }
 }
 
@@ -785,10 +812,12 @@
     required int? highlightedIndex,
     required Color thumbColor,
     required double thumbScale,
+    required bool proportionalWidth,
     required this.state,
   }) : _highlightedIndex = highlightedIndex,
        _thumbColor = thumbColor,
-       _thumbScale = thumbScale;
+       _thumbScale = thumbScale,
+       _proportionalWidth = proportionalWidth;
 
   final _SegmentedControlState<T> state;
 
@@ -841,6 +870,16 @@
     markNeedsPaint();
   }
 
+  bool get proportionalWidth => _proportionalWidth;
+  bool _proportionalWidth;
+  set proportionalWidth(bool value) {
+    if (_proportionalWidth == value) {
+      return;
+    }
+    _proportionalWidth = value;
+    markNeedsLayout();
+  }
+
   @override
   void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
     assert(debugHandleEvent(event, entry));
@@ -853,8 +892,29 @@
   }
 
   // Intrinsic Dimensions
+  double get separatorWidth => _kSeparatorInset.horizontal + _kSeparatorWidth;
+  double get totalSeparatorWidth => separatorWidth * (childCount ~/ 2);
 
-  double get totalSeparatorWidth => (_kSeparatorInset.horizontal + _kSeparatorWidth) * (childCount ~/ 2);
+  int getClosestSegmentIndex(double dx) {
+    int index = 0;
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
+      final double clampX = clampDouble(dx, childParentData.offset.dx, child.size.width + childParentData.offset.dx);
+
+      if (dx <= clampX) {
+        break;
+      }
+
+      index++;
+      child = nonSeparatorChildAfter(child);
+    }
+
+    final int segmentCount = childCount ~/ 2 + 1;
+    // When the thumb is dragging out of bounds, the return result must be
+    // smaller than segment count.
+    return min(index, segmentCount - 1);
+  }
 
   RenderBox? nonSeparatorChildAfter(RenderBox child) {
     final RenderBox? nextChild = childAfter(child);
@@ -923,62 +983,106 @@
     }
   }
 
-  Size _calculateChildSize(BoxConstraints constraints) {
+  double _getMaxChildWidth(BoxConstraints constraints) {
     final int childCount = this.childCount ~/ 2 + 1;
     double childWidth = (constraints.minWidth - totalSeparatorWidth) / childCount;
-    double maxHeight = _kMinSegmentedControlHeight;
     RenderBox? child = firstChild;
     while (child != null) {
       childWidth = math.max(childWidth, child.getMaxIntrinsicWidth(double.infinity) + 2 * _kSegmentMinPadding);
       child = nonSeparatorChildAfter(child);
     }
-    childWidth = math.min(
+    return math.min(
       childWidth,
       (constraints.maxWidth - totalSeparatorWidth) / childCount,
     );
-    child = firstChild;
+  }
+
+  double _getMaxChildHeight(BoxConstraints constraints, double childWidth) {
+    double maxHeight = _kMinSegmentedControlHeight;
+    RenderBox? child = firstChild;
     while (child != null) {
       final double boxHeight = child.getMaxIntrinsicHeight(childWidth);
       maxHeight = math.max(maxHeight, boxHeight);
       child = nonSeparatorChildAfter(child);
     }
-    return Size(childWidth, maxHeight);
+    return maxHeight;
   }
 
-  Size _computeOverallSizeFromChildSize(Size childSize, BoxConstraints constraints) {
-    final int childCount = this.childCount ~/ 2 + 1;
-    return constraints.constrain(Size(childSize.width * childCount + totalSeparatorWidth, childSize.height));
+  List<double> _getChildWidths(BoxConstraints constraints) {
+    if (!proportionalWidth) {
+      final double maxChildWidth = _getMaxChildWidth(constraints);
+      final int segmentCount = childCount ~/ 2 + 1;
+      return List<double>.filled(segmentCount, maxChildWidth);
+    }
+
+    final List<double> segmentWidths = <double>[];
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final double childWidth = child.getMaxIntrinsicWidth(double.infinity) + 2 * _kSegmentMinPadding;
+      child = nonSeparatorChildAfter(child);
+      segmentWidths.add(childWidth);
+    }
+
+    final double totalWidth = segmentWidths.sum;
+
+    // If the sum of the children's width is larger than the allowed max width,
+    // each segment width should scale down until the overall size can fit in
+    // the parent constraints; similarly, if the sum of the children's width is
+    // smaller than the allowed min width, each segment width should scale up
+    // until the overall size can fit in the parent constraints.
+    final double allowedMaxWidth = constraints.maxWidth - totalSeparatorWidth;
+    final double allowedMinWidth = constraints.minWidth - totalSeparatorWidth;
+
+    final double scale = clampDouble(totalWidth, allowedMinWidth, allowedMaxWidth) / totalWidth;
+    if (scale != 1) {
+      for (int i = 0; i < segmentWidths.length; i++) {
+        segmentWidths[i] = segmentWidths[i] * scale;
+      }
+    }
+    return segmentWidths;
+  }
+
+  Size _computeOverallSize(BoxConstraints constraints) {
+    final double maxChildHeight = _getMaxChildHeight(constraints, constraints.maxWidth);
+    return constraints.constrain(Size(_getChildWidths(constraints).sum + totalSeparatorWidth, maxChildHeight));
   }
 
   @override
   double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
-    final Size childSize = _calculateChildSize(constraints);
-    final BoxConstraints childConstraints = BoxConstraints.tight(childSize);
+    final List<double> segmentWidths = _getChildWidths(constraints);
+    final double childHeight = _getMaxChildHeight(constraints, constraints.maxWidth);
 
+    int index = 0;
     BaselineOffset baselineOffset = BaselineOffset.noBaseline;
-    for (RenderBox? child = firstChild; child != null; child = childAfter(child)) {
+    RenderBox? child = firstChild;
+    while (child != null) {
+      final BoxConstraints childConstraints = BoxConstraints.tight(Size(segmentWidths[index], childHeight));
       baselineOffset = baselineOffset.minOf(BaselineOffset(child.getDryBaseline(childConstraints, baseline)));
+
+      child = nonSeparatorChildAfter(child);
+      index++;
     }
+
     return baselineOffset.offset;
   }
 
   @override
   Size computeDryLayout(BoxConstraints constraints) {
-    final Size childSize = _calculateChildSize(constraints);
-    return _computeOverallSizeFromChildSize(childSize, constraints);
+    return _computeOverallSize(constraints);
   }
 
   @override
   void performLayout() {
     final BoxConstraints constraints = this.constraints;
-    final Size childSize = _calculateChildSize(constraints);
-    final BoxConstraints childConstraints = BoxConstraints.tight(childSize);
-    final BoxConstraints separatorConstraints = childConstraints.heightConstraints();
+    final List<double> segmentWidths = _getChildWidths(constraints);
 
+    final double childHeight = _getMaxChildHeight(constraints, double.infinity);
+    final BoxConstraints separatorConstraints = BoxConstraints(minHeight: childHeight, maxHeight: childHeight);
     RenderBox? child = firstChild;
     int index = 0;
     double start = 0;
     while (child != null) {
+      final BoxConstraints childConstraints = BoxConstraints.tight(Size(segmentWidths[index ~/ 2], childHeight));
       child.layout(index.isEven ? childConstraints : separatorConstraints, parentUsesSize: true);
       final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
       final Offset childOffset = Offset(start, 0);
@@ -991,8 +1095,7 @@
       child = childAfter(child);
       index += 1;
     }
-
-    size = _computeOverallSizeFromChildSize(childSize, constraints);
+    size = _computeOverallSize(constraints);
   }
 
   // This method is used to convert the original unscaled thumb rect painted in
diff --git a/packages/flutter/test/cupertino/sliding_segmented_control_test.dart b/packages/flutter/test/cupertino/sliding_segmented_control_test.dart
index 166257b..a5975b7 100644
--- a/packages/flutter/test/cupertino/sliding_segmented_control_test.dart
+++ b/packages/flutter/test/cupertino/sliding_segmented_control_test.dart
@@ -510,7 +510,7 @@
     );
   });
 
-  testWidgets('Width of each segmented control segment is determined by widest widget', (WidgetTester tester) async {
+  testWidgets('Width of each segmented control segment is determined by widest widget by default', (WidgetTester tester) async {
     final Map<int, Widget> children = <int, Widget>{
       0: Container(constraints: const BoxConstraints.tightFor(width: 50.0)),
       1: Container(constraints: const BoxConstraints.tightFor(width: 100.0)),
@@ -541,6 +541,266 @@
     expect(childWidth, 200.0 + 9.25 * 2);
   });
 
+  testWidgets('If proportionalWidth is true, the width of each segmented '
+  'control segment is determined by its own content', (WidgetTester tester) async {
+    final Map<int, Widget> children = <int, Widget>{
+      0: const SizedBox(width: 50, child: Text('First')),
+      1: const SizedBox(width: 100, child: Text('Second')),
+      2: const SizedBox(width: 70, child: Text('Third')),
+    };
+
+    await tester.pumpWidget(
+      boilerplate(
+        builder: (BuildContext context) {
+          return CupertinoSlidingSegmentedControl<int>(
+            key: const ValueKey<String>('Segmented Control'),
+            children: children,
+            groupValue: groupValue,
+            proportionalWidth: true,
+            onValueChanged: defaultCallback,
+          );
+        },
+      ),
+    );
+
+    Size getChildSize(int index) {
+      return tester.getSize(
+        find.ancestor(
+          of: find.byWidget(children[index]!),
+          matching: find.byType(MetaData)
+        )
+      );
+    }
+
+    final Size firstChildSize = getChildSize(0);
+    expect(firstChildSize.width, 50 + 9.25 * 2);
+
+    final Size secondChildSize = getChildSize(1);
+    expect(secondChildSize.width, 100 + 9.25 * 2);
+
+    final Size thirdChildSize = getChildSize(2);
+    expect(thirdChildSize.width, 70 + 9.25 * 2);
+
+    // Overall segment control width is the sum of the segment widths + horizontal paddings + 2 separator width.
+    final RenderBox segmentedControl = tester.renderObject(
+      find.byKey(const ValueKey<String>('Segmented Control')),
+    );
+
+    final double childWidthSum = firstChildSize.width + secondChildSize.width + thirdChildSize.width;
+    expect(segmentedControl.size.width, childWidthSum + 6.0 + 2.0);
+  });
+
+  testWidgets('proportionalWidth rebuild', (WidgetTester tester) async {
+    final Map<int, Widget> children = <int, Widget>{
+      0: const SizedBox(width: 50, child: Text('First')),
+      1: const SizedBox(width: 200, child: Text('Second')),
+      2: const SizedBox(width: 70, child: Text('Third')),
+    };
+    bool proportionalWidth = false;
+
+    await tester.pumpWidget(
+      boilerplate(
+        builder: (BuildContext context) {
+          return CupertinoSlidingSegmentedControl<int>(
+            key: const ValueKey<String>('Segmented Control'),
+            children: children,
+            proportionalWidth: proportionalWidth,
+            groupValue: groupValue,
+            onValueChanged: defaultCallback,
+          );
+        },
+      ),
+    );
+
+    Size getChildSize(int index) {
+      return tester.getSize(
+        find.ancestor(
+          of: find.byWidget(children[index]!),
+          matching: find.byType(MetaData)
+        )
+      );
+    }
+
+    Size firstChildSize = getChildSize(0);
+    expect(firstChildSize.width, 200 + 9.25 * 2);
+
+    Size secondChildSize = getChildSize(1);
+    expect(secondChildSize.width, 200 + 9.25 * 2);
+
+    Size thirdChildSize = getChildSize(2);
+    expect(thirdChildSize.width, 200 + 9.25 * 2);
+
+    setState!(() { proportionalWidth = true; });
+    await tester.pump();
+
+    firstChildSize = getChildSize(0);
+    expect(firstChildSize.width, 50 + 9.25 * 2);
+
+    secondChildSize = getChildSize(1);
+    expect(secondChildSize.width, 200 + 9.25 * 2);
+
+    thirdChildSize = getChildSize(2);
+    expect(thirdChildSize.width, 70 + 9.25 * 2);
+  });
+
+  testWidgets('If proportionalWidth is true, the width of each segmented '
+  'control segment is updated when children change', (WidgetTester tester) async {
+    Map<int, Widget> children = <int, Widget>{
+      0: const SizedBox(width: 50, child: Text('First')),
+      1: const SizedBox(width: 100, child: Text('Second')),
+      2: const SizedBox(width: 70, child: Text('Third')),
+    };
+
+    await tester.pumpWidget(
+      boilerplate(
+        builder: (BuildContext context) {
+          return CupertinoSlidingSegmentedControl<int>(
+            key: const ValueKey<String>('Segmented Control'),
+            children: children,
+            groupValue: groupValue,
+            proportionalWidth: true,
+            onValueChanged: defaultCallback,
+          );
+        },
+      ),
+    );
+
+    Size getChildSize(int index) {
+      return tester.getSize(
+        find.ancestor(
+          of: find.byWidget(children[index]!),
+          matching: find.byType(MetaData)
+        )
+      );
+    }
+
+    Size firstChildSize = getChildSize(0);
+    expect(firstChildSize.width, 50 + 9.25 * 2);
+
+    Size secondChildSize = getChildSize(1);
+    expect(secondChildSize.width, 100 + 9.25 * 2);
+
+    Size thirdChildSize = getChildSize(2);
+    expect(thirdChildSize.width, 70 + 9.25 * 2);
+
+    setState!(() {
+      children = <int, Widget>{
+        0: const SizedBox(),
+        1: const SizedBox(width: 220, child: Text('Second')),
+        2: const SizedBox(width: 170, child: Text('Third')),
+      };
+    });
+    await tester.pump();
+
+    firstChildSize = getChildSize(0);
+    expect(firstChildSize.width, 0 + 9.25 * 2);
+
+    secondChildSize = getChildSize(1);
+    expect(secondChildSize.width, 220 + 9.25 * 2);
+
+    thirdChildSize = getChildSize(2);
+    expect(thirdChildSize.width, 170 + 9.25 * 2);
+  });
+
+
+  testWidgets('If proportionalWidth is true and the overall segment control width '
+  'is larger than the max width of the parent constraints, each segment scales down', (WidgetTester tester) async {
+    final Map<int, Widget> children = <int, Widget>{
+      0: const SizedBox(width: 50, child: Text('First')),
+      1: const SizedBox(width: 100, child: Text('Second')),
+      2: const SizedBox(width: 200, child: Text('Third')),
+    };
+
+    await tester.pumpWidget(
+      boilerplate(
+        builder: (BuildContext context) {
+          return ConstrainedBox(
+            constraints: const BoxConstraints(maxWidth: 200),
+            child: CupertinoSlidingSegmentedControl<int>(
+              key: const ValueKey<String>('Segmented Control'),
+              children: children,
+              groupValue: groupValue,
+              proportionalWidth: true,
+              onValueChanged: defaultCallback,
+            ),
+          );
+        },
+      ),
+    );
+
+    Size getChildSize(int index) {
+      return tester.getSize(
+        find.ancestor(
+          of: find.byWidget(children[index]!),
+          matching: find.byType(MetaData)
+        )
+      );
+    }
+
+    // Without constraints, the overall size should be 405.5:  50 + 100 + 200
+    // + 9.25 * 6(horizontal padding). To fit in 194(allowed max width - padding),
+    // each segment width should scale down to original width * (194 - separator) / 413.5.
+    final Size firstChildSize = getChildSize(0);
+    const double maxAllowedTotal = 200 - 6 - 2;
+    const double originalTotal = 405.5;
+    expect(firstChildSize.width, (50 + 9.25 * 2) * maxAllowedTotal / originalTotal);
+
+    final Size secondChildSize = getChildSize(1);
+    expect(secondChildSize.width, (100 + 9.25 * 2) * maxAllowedTotal / originalTotal);
+
+    final Size thirdChildSize = getChildSize(2);
+    expect(thirdChildSize.width, (200 + 9.25 * 2) * maxAllowedTotal / originalTotal);
+  });
+
+  testWidgets('If proportionalWidth is true and the overall segment control width '
+  'is smaller than the min width of the parent constraints, each segment scales up', (WidgetTester tester) async {
+    final Map<int, Widget> children = <int, Widget>{
+      0: const SizedBox(width: 20, child: Text('First')),
+      1: const SizedBox(width: 30, child: Text('Second')),
+      2: const SizedBox(width: 50, child: Text('Third')),
+    };
+
+    await tester.pumpWidget(
+      boilerplate(
+        builder: (BuildContext context) {
+          return ConstrainedBox(
+            constraints: const BoxConstraints(minWidth: 200),
+            child: CupertinoSlidingSegmentedControl<int>(
+              key: const ValueKey<String>('Segmented Control'),
+              children: children,
+              groupValue: groupValue,
+              proportionalWidth: true,
+              onValueChanged: defaultCallback,
+            ),
+          );
+        },
+      ),
+    );
+
+    Size getChildSize(int index) {
+      return tester.getSize(
+        find.ancestor(
+          of: find.byWidget(children[index]!),
+          matching: find.byType(MetaData)
+        )
+      );
+    }
+
+    // Without constraints, the overall size should be 155.5:  20 + 30 + 50
+    // + 9.25 * 6(horizontal padding). To fit in 194(allowed max width - padding),
+    // each segment width should scale up to original width * (194 - separator) / 155.5.
+    final Size firstChildSize = getChildSize(0);
+    const double constraintsMinWidth = 200 - 6 - 2;
+    const double originalTotal = 155.5;
+    expect(firstChildSize.width, moreOrLessEquals((20 + 9.25 * 2) * constraintsMinWidth / originalTotal));
+
+    final Size secondChildSize = getChildSize(1);
+    expect(secondChildSize.width, moreOrLessEquals((30 + 9.25 * 2) * constraintsMinWidth / originalTotal));
+
+    final Size thirdChildSize = getChildSize(2);
+    expect(thirdChildSize.width, moreOrLessEquals((50 + 9.25 * 2) * constraintsMinWidth / originalTotal));
+  });
+
   testWidgets('Width is finite in unbounded space', (WidgetTester tester) async {
     const Map<int, Widget> children = <int, Widget>{
       0: SizedBox(width: 50),
@@ -759,6 +1019,46 @@
     expect(groupValue, 1);
   });
 
+  testWidgets('Non-centered taps work on propotional segments', (WidgetTester tester) async {
+    final Map<int, Widget> children = <int, Widget>{};
+    children[0] = const SizedBox(width: 50, height: 30);
+    children[1] = const SizedBox();
+    children[2] = const SizedBox(width: 100, height: 30);
+
+    await tester.pumpWidget(
+      boilerplate(
+        builder: (BuildContext context) {
+          return CupertinoSlidingSegmentedControl<int>(
+            key: const ValueKey<String>('Segmented Control'),
+            proportionalWidth: true,
+            children: children,
+            groupValue: groupValue,
+            onValueChanged: defaultCallback,
+          );
+        },
+      ),
+    );
+
+    expect(groupValue, 0);
+
+    final Rect firstChild = tester.getRect(find.ancestor(of: find.byWidget(children[0]!), matching: find.byType(MetaData)));
+    expect(firstChild.width, 50.0 + 9.25 * 2);
+
+    final Rect secondChild = tester.getRect(find.ancestor(of: find.byWidget(children[1]!), matching: find.byType(MetaData)));
+    expect(secondChild.width, 0.0 + 9.25 * 2);
+
+    final Rect thirdChild = tester.getRect(find.ancestor(of: find.byWidget(children[2]!), matching: find.byType(MetaData)));
+    expect(thirdChild.width, 100.0  + 9.25 * 2);
+
+    final Finder child0 = find.ancestor(of: find.byWidget(children[0]!), matching: find.byType(MetaData));
+    final Offset centerOfChild0 = tester.getCenter(child0);
+    await tester.tapAt(centerOfChild0 + Offset(firstChild.width / 2 + 1, 0));
+    expect(groupValue, 1);
+
+    await tester.tapAt(centerOfChild0 + Offset(firstChild.width / 2 + 1 + secondChild.width + 1, 0));
+    expect(groupValue, 2);
+  });
+
   testWidgets('Hit-tests report accurate local position in segments', (WidgetTester tester) async {
     final Map<int, Widget> children = <int, Widget>{};
     late TapDownDetails tapDownDetails;
@@ -791,6 +1091,39 @@
     expect(tapDownDetails.globalPosition, segment0GlobalOffset + const Offset(7, 11));
   });
 
+  testWidgets('Hit-tests report accurate local position in proportional segments', (WidgetTester tester) async {
+    final Map<int, Widget> children = <int, Widget>{};
+    late TapDownDetails tapDownDetails;
+    children[0] = GestureDetector(
+      behavior: HitTestBehavior.opaque,
+      onTapDown: (TapDownDetails details) { tapDownDetails = details; },
+      child: const SizedBox(width: 200, height: 200),
+    );
+    children[1] = const Text('Child 2');
+
+    await tester.pumpWidget(
+      boilerplate(
+        builder: (BuildContext context) {
+          return CupertinoSlidingSegmentedControl<int>(
+            key: const ValueKey<String>('Segmented Control'),
+            proportionalWidth: true,
+            children: children,
+            groupValue: groupValue,
+            onValueChanged: defaultCallback,
+          );
+        },
+      ),
+    );
+
+    expect(groupValue, 0);
+
+    final Offset segment0GlobalOffset = tester.getTopLeft(find.byWidget(children[0]!));
+    await tester.tapAt(segment0GlobalOffset + const Offset(7, 11));
+
+    expect(tapDownDetails.localPosition, const Offset(7, 11));
+    expect(tapDownDetails.globalPosition, segment0GlobalOffset + const Offset(7, 11));
+  });
+
   testWidgets('Thumb animation is correct when the selected segment changes', (WidgetTester tester) async {
     await tester.pumpWidget(setupSimpleSegmentedControl());
 
@@ -1084,6 +1417,64 @@
     expect(callbackCalled, isFalse);
   });
 
+  testWidgets('Dragging out of bound does not cause out of range exception', (WidgetTester tester) async {
+    const Map<int, Widget> children = <int, Widget>{
+      0: Text('A'),
+      1: Text('BB'),
+      2: Text('CCC'),
+    };
+
+    await tester.pumpWidget(
+      boilerplate(
+        builder: (BuildContext context) {
+          return CupertinoSlidingSegmentedControl<int>(
+            proportionalWidth: true,
+            children: children,
+            groupValue: groupValue,
+            onValueChanged: defaultCallback,
+          );
+        },
+      ),
+    );
+
+    Size getChildSize(int index) {
+      return tester.getSize(
+        find.ancestor(
+          of: find.byWidget(children[index]!),
+          matching: find.byType(MetaData)
+        )
+      );
+    }
+
+    expect(getChildSize(0).width, 32.5);
+    expect(getChildSize(2).width, 60.5);
+
+    // Start dragging.
+    final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('A')));
+    await tester.pump();
+    await tester.pump(const Duration(seconds: 1));
+
+    // Dragging to left until out of bound.
+    await gesture.moveTo(const Offset(-100, 0));
+    await tester.pump();
+    expect(getHighlightedIndex(tester), 0);
+
+    // Move the pointer to the last child and continue dragging until out of bound.
+    final Offset thirdChild = tester.getCenter(find.text('CCC'));
+    await gesture.moveTo(thirdChild);
+    await tester.pump();
+
+    await gesture.moveTo(thirdChild + const Offset(100, 0));
+    await tester.pump();
+
+    await gesture.up();
+    await tester.pumpAndSettle();
+
+    expect(getHighlightedIndex(tester), 2);
+
+    expect(tester.takeException(), isNull);
+  });
+
   testWidgets('Disallow new gesture when dragging', (WidgetTester tester) async {
     const Map<int, Widget> children = <int, Widget>{
       0: Text('A'),