更改系统源码后的新增了自定义高度和圆角
源码默认圆角为5,高度为28,构造方法里面没有这些参数
下图是更改为圆角20,高度50,padding没有用,自己添加margin为10的状态
1.复制下面的代码到一个新的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.
// @dart = 2.8
import 'dart:collection';
import 'dart:math' as math;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:people_manager/config/net_config/api.dart';
// Minimum padding from edges of the segmented control to edges of
// encompassing widget.
const EdgeInsetsGeometry _kHorizontalItemPadding =
EdgeInsets.symmetric(horizontal:30.0);
// Minimum height of the segmented control.
// const double _kMinSegmentedControlHeight = 40.0;
// The duration of the fade animation used to transition when a new widget
// is selected.
const Duration _kFadeDuration =Duration(milliseconds:165);
/// An iOS-style segmented control.
///
/// Displays the widgets provided in the [Map] of [children] in a
/// horizontal list. Used to select between a number of mutually exclusive
/// options. When one option in the segmented control is selected, the other
/// options in the segmented control cease to be selected.
///
/// A segmented control can feature any [Widget] as one of the values in its
/// [Map] of [children]. The type T is the type of the keys used
/// to identify each widget and determine which widget is selected. As
/// required by the [Map] class, keys must be of consistent types
/// and must be comparable. The ordering of the keys will determine the order
/// of the widgets in the segmented control.
///
/// When the state of the segmented control changes, the widget calls the
/// [onValueChanged] callback. The map key associated with the newly selected
/// widget is returned in the [onValueChanged] callback. Typically, widgets
/// that use a segmented control will listen for the [onValueChanged] callback
/// and rebuild the segmented control with a new [groupValue] to update which
/// option is currently selected.
///
/// The [children] will be displayed in the order of the keys in the [Map].
/// The height of the segmented control is determined by the height of the
/// tallest widget provided as a value in the [Map] of [children].
/// The width of each child in the segmented control will be equal to the width
/// of widest child, unless the combined width of the children is wider than
/// the available horizontal space. In this case, the available horizontal space
/// is divided by the number of provided [children] to determine the width of
/// each widget. The selection area for each of the widgets in the [Map] of
/// [children] will then be expanded to fill the calculated space, so each
/// widget will appear to have the same dimensions.
///
/// A segmented control may optionally be created with custom colors. The
/// [unselectedColor], [selectedColor], [borderColor], and [pressedColor]
/// arguments can be used to override the segmented control's colors from
/// [CupertinoTheme] defaults.
///
/// See also:
///
/// * [CupertinoSegmentedControl], a segmented control widget in the style used
/// up until iOS 13.
/// *
class CupertinoSegmentedControlextends StatefulWidget {
/// Creates an iOS-style segmented control bar.
///
/// The [children] and [onValueChanged] arguments must not be null. The
/// [children] argument must be an ordered [Map] such as a [LinkedHashMap].
/// Further, the length of the [children] list must be greater than one.
///
/// Each widget value in the map of [children] must have an associated key
/// that uniquely identifies this widget. This key is what will be returned
/// in the [onValueChanged] callback when a new value from the [children] map
/// is selected.
///
/// The [groupValue] 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.
CupertinoSegmentedControl({
Key key,
@required this.children,
@required this.onValueChanged,
this.groupValue,
this.unselectedColor,
this.selectedColor,
this.borderColor,
this.radius =5,
this.kMinSegmentedControlHeight = 30,
this.pressedColor,
this.padding,
}) :assert(children !=null),
assert(children.length >=2),
assert(onValueChanged !=null),
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.',
),
super(key: key);
/// The identifying keys and corresponding widget values in the
/// segmented control.
///
/// The map must have more than one entry.
/// This attribute must be an ordered [Map] such as a [LinkedHashMap].
final Mapchildren;
/// 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 TgroupValue;
/// The callback that is called when a new option is tapped.
///
/// This attribute must not be null.
///
/// The segmented control passes the newly selected widget's associated key
/// to the callback but does not actually change state until the parent
/// widget rebuilds the segmented control with the new [groupValue].
///
/// The callback provided to [onValueChanged] should update the state of
/// the parent [StatefulWidget] using the [State.setState] method, so that
/// the parent gets rebuilt; for example:
///
/// {@tool snippet}
///
/// ```dart
/// class SegmentedControlExample extends StatefulWidget {
/// @override
/// State createState() => SegmentedControlExampleState();
/// }
///
/// class SegmentedControlExampleState extends State {
/// final Map children = const {
/// 0: Text('Child 1'),
/// 1: Text('Child 2'),
/// };
///
/// int currentValue;
///
/// @override
/// Widget build(BuildContext context) {
/// return Container(
/// child: CupertinoSegmentedControl(
/// children: children,
/// onValueChanged: (int newValue) {
/// setState(() {
/// currentValue = newValue;
/// });
/// },
/// groupValue: currentValue,
/// ),
/// );
/// }
/// }
/// ```
/// {@end-tool}
final ValueChangedonValueChanged;
/// 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 ColorunselectedColor;
/// 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 ColorselectedColor;
/// The color used as the border around each widget.
///
/// Defaults to [CupertinoTheme]'s `primaryColor` if null.
final ColorborderColor;
/// 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 ColorpressedColor;
/// The CupertinoSegmentedControl will be placed inside this padding.
///
/// Defaults to EdgeInsets.symmetric(horizontal: 16.0)
final EdgeInsetsGeometrypadding;
final doubleradius;
final doublekMinSegmentedControlHeight;
@override
_SegmentedControlStatecreateState() =>_SegmentedControlState();
}
class _SegmentedControlStateextends State>
with TickerProviderStateMixin> {
T_pressedKey;
final List_selectionControllers =
[];
final List_childTweens = [];
ColorTween_forwardBackgroundColorTween;
ColorTween_reverseBackgroundColorTween;
ColorTween_textColorTween;
Color_selectedColor;
Color_unselectedColor;
Color_borderColor;
double_radius;
Color_pressedColor;
double_kMinSegmentedControlHeight;
AnimationControllercreateAnimationController() {
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 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 borderColor =
widget.borderColor ?? CupertinoTheme.of(context).primaryColor;
if (_borderColor != borderColor) {
changed =true;
_borderColor = borderColor;
}
_radius =widget.radius;
_kMinSegmentedControlHeight =widget.kMinSegmentedControlHeight;
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 controllerin _selectionControllers) {
controller.dispose();
}
_selectionControllers.clear();
_childTweens.clear();
for (final T keyin 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(CupertinoSegmentedControl oldWidget) {
super.didUpdateWidget(oldWidget);
if (_updateColors() ||
oldWidget.children.length !=widget.children.length) {
_updateAnimationControllers();
}
if (oldWidget.groupValue !=widget.groupValue) {
int index =0;
for (final T keyin 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 (currentKey !=widget.groupValue) {
widget.onValueChanged(currentKey);
}
_pressedKey =null;
}
ColorgetTextColor(int index, T currentKey) {
if (_selectionControllers[index].isAnimating)
return _textColorTween.evaluate(_selectionControllers[index]);
if (widget.groupValue == currentKey)return _unselectedColor;
return _selectedColor;
}
ColorgetBackgroundColor(int index, T currentKey) {
if (_selectionControllers[index].isAnimating)
return _childTweens[index].evaluate(_selectionControllers[index]);
if (widget.groupValue == currentKey)return _selectedColor;
if (_pressedKey == currentKey)return _pressedColor;
return _unselectedColor;
}
@override
Widgetbuild(BuildContext context) {
final List _gestureChildren = [];
final List _backgroundColors = [];
int index =0;
int selectedIndex;
int pressedIndex;
for (final T currentKeyin 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 =GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (TapDownDetails event) {
_onTapDown(currentKey);
},
onTapCancel: _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(
children: _gestureChildren,
selectedIndex: selectedIndex,
pressedIndex: pressedIndex,
backgroundColors: _backgroundColors,
borderColor:_borderColor,
radius:_radius,
kMinSegmentedControlHeight:_kMinSegmentedControlHeight);
return Padding(
padding:widget.padding ?? _kHorizontalItemPadding,
child:UnconstrainedBox(
constrainedAxis: Axis.horizontal,
child: box,
),
);
}
}
class _SegmentedControlRenderWidgetextends MultiChildRenderObjectWidget {
_SegmentedControlRenderWidget(
{Key key,
List children =const [],
@required this.selectedIndex,
@required this.pressedIndex,
@required this.backgroundColors,
@required this.borderColor,
@required this.radius,
@required this.kMinSegmentedControlHeight})
:super(
key: key,
children: children,
);
final intselectedIndex;
final intpressedIndex;
final ListbackgroundColors;
final ColorborderColor;
final doubleradius;
final doublekMinSegmentedControlHeight;
@override
RenderObjectcreateRenderObject(BuildContext context) {
return _RenderSegmentedControl(
textDirection: Directionality.of(context),
selectedIndex:selectedIndex,
pressedIndex:pressedIndex,
backgroundColors:backgroundColors,
borderColor:borderColor,
radius:radius,
kMinSegmentedControlHeight:kMinSegmentedControlHeight);
}
@override
void updateRenderObject(
BuildContext context, _RenderSegmentedControl renderObject) {
renderObject
..textDirection = Directionality.of(context)
..selectedIndex =selectedIndex
..pressedIndex =pressedIndex
..backgroundColors =backgroundColors
..borderColor =borderColor;
}
}
class _SegmentedControlContainerBoxParentData
extends ContainerBoxParentData {
RRectsurroundingRect;
}
typedef _NextChild = RenderBox Function(RenderBox child);
class _RenderSegmentedControlextends RenderBox
with
ContainerRenderObjectMixin
ContainerBoxParentData>,
RenderBoxContainerDefaultsMixin
ContainerBoxParentData> {
_RenderSegmentedControl({
@required int selectedIndex,
@required int pressedIndex,
@required TextDirection textDirection,
@required List backgroundColors,
@required Color borderColor,
@required double radius,
@required double kMinSegmentedControlHeight,
}) :assert(textDirection !=null),
_textDirection = textDirection,
_selectedIndex = selectedIndex,
_pressedIndex = pressedIndex,
_backgroundColors = backgroundColors,
_borderColor = borderColor,
_radius = radius,
_kMinSegmentedControlHeight = kMinSegmentedControlHeight;
intget selectedIndex =>_selectedIndex;
int_selectedIndex;
set selectedIndex(int value) {
if (_selectedIndex == value) {
return;
}
_selectedIndex = value;
markNeedsPaint();
}
intget pressedIndex =>_pressedIndex;
int_pressedIndex;
set pressedIndex(int value) {
if (_pressedIndex == value) {
return;
}
_pressedIndex = value;
markNeedsPaint();
}
TextDirectionget textDirection =>_textDirection;
TextDirection_textDirection;
set textDirection(TextDirection value) {
if (_textDirection == value) {
return;
}
_textDirection = value;
markNeedsLayout();
}
Listget backgroundColors =>_backgroundColors;
List_backgroundColors;
set backgroundColors(List value) {
if (_backgroundColors == value) {
return;
}
_backgroundColors = value;
markNeedsPaint();
}
Colorget borderColor =>_borderColor;
Color_borderColor;
set borderColor(Color value) {
if (_borderColor == value) {
return;
}
_borderColor = value;
markNeedsPaint();
}
doubleget radius =>_radius;
double_radius;
set radius(double value) {
if (_radius == value) {
return;
}
_radius = value;
markNeedsPaint();
}
doubleget kMinSegmentedControlHeight =>_kMinSegmentedControlHeight;
double_kMinSegmentedControlHeight;
set kMinSegmentedControlHeight(double value) {
if (_kMinSegmentedControlHeight == value) {
return;
}
_kMinSegmentedControlHeight = value;
markNeedsPaint();
}
@override
doublecomputeMinIntrinsicWidth(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
doublecomputeMaxIntrinsicWidth(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
doublecomputeMinIntrinsicHeight(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
doublecomputeMaxIntrinsicHeight(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
doublecomputeDistanceToActualBaseline(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);
RRect rChildRect;
if (child == leftChild) {
rChildRect =RRect.fromRectAndCorners(childRect,
topLeft:Radius.circular(radius),
bottomLeft:Radius.circular(radius));
}else if (child == rightChild) {
rChildRect =RRect.fromRectAndCorners(childRect,
topRight:Radius.circular(radius),
bottomRight:Radius.circular(radius));
}else {
rChildRect =RRect.fromRectAndCorners(childRect);
}
childParentData.surroundingRect = rChildRect;
start += child.size.width;
child = nextChild(child);
}
}
@override
void performLayout() {
final BoxConstraints constraints =this.constraints;
double maxHeight =_kMinSegmentedControlHeight;
double childWidth = constraints.minWidth /childCount;
for (final RenderBox childin getChildrenAsList()) {
childWidth =
math.max(childWidth, child.getMaxIntrinsicWidth(double.infinity));
}
childWidth = math.min(childWidth, constraints.maxWidth /childCount);
RenderBox child =firstChild;
while (child !=null) {
final double boxHeight = child.getMaxIntrinsicHeight(childWidth);
maxHeight = math.max(maxHeight, boxHeight);
child = childAfter(child);
}
constraints.constrainHeight(maxHeight);
final BoxConstraints childConstraints =BoxConstraints.tightFor(
width: childWidth,
height: maxHeight,
);
child =firstChild;
while (child !=null) {
child.layout(childConstraints, parentUsesSize:true);
child = childAfter(child);
}
switch (textDirection) {
case TextDirection.rtl:
_layoutRects(
childBefore,
lastChild,
firstChild,
);
break;
case TextDirection.ltr:
_layoutRects(
childAfter,
firstChild,
lastChild,
);
break;
}
size = constraints.constrain(Size(childWidth *childCount, maxHeight));
}
@override
void paint(PaintingContext context, Offset offset) {
RenderBox child =firstChild;
int index =0;
while (child !=null) {
_paintChild(context, offset, child, index);
child = childAfter(child);
index +=1;
}
}
void _paintChild(
PaintingContext context, Offset offset, RenderBox child, int childIndex) {
assert(child !=null);
final _SegmentedControlContainerBoxParentData childParentData =
child.parentData 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
boolhitTestChildren(BoxHitTestResult result, {@required Offset position}) {
assert(position !=null);
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;
}
}
2.引用修改过得文件和组件,准备需要的数据,下面之所以用到用container包裹是因为我发现直接设置padding没有效果,只能靠中间的文字设置margin才能起到padding的作用
import 'package:people_manager/widget/segment_widget.dart' as sement;
//避免和系统组件冲突,因为名字一样
//下面是所需要的数据
Map<String, Container> map = {```
'0':Container(
decoration:BoxDecoration(borderRadius:BorderRadius.circular(20.0)),
margin:EdgeInsets.symmetric(horizontal:20.0),
child:Text('Apple'),
),
'1':Container(
decoration:BoxDecoration(borderRadius:BorderRadius.circular(20.0)),
margin:EdgeInsets.symmetric(horizontal:20.0),
child:Text('Orange'),
)
};
static String_fruit ='0';
主要使用代码
sement.CupertinoSegmentedControl(
kMinSegmentedControlHeight: 50,
radius: 20,
children: map,
// 数据
groupValue: _fruit,
// 选中的数据
onValueChanged: (fruit) {
setState(() {
// 数据改变时通过setState改变选中状态
_fruit = fruit;
});
},
unselectedColor: CupertinoColors.white,
// 未选中颜色
selectedColor: CupertinoColors.activeBlue,
// 选中颜色
borderColor: CupertinoColors.activeBlue,
// 边框颜色
pressedColor: const Color(0x33007AFF), // 点击时候的颜色
)