785 lines
24 KiB
Dart
785 lines
24 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
/// @docImport 'switch.dart';
|
|
library;
|
|
|
|
import 'dart:collection';
|
|
import 'dart:math' as math;
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
|
|
// Minimum padding from edges of the segmented control to edges of
|
|
// encompassing widget.
|
|
const EdgeInsetsGeometry _kHorizontalItemPadding = EdgeInsets.symmetric(
|
|
horizontal: 16.0,
|
|
);
|
|
|
|
// Minimum height of the segmented control.
|
|
const double _kMinSegmentedControlHeight = 28.0;
|
|
|
|
// The default color used for the text of the disabled segment.
|
|
const Color _kDisableTextColor = Color.fromARGB(115, 122, 122, 122);
|
|
|
|
// The duration of the fade animation used to transition when a new widget
|
|
// is selected.
|
|
const Duration _kFadeDuration = Duration(milliseconds: 165);
|
|
|
|
class NewCupertinoSegmentedControl<T extends Object> extends StatefulWidget {
|
|
/// Creates an iOS-style segmented control bar.
|
|
///
|
|
/// 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] 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.
|
|
NewCupertinoSegmentedControl({
|
|
super.key,
|
|
required this.children,
|
|
required this.onValueChanged,
|
|
this.groupValue,
|
|
this.unselectedColor,
|
|
this.selectedColor,
|
|
this.borderColor,
|
|
this.pressedColor,
|
|
this.disabledColor,
|
|
this.disabledTextColor,
|
|
this.padding,
|
|
this.disabledChildren = const <Never>{},
|
|
}) : assert(children.length >= 2),
|
|
assert(
|
|
groupValue == null ||
|
|
children.keys.any((T child) => child == groupValue),
|
|
'The groupValue must be either null or one of the keys in the children map.',
|
|
);
|
|
|
|
/// 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.
|
|
///
|
|
/// 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].
|
|
final ValueChanged<T> onValueChanged;
|
|
|
|
/// The color used to fill the backgrounds of unselected widgets and as the
|
|
/// text color of the selected widget.
|
|
///
|
|
/// Defaults to [CupertinoTheme]'s `primaryContrastingColor` if null.
|
|
final Color? unselectedColor;
|
|
|
|
/// The color used to fill the background of the selected widget and as the text
|
|
/// color of unselected widgets.
|
|
///
|
|
/// Defaults to [CupertinoTheme]'s `primaryColor` if null.
|
|
final Color? selectedColor;
|
|
|
|
/// The color used as the border around each widget.
|
|
///
|
|
/// Defaults to [CupertinoTheme]'s `primaryColor` if null.
|
|
final Color? borderColor;
|
|
|
|
/// The color used to fill the background of the widget the user is
|
|
/// temporarily interacting with through a long press or drag.
|
|
///
|
|
/// Defaults to the selectedColor at 20% opacity if null.
|
|
final Color? pressedColor;
|
|
|
|
/// The color used to fill the background of the segment when it is disabled.
|
|
///
|
|
/// If null, this color will be 50% opacity of the [selectedColor] when
|
|
/// the segment is selected. If the segment is unselected, this color will be
|
|
/// set to [unselectedColor].
|
|
final Color? disabledColor;
|
|
|
|
/// The color used for the text of the segment when it is disabled.
|
|
final Color? disabledTextColor;
|
|
|
|
/// The CupertinoSegmentedControl will be placed inside this padding.
|
|
///
|
|
/// Defaults to EdgeInsets.symmetric(horizontal: 16.0)
|
|
final EdgeInsetsGeometry? padding;
|
|
|
|
/// The set of identifying keys that correspond to the segments that should be disabled.
|
|
///
|
|
/// All segments are enabled by default.
|
|
final Set<T> disabledChildren;
|
|
|
|
@override
|
|
State<NewCupertinoSegmentedControl<T>> createState() =>
|
|
_SegmentedControlState<T>();
|
|
}
|
|
|
|
class _SegmentedControlState<T extends Object>
|
|
extends State<NewCupertinoSegmentedControl<T>>
|
|
with TickerProviderStateMixin<NewCupertinoSegmentedControl<T>> {
|
|
T? _pressedKey;
|
|
|
|
final List<AnimationController> _selectionControllers =
|
|
<AnimationController>[];
|
|
final List<ColorTween> _childTweens = <ColorTween>[];
|
|
|
|
late ColorTween _forwardBackgroundColorTween;
|
|
late ColorTween _reverseBackgroundColorTween;
|
|
late ColorTween _textColorTween;
|
|
|
|
Color? _selectedColor;
|
|
Color? _unselectedColor;
|
|
Color? _borderColor;
|
|
Color? _pressedColor;
|
|
Color? _selectedDisabledColor;
|
|
Color? _unselectedDisabledColor;
|
|
Color? _disabledTextColor;
|
|
|
|
AnimationController createAnimationController() {
|
|
return AnimationController(duration: _kFadeDuration, vsync: this)
|
|
..addListener(() {
|
|
setState(() {
|
|
// State of background/text colors has changed
|
|
});
|
|
});
|
|
}
|
|
|
|
bool _updateColors() {
|
|
assert(mounted, 'This should only be called after didUpdateDependencies');
|
|
bool changed = false;
|
|
final Color disabledTextColor =
|
|
widget.disabledTextColor ?? _kDisableTextColor;
|
|
if (_disabledTextColor != disabledTextColor) {
|
|
changed = true;
|
|
_disabledTextColor = disabledTextColor;
|
|
}
|
|
final Color selectedColor =
|
|
widget.selectedColor ?? CupertinoTheme.of(context).primaryColor;
|
|
if (_selectedColor != selectedColor) {
|
|
changed = true;
|
|
_selectedColor = selectedColor;
|
|
}
|
|
final Color unselectedColor =
|
|
widget.unselectedColor ??
|
|
CupertinoTheme.of(context).primaryContrastingColor;
|
|
if (_unselectedColor != unselectedColor) {
|
|
changed = true;
|
|
_unselectedColor = unselectedColor;
|
|
}
|
|
final Color selectedDisabledColor =
|
|
widget.disabledColor ?? selectedColor.withOpacity(0.5);
|
|
final Color unselectedDisabledColor =
|
|
widget.disabledColor ?? unselectedColor;
|
|
if (_selectedDisabledColor != selectedDisabledColor ||
|
|
_unselectedDisabledColor != unselectedDisabledColor) {
|
|
changed = true;
|
|
_selectedDisabledColor = selectedDisabledColor;
|
|
_unselectedDisabledColor = unselectedDisabledColor;
|
|
}
|
|
final Color borderColor =
|
|
widget.borderColor ?? CupertinoTheme.of(context).primaryColor;
|
|
if (_borderColor != borderColor) {
|
|
changed = true;
|
|
_borderColor = borderColor;
|
|
}
|
|
final Color pressedColor =
|
|
widget.pressedColor ??
|
|
CupertinoTheme.of(context).primaryColor.withOpacity(0.2);
|
|
if (_pressedColor != pressedColor) {
|
|
changed = true;
|
|
_pressedColor = pressedColor;
|
|
}
|
|
|
|
_forwardBackgroundColorTween = ColorTween(
|
|
begin: _pressedColor,
|
|
end: _selectedColor,
|
|
);
|
|
_reverseBackgroundColorTween = ColorTween(
|
|
begin: _unselectedColor,
|
|
end: _selectedColor,
|
|
);
|
|
_textColorTween = ColorTween(begin: _selectedColor, end: _unselectedColor);
|
|
return changed;
|
|
}
|
|
|
|
void _updateAnimationControllers() {
|
|
assert(mounted, 'This should only be called after didUpdateDependencies');
|
|
for (final AnimationController controller in _selectionControllers) {
|
|
controller.dispose();
|
|
}
|
|
_selectionControllers.clear();
|
|
_childTweens.clear();
|
|
|
|
for (final T key in widget.children.keys) {
|
|
final AnimationController animationController =
|
|
createAnimationController();
|
|
if (widget.groupValue == key) {
|
|
_childTweens.add(_reverseBackgroundColorTween);
|
|
animationController.value = 1.0;
|
|
} else {
|
|
_childTweens.add(_forwardBackgroundColorTween);
|
|
}
|
|
_selectionControllers.add(animationController);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
|
|
if (_updateColors()) {
|
|
_updateAnimationControllers();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(NewCupertinoSegmentedControl<T> oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
|
|
if (_updateColors() ||
|
|
oldWidget.children.length != widget.children.length) {
|
|
_updateAnimationControllers();
|
|
}
|
|
|
|
if (oldWidget.groupValue != widget.groupValue) {
|
|
int index = 0;
|
|
for (final T key in widget.children.keys) {
|
|
if (widget.groupValue == key) {
|
|
_childTweens[index] = _forwardBackgroundColorTween;
|
|
_selectionControllers[index].forward();
|
|
} else {
|
|
_childTweens[index] = _reverseBackgroundColorTween;
|
|
_selectionControllers[index].reverse();
|
|
}
|
|
index += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
for (final AnimationController animationController
|
|
in _selectionControllers) {
|
|
animationController.dispose();
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
void _onTapDown(T currentKey) {
|
|
if (_pressedKey == null && currentKey != widget.groupValue) {
|
|
setState(() {
|
|
_pressedKey = currentKey;
|
|
});
|
|
}
|
|
}
|
|
|
|
void _onTapCancel() {
|
|
setState(() {
|
|
_pressedKey = null;
|
|
});
|
|
}
|
|
|
|
void _onTap(T currentKey) {
|
|
if (currentKey != _pressedKey) {
|
|
return;
|
|
}
|
|
if (!widget.disabledChildren.contains(currentKey)) {
|
|
if (currentKey != widget.groupValue) {
|
|
widget.onValueChanged(currentKey);
|
|
}
|
|
}
|
|
_pressedKey = null;
|
|
}
|
|
|
|
Color? getTextColor(int index, T currentKey) {
|
|
if (widget.disabledChildren.contains(currentKey)) {
|
|
return _disabledTextColor;
|
|
}
|
|
if (_selectionControllers[index].isAnimating) {
|
|
return _textColorTween.evaluate(_selectionControllers[index]);
|
|
}
|
|
if (widget.groupValue == currentKey) {
|
|
return _unselectedColor;
|
|
}
|
|
return _selectedColor;
|
|
}
|
|
|
|
Color? getBackgroundColor(int index, T currentKey) {
|
|
if (widget.disabledChildren.contains(currentKey)) {
|
|
return widget.groupValue == currentKey
|
|
? _selectedDisabledColor
|
|
: _unselectedDisabledColor;
|
|
}
|
|
if (_selectionControllers[index].isAnimating) {
|
|
return _childTweens[index].evaluate(_selectionControllers[index]);
|
|
}
|
|
if (widget.groupValue == currentKey) {
|
|
return _selectedColor;
|
|
}
|
|
if (_pressedKey == currentKey) {
|
|
return _pressedColor;
|
|
}
|
|
return _unselectedColor;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final List<Widget> gestureChildren = <Widget>[];
|
|
final List<Color> backgroundColors = <Color>[];
|
|
int index = 0;
|
|
int? selectedIndex;
|
|
int? pressedIndex;
|
|
for (final 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: getTextColor(index, currentKey));
|
|
final IconThemeData iconTheme = IconThemeData(
|
|
color: getTextColor(index, currentKey),
|
|
);
|
|
|
|
Widget child = Center(child: widget.children[currentKey]);
|
|
|
|
child = MouseRegion(
|
|
cursor: kIsWeb ? SystemMouseCursors.click : MouseCursor.defer,
|
|
child: GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTapDown:
|
|
widget.disabledChildren.contains(currentKey)
|
|
? null
|
|
: (TapDownDetails event) {
|
|
_onTapDown(currentKey);
|
|
},
|
|
onTapCancel:
|
|
widget.disabledChildren.contains(currentKey)
|
|
? null
|
|
: _onTapCancel,
|
|
onTap: () {
|
|
_onTap(currentKey);
|
|
},
|
|
child: IconTheme(
|
|
data: iconTheme,
|
|
child: DefaultTextStyle(
|
|
style: textStyle,
|
|
child: Semantics(
|
|
button: true,
|
|
inMutuallyExclusiveGroup: true,
|
|
selected: widget.groupValue == currentKey,
|
|
child: child,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
backgroundColors.add(getBackgroundColor(index, currentKey)!);
|
|
gestureChildren.add(child);
|
|
index += 1;
|
|
}
|
|
|
|
final Widget box = _SegmentedControlRenderWidget<T>(
|
|
selectedIndex: selectedIndex,
|
|
pressedIndex: pressedIndex,
|
|
backgroundColors: backgroundColors,
|
|
borderColor: _borderColor!,
|
|
children: gestureChildren,
|
|
);
|
|
|
|
return Padding(
|
|
padding: widget.padding ?? _kHorizontalItemPadding,
|
|
child: UnconstrainedBox(constrainedAxis: Axis.horizontal, child: box),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget {
|
|
const _SegmentedControlRenderWidget({
|
|
super.key,
|
|
super.children,
|
|
required this.selectedIndex,
|
|
required this.pressedIndex,
|
|
required this.backgroundColors,
|
|
required this.borderColor,
|
|
});
|
|
|
|
final int? selectedIndex;
|
|
final int? pressedIndex;
|
|
final List<Color> backgroundColors;
|
|
final Color borderColor;
|
|
|
|
@override
|
|
RenderObject createRenderObject(BuildContext context) {
|
|
return _RenderSegmentedControl<T>(
|
|
textDirection: Directionality.of(context),
|
|
selectedIndex: selectedIndex,
|
|
pressedIndex: pressedIndex,
|
|
backgroundColors: backgroundColors,
|
|
borderColor: borderColor,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void updateRenderObject(
|
|
BuildContext context,
|
|
_RenderSegmentedControl<T> renderObject,
|
|
) {
|
|
renderObject
|
|
..textDirection = Directionality.of(context)
|
|
..selectedIndex = selectedIndex
|
|
..pressedIndex = pressedIndex
|
|
..backgroundColors = backgroundColors
|
|
..borderColor = borderColor;
|
|
}
|
|
}
|
|
|
|
class _SegmentedControlContainerBoxParentData
|
|
extends ContainerBoxParentData<RenderBox> {
|
|
RRect? surroundingRect;
|
|
}
|
|
|
|
typedef _NextChild = RenderBox? Function(RenderBox child);
|
|
|
|
class _RenderSegmentedControl<T> extends RenderBox
|
|
with
|
|
ContainerRenderObjectMixin<
|
|
RenderBox,
|
|
ContainerBoxParentData<RenderBox>
|
|
>,
|
|
RenderBoxContainerDefaultsMixin<
|
|
RenderBox,
|
|
ContainerBoxParentData<RenderBox>
|
|
> {
|
|
_RenderSegmentedControl({
|
|
required int? selectedIndex,
|
|
required int? pressedIndex,
|
|
required TextDirection textDirection,
|
|
required List<Color> backgroundColors,
|
|
required Color borderColor,
|
|
}) : _textDirection = textDirection,
|
|
_selectedIndex = selectedIndex,
|
|
_pressedIndex = pressedIndex,
|
|
_backgroundColors = backgroundColors,
|
|
_borderColor = borderColor;
|
|
|
|
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();
|
|
}
|
|
|
|
List<Color> get backgroundColors => _backgroundColors;
|
|
List<Color> _backgroundColors;
|
|
|
|
set backgroundColors(List<Color> value) {
|
|
if (_backgroundColors == value) {
|
|
return;
|
|
}
|
|
_backgroundColors = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
Color get borderColor => _borderColor;
|
|
Color _borderColor;
|
|
|
|
set borderColor(Color value) {
|
|
if (_borderColor == value) {
|
|
return;
|
|
}
|
|
_borderColor = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
@override
|
|
double computeMinIntrinsicWidth(double height) {
|
|
RenderBox? child = firstChild;
|
|
double minWidth = 0.0;
|
|
while (child != null) {
|
|
final _SegmentedControlContainerBoxParentData childParentData =
|
|
child.parentData! as _SegmentedControlContainerBoxParentData;
|
|
final double childWidth = child.getMinIntrinsicWidth(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! as _SegmentedControlContainerBoxParentData;
|
|
final double childWidth = child.getMaxIntrinsicWidth(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! as _SegmentedControlContainerBoxParentData;
|
|
final double childHeight = child.getMinIntrinsicHeight(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! as _SegmentedControlContainerBoxParentData;
|
|
final double childHeight = child.getMaxIntrinsicHeight(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 = _SegmentedControlContainerBoxParentData();
|
|
}
|
|
}
|
|
|
|
void _layoutRects(
|
|
_NextChild nextChild,
|
|
RenderBox? leftChild,
|
|
RenderBox? rightChild,
|
|
) {
|
|
RenderBox? child = leftChild;
|
|
double start = 0.0;
|
|
while (child != null) {
|
|
final _SegmentedControlContainerBoxParentData childParentData =
|
|
child.parentData! as _SegmentedControlContainerBoxParentData;
|
|
final Offset childOffset = Offset(start, 0.0);
|
|
childParentData.offset = childOffset;
|
|
final Rect childRect = Rect.fromLTWH(
|
|
start,
|
|
0.0,
|
|
child.size.width,
|
|
child.size.height,
|
|
);
|
|
final RRect rChildRect;
|
|
if (child == leftChild) {
|
|
rChildRect = RRect.fromRectAndCorners(
|
|
childRect,
|
|
topLeft: const Radius.circular(10.0),
|
|
bottomLeft: const Radius.circular(10.0),
|
|
);
|
|
} else if (child == rightChild) {
|
|
rChildRect = RRect.fromRectAndCorners(
|
|
childRect,
|
|
topRight: const Radius.circular(10.0),
|
|
bottomRight: const Radius.circular(10.0),
|
|
);
|
|
} else {
|
|
rChildRect = RRect.fromRectAndCorners(childRect);
|
|
}
|
|
childParentData.surroundingRect = rChildRect;
|
|
start += child.size.width;
|
|
child = nextChild(child);
|
|
}
|
|
}
|
|
|
|
Size _calculateChildSize(BoxConstraints constraints) {
|
|
double maxHeight = _kMinSegmentedControlHeight;
|
|
double childWidth = constraints.minWidth / childCount;
|
|
RenderBox? child = firstChild;
|
|
while (child != null) {
|
|
childWidth = math.max(
|
|
childWidth,
|
|
child.getMaxIntrinsicWidth(double.infinity),
|
|
);
|
|
child = childAfter(child);
|
|
}
|
|
childWidth = math.min(childWidth, constraints.maxWidth / childCount);
|
|
child = firstChild;
|
|
while (child != null) {
|
|
final double boxHeight = child.getMaxIntrinsicHeight(childWidth);
|
|
maxHeight = math.max(maxHeight, boxHeight);
|
|
child = childAfter(child);
|
|
}
|
|
return Size(childWidth, maxHeight);
|
|
}
|
|
|
|
Size _computeOverallSizeFromChildSize(Size childSize) {
|
|
return constraints.constrain(
|
|
Size(childSize.width * childCount, childSize.height),
|
|
);
|
|
}
|
|
|
|
@override
|
|
double? computeDryBaseline(
|
|
covariant BoxConstraints constraints,
|
|
TextBaseline baseline,
|
|
) {
|
|
final Size childSize = _calculateChildSize(constraints);
|
|
final BoxConstraints childConstraints = BoxConstraints.tight(childSize);
|
|
|
|
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
|
|
for (
|
|
RenderBox? child = firstChild;
|
|
child != null;
|
|
child = childAfter(child)
|
|
) {
|
|
baselineOffset = baselineOffset.minOf(
|
|
BaselineOffset(child.getDryBaseline(childConstraints, baseline)),
|
|
);
|
|
}
|
|
return baselineOffset.offset;
|
|
}
|
|
|
|
@override
|
|
Size computeDryLayout(BoxConstraints constraints) {
|
|
final Size childSize = _calculateChildSize(constraints);
|
|
return _computeOverallSizeFromChildSize(childSize);
|
|
}
|
|
|
|
@override
|
|
void performLayout() {
|
|
final BoxConstraints constraints = this.constraints;
|
|
final Size childSize = _calculateChildSize(constraints);
|
|
|
|
final BoxConstraints childConstraints = BoxConstraints.tightFor(
|
|
width: childSize.width,
|
|
height: childSize.height,
|
|
);
|
|
|
|
RenderBox? child = firstChild;
|
|
while (child != null) {
|
|
child.layout(childConstraints, parentUsesSize: true);
|
|
child = childAfter(child);
|
|
}
|
|
|
|
switch (textDirection) {
|
|
case TextDirection.rtl:
|
|
_layoutRects(childBefore, lastChild, firstChild);
|
|
case TextDirection.ltr:
|
|
_layoutRects(childAfter, firstChild, lastChild);
|
|
}
|
|
|
|
size = _computeOverallSizeFromChildSize(childSize);
|
|
}
|
|
|
|
@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,
|
|
) {
|
|
final _SegmentedControlContainerBoxParentData childParentData =
|
|
child.parentData! as _SegmentedControlContainerBoxParentData;
|
|
|
|
context.canvas.drawRRect(
|
|
childParentData.surroundingRect!.shift(offset),
|
|
Paint()
|
|
..color = backgroundColors[childIndex]
|
|
..style = PaintingStyle.fill,
|
|
);
|
|
context.canvas.drawRRect(
|
|
childParentData.surroundingRect!.shift(offset),
|
|
Paint()
|
|
..color = borderColor
|
|
..strokeWidth = 1.0
|
|
..style = PaintingStyle.stroke,
|
|
);
|
|
|
|
context.paintChild(child, childParentData.offset + offset);
|
|
}
|
|
|
|
@override
|
|
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
|
|
RenderBox? child = lastChild;
|
|
while (child != null) {
|
|
final _SegmentedControlContainerBoxParentData childParentData =
|
|
child.parentData! as _SegmentedControlContainerBoxParentData;
|
|
if (childParentData.surroundingRect!.contains(position)) {
|
|
return result.addWithPaintOffset(
|
|
offset: childParentData.offset,
|
|
position: position,
|
|
hitTest: (BoxHitTestResult result, Offset localOffset) {
|
|
assert(localOffset == position - childParentData.offset);
|
|
return child!.hitTest(result, position: localOffset);
|
|
},
|
|
);
|
|
}
|
|
child = childParentData.previousSibling;
|
|
}
|
|
return false;
|
|
}
|
|
}
|