Flutter 重复造轮子 (7) Switch 开关组件的封装
2023-09-16 本文已影响0人
半城半离人
介绍
Switch组件的增强版.用于在打开和关闭状态之间进行切换。支持更换背景/增加文字 Icon等
![](https://img.haomeiwen.com/i27902463/8455bb225f6ce8ff.gif)
代码演示
基础用法
HcSwitch(
onChanged: (bool value) {
return Future.value(!value);
},
value: true,
),
修改圆角
HcSwitch(
radius: 3,
onChanged: (bool value) {
return Future.value(!value);
},
value: true,
),
禁用状态
HcSwitch(
onChanged: (bool value) {
return Future.value(!value);
},
value: true,
disabled: true,
),
修改颜色
HcSwitch(
radius: 3,
inactiveThumbColor: Colors.yellow,
inactiveTrackColor: Colors.grey,
activeColor: Colors.purple,
activeTrackColor: Colors.green,
onChanged: (bool value) {
return Future.value(!value);
},
value: false,
),
修改大小
HcSwitch(
radius: 3,
size:48,
inactiveThumbColor: Colors.yellow,
inactiveTrackColor: Colors.grey,
activeColor: Colors.purple,
activeTrackColor: Colors.green,
onChanged: (bool value) {
return Future.value(!value);
},
value: false,
),
显示文字
HcSwitch(
activeWidget: Text("开"),
inactiveWidget: Text("关"),
showPosition: HcSwitchShowPosition.thumb,
activeColor: Colors.purple,
activeTrackColor: Colors.green,
onChanged: (bool value) {
return Future.value(!value);
},
value: true,
),
修改按钮组件
HcSwitch(
thumbWidget: HcImage(
src: "assets/images/icon.jpeg",
),
inactiveWidget: Text("关"),
showPosition: HcSwitchShowPosition.track,
onChanged: (bool value) {
return Future.value(!value);
},
value: true,
),
增加Loading动画
HcSwitch(
radius: 3,
thumbWidget: HcImage(
src: "assets/images/icon.jpeg",
borderRadius: BorderRadius.circular(3),
),
inactiveThumbColor: Colors.yellow,
inactiveTrackColor: Colors.grey,
showLoading: true,
activeWidget: Text("开"),
inactiveWidget: Text("关"),
showPosition: HcSwitchShowPosition.all,
onChanged: (bool value) async {
print("value${value}");
await Future.delayed(Duration(seconds: 2), () {
print('延迟2秒后执行');
});
return Future.value(!value);
},
value: false,
),
API
props
参数 | 说明 | 类型 | 默认值 | 是否必填 |
---|---|---|---|---|
value | switch的状态 | bool | false | false |
onChanged | 点击后的回调 | HcSwitchChange | - | true |
disabled | 是否禁用 | bool | - | false |
activeColor | 激活状态下按钮颜色 | color | - | false |
activeTrackColor | 激活状态下轨道颜色 | color | - | false |
inactiveThumbColor | 非激活状态下按钮颜色 | color | - | false |
inactiveTrackColor | 非激活状态下按钮颜色 | color | - | false |
thumbWidget | 按钮的组件 | Widget | - | false |
showLoading | 是否展示加载中动画 | bool | false | false |
size | 按钮的大小 | double | 20.0 | false |
radius | 按钮的圆角 | double | 10.0 | false |
padding | 轨道和按钮中间的距离 | double | 1.0 | false |
activeWidget | 激活时显示的组件(text/icon) | Widget | - | false |
inactiveWidget | 未激活时显示的组件 (text/icon) | Widget | - | false |
showPosition | 显示组件的位置 | HcSwitchShowPosition | - | false |
duration | 动画时间 | Duration | 300ms | false |
HcSwitchShowPosition
展示activeWidget/inactiveWidget组件的位置
参数名 | 说明 |
---|---|
none | 不展示 |
track | 只展示在轨道上 |
thumb | 只展示在按钮上 |
all | 展示按钮和轨道上 |
Function
方法名 | 说明 | 参数 | 返回类型 |
---|---|---|---|
HcSwitchChange | 滑动/点击后的回调 | Function(bool) | Future<bool?> |
项目源码
//展示组件的位置 none不显示 track轨道上 thumb 按钮上 ,all全部
enum HcSwitchShowPosition { none, track, thumb, all }
typedef HcSwitchChange = Future<bool?> Function(bool);
class HcSwitch extends StatefulWidget {
//点击后的回调
final HcSwitchChange onChanged;
//当前状态
final bool value;
//是否禁用
final bool disabled;
//激活状态下按钮颜色
final Color? activeColor;
//激活状态下轨道颜色
final Color? activeTrackColor;
//关闭状态下按钮颜色
final Color? inactiveThumbColor;
//关闭状态下轨道颜色
final Color? inactiveTrackColor;
//按钮的组件
final Widget? thumbWidget;
//是否展示加载中
final bool showLoading;
//按钮大小
final double size;
//组件和按钮的圆角
final double? radius;
//轨道和按钮间的距离
final double padding;
//激活状态显示的组件
final Widget? activeWidget;
//未激活状态显示的组件
final Widget? inactiveWidget;
//显示组件的位置
final HcSwitchShowPosition showPosition;
//动画时间
final Duration? duration;
const HcSwitch(
{Key? key,
required this.onChanged,
this.value = false,
this.disabled = false,
this.activeColor,
this.activeTrackColor,
this.inactiveThumbColor,
this.inactiveTrackColor,
this.thumbWidget,
this.showLoading = false,
this.size = 20,
this.radius,
this.duration,
this.padding = 3,
this.activeWidget,
this.inactiveWidget,
this.showPosition = HcSwitchShowPosition.none})
: super(key: key);
@override
State<HcSwitch> createState() => _HcSwitchState();
}
class _HcSwitchState extends State<HcSwitch> {
//手指滑动的距离
double dragDistance = 0;
//当前选中的状态
bool value = false;
//当前是否展示加载中效果
bool showLoading = false;
//圆角
double radius = 0;
//需要展示的 组件列表
List<Widget> widgetList = List.empty();
@override
void initState() {
// TODO: implement initState
super.initState();
value = widget.value;
radius = widget.radius ?? (widget.size + widget.padding * 2) / 2;
widgetList = [
widget.activeWidget ?? const Text(""),
widget.inactiveWidget ?? const Text(""),
];
if (widget.showPosition == HcSwitchShowPosition.all) {
widgetList = widgetList.reversed.toList();
}
}
@override
void didUpdateWidget(HcSwitch oldWidget) {
super.didUpdateWidget(oldWidget);
widgetList = [
widget.activeWidget ?? const Text(""),
widget.inactiveWidget ?? const Text(""),
];
if (widget.showPosition == HcSwitchShowPosition.all) {
widgetList = widgetList.reversed.toList();
}
setState(() {
value = widget.value;
radius = widget.radius ?? (widget.size + widget.padding * 2) / 2;
widgetList = widgetList;
});
}
@override
Widget build(BuildContext context) {
return Semantics(
toggled: value,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(radius),
),
child: Stack(
children: [_trackWidget(), _thumbWidget()],
),
),
);
}
//轨道构建组件
Widget _trackWidget() {
Color trackColor = value
? (widget.activeTrackColor ?? Theme.of(context).primaryColor)
: (widget.inactiveTrackColor ?? Colors.grey);
return GestureDetector(
excludeFromSemantics: true,
onTap: _onTap,
child: AnimatedContainer(
width: widget.size * 2 + widget.padding * 2,
height: widget.size + widget.padding * 2,
decoration: BoxDecoration(
color: trackColor.withOpacity(widget.disabled
? HcSize.defaultSwitchDisableOpacity
: HcSize.defaultSwitchOpacity),
borderRadius: BorderRadius.circular(radius),
),
duration: widget.duration ?? const Duration(milliseconds: 500),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: widget.showPosition == HcSwitchShowPosition.track ||
widget.showPosition == HcSwitchShowPosition.all
? widgetList
.map((child) => _buildListItem(child, trackColor))
.toList()
: [const SizedBox(), const SizedBox()],
),
),
);
}
Widget _buildListItem(Widget child, Color color) {
final ThemeData theme = Theme.of(context);
final TextStyle effectiveTextStyle = theme.useMaterial3
? theme.textTheme.titleMedium!
: theme.primaryTextTheme.titleMedium!;
TextStyle textStyle =
effectiveTextStyle.copyWith(color: color, fontSize: widget.size / 2);
switch (ThemeData.estimateBrightnessForColor(color)) {
case Brightness.dark:
textStyle = textStyle.copyWith(color: theme.primaryColorLight);
break;
case Brightness.light:
textStyle = textStyle.copyWith(color: theme.primaryColorDark);
break;
}
return SizedBox(
width: widget.size,
height: widget.size,
child: Center(
child: MediaQuery(
// Need to ignore the ambient textScaleFactor here so that the
// text doesn't escape the avatar when the textScaleFactor is large.
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: IconTheme(
data: theme.iconTheme
.copyWith(color: textStyle.color, size: widget.size / 2),
child: DefaultTextStyle(
style: textStyle,
child: child,
),
),
),
),
);
}
//顶部按钮组件
Widget _thumbWidget() {
Color bgColor = value
? (widget.activeColor ?? Theme.of(context).colorScheme.secondary)
: (widget.inactiveThumbColor ?? Colors.white);
//动画组件
return AnimatedPositioned(
left: !value ? 0 : 1 * widget.size,
duration: widget.duration ?? const Duration(milliseconds: 100),
child: GestureDetector(
excludeFromSemantics: true,
//滑动开始
onHorizontalDragStart: _onHorizontalDragStart,
//滑动更新
onHorizontalDragUpdate: _onHorizontalDragUpdate,
//滑动结束
onHorizontalDragEnd: _onHorizontalDragEnd,
//点击事件
onTap: _onTap,
child: AnimatedContainer(
width: widget.size,
height: widget.size,
margin: EdgeInsets.all(widget.padding),
decoration: BoxDecoration(
color: bgColor, borderRadius: BorderRadius.circular(radius)),
duration: const Duration(milliseconds: 300),
child: Center(
child: !showLoading
? widget.thumbWidget ??
(widget.showPosition.index <
HcSwitchShowPosition.thumb.index
? const Text("")
: widgetList
.map((child) => _buildListItem(child, bgColor))
.toList()[value ? 1 : 0])
: Padding(
padding: EdgeInsets.all(widget.size / 5),
child: CircularProgressIndicator(
strokeWidth: widget.size / 20,
color: HcColorUtil.isLightColor(bgColor)
? Theme.of(context).primaryColorDark
: Theme.of(context).primaryColorLight,
),
),
),
),
));
}
//点击事件
void _onTap() {
dragDistance = widget.size * (value ? -1 : 1);
changeState();
}
//手指结束
void _onHorizontalDragEnd(details) => changeState();
//手指更新
void _onHorizontalDragUpdate(value) => dragDistance = value.localPosition.dx;
//手指滑动开始
void _onHorizontalDragStart(value) => dragDistance = 0;
changeState() async {
if (widget.disabled) return;
if ((value && dragDistance > 0) || (!value && dragDistance < 0)) {
return;
}
//如果参数不为空
if (widget.showLoading) {
setState(() {
showLoading = true;
});
}
bool result = !value;
try {
result = (await widget.onChanged.call(value)) ?? !value;
} finally {}
setState(() {
value = result;
showLoading = false;
});
}
}