美文网首页
Flutter 重复造轮子 (7) Switch 开关组件的封装

Flutter 重复造轮子 (7) Switch 开关组件的封装

作者: 半城半离人 | 来源:发表于2023-09-16 09:19 被阅读0次

详细可以访问仓库 HcUi: 重复创造Flutter 的轮子 在原有组件上拓展 展现出新的特性 (gitee.com)

介绍

Switch组件的增强版.用于在打开和关闭状态之间进行切换。支持更换背景/增加文字 Icon等


pt304-mte45.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;
    });
  }
}

相关文章

网友评论

      本文标题:Flutter 重复造轮子 (7) Switch 开关组件的封装

      本文链接:https://www.haomeiwen.com/subject/psybsdtx.html