You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Flutter应用中自定义符合UI需求的开关按钮?

实现Flutter自定义UI的开关按钮

我之前也碰到过默认Switch组件完全匹配不上设计稿的情况,折腾出几个实用的方案,分享给你:

方案1:用基础组件快速组合(入门友好)

这个方法不用自定义绘制,靠AnimatedContainer+GestureDetector就能快速实现带动画的自定义开关,适合大多数场景。

class CustomSwitch extends StatefulWidget {
  final bool initialValue;
  final ValueChanged<bool> onChanged;

  const CustomSwitch({
    super.key,
    required this.initialValue,
    required this.onChanged,
  });

  @override
  State<CustomSwitch> createState() => _CustomSwitchState();
}

class _CustomSwitchState extends State<CustomSwitch> {
  late bool _isEnabled;

  @override
  void initState() {
    super.initState();
    _isEnabled = widget.initialValue;
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() => _isEnabled = !_isEnabled);
        widget.onChanged(_isEnabled);
      },
      // 用AnimatedContainer实现平滑过渡
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 200),
        curve: Curves.easeInOut,
        width: 60,
        height: 32,
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(30),
          // 根据状态切换背景色
          color: _isEnabled ? Colors.greenAccent : Colors.grey.shade300,
        ),
        child: Align(
          // 滑块位置随状态变化
          alignment: _isEnabled ? Alignment.centerRight : Alignment.centerLeft,
          child: Container(
            width: 28,
            height: 28,
            margin: const EdgeInsets.symmetric(horizontal: 2),
            decoration: const BoxDecoration(
              color: Colors.white,
              shape: BoxShape.circle,
              boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 2)],
            ),
          ),
        ),
      ),
    );
  }
}

使用的时候直接调用:

CustomSwitch(
  initialValue: false,
  onChanged: (value) {
    // 处理开关状态变化逻辑
    print("开关状态:$value");
  },
)

方案2:用CustomPainter实现完全自定义形状(进阶)

如果你的设计需要特殊形状(比如方形切角、渐变背景、自定义滑块),可以用CustomPainter来绘制开关的每一部分,自由度拉满。

class CustomSwitchPainter extends CustomPainter {
  final bool isEnabled;

  CustomSwitchPainter({required this.isEnabled});

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..style = PaintingStyle.fill
      ..color = isEnabled ? Colors.blue.shade600 : Colors.grey.shade400;

    // 绘制开关背景(圆角矩形)
    canvas.drawRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(0, 0, size.width, size.height),
        Radius.circular(size.height / 2),
      ),
      paint,
    );

    // 绘制滑块(圆形)
    paint.color = Colors.white;
    final sliderX = isEnabled ? size.width - size.height + 4 : 4;
    canvas.drawCircle(
      Offset(sliderX, size.height / 2),
      size.height / 2 - 4,
      paint,
    );
  }

  @override
  bool shouldRepaint(covariant CustomSwitchPainter oldDelegate) {
    return oldDelegate.isEnabled != isEnabled;
  }
}

// 封装成可复用组件
class AdvancedCustomSwitch extends StatefulWidget {
  final bool initialValue;
  final ValueChanged<bool> onChanged;

  const AdvancedCustomSwitch({
    super.key,
    required this.initialValue,
    required this.onChanged,
  });

  @override
  State<AdvancedCustomSwitch> createState() => _AdvancedCustomSwitchState();
}

class _AdvancedCustomSwitchState extends State<AdvancedCustomSwitch> {
  late bool _isEnabled;

  @override
  void initState() {
    super.initState();
    _isEnabled = widget.initialValue;
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() => _isEnabled = !_isEnabled);
        widget.onChanged(_isEnabled);
      },
      child: AnimatedSwitcher(
        duration: const Duration(milliseconds: 200),
        child: CustomPaint(
          key: ValueKey(_isEnabled),
          size: const Size(64, 36),
          painter: CustomSwitchPainter(isEnabled: _isEnabled),
        ),
      ),
    );
  }
}

额外优化小技巧

  • 替换GestureDetectorInkWell,可以获得Material风格的水波纹点击反馈
  • 添加点击时的缩放动画:在GestureDetectoronTapDownonTapUp里修改缩放系数,用Transform.scale包裹组件
  • 支持自定义尺寸、颜色:把颜色、宽高做成组件参数,方便复用不同风格的开关

内容的提问来源于stack exchange,提问作者Sumit Tifane

火山引擎 最新活动