You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在QML中实现类iOS左右滑动开关组件?已尝试Slider/SwipeDelegate

实现iOS风格滑动触发开关组件

核心思路

不用Slider(它是连续取值组件,核心是「选择数值」,而非「触发式确认」),也不用SwipeDelegate(它更适配页面级滑动操作),而是用GestureDetector监听拖动手势,结合AnimatedContainer做滑块的平滑动画,当拖动距离达到容器宽度的设定阈值时,触发ON/OFF状态切换。

完整代码实现

import 'package:flutter/material.dart';

class SlideToggleSwitch extends StatefulWidget {
  final bool initialValue;
  final ValueChanged<bool>? onChanged;
  final String onText;
  final String offText;
  final Color activeColor;
  final Color inactiveColor;

  const SlideToggleSwitch({
    super.key,
    this.initialValue = false,
    this.onChanged,
    this.onText = "ON",
    this.offText = "OFF",
    this.activeColor = Colors.green,
    this.inactiveColor = Colors.grey,
  });

  @override
  State<SlideToggleSwitch> createState() => _SlideToggleSwitchState();
}

class _SlideToggleSwitchState extends State<SlideToggleSwitch> {
  late bool _isActive;
  double _sliderPosition = 0;
  late double _containerWidth;
  final double _sliderWidth = 60;
  final double _thresholdRatio = 0.7; // 滑动到70%宽度触发状态切换

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

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        _containerWidth = constraints.maxWidth;
        // 初始化滑块位置
        if (_sliderPosition == 0) {
          _sliderPosition = _isActive ? _containerWidth - _sliderWidth : 0;
        }
        return GestureDetector(
          onHorizontalDragUpdate: (details) {
            setState(() {
              // 限制滑块在容器范围内移动
              _sliderPosition = (_sliderPosition + details.delta.dx)
                  .clamp(0.0, _containerWidth - _sliderWidth);
            });
          },
          onHorizontalDragEnd: (details) {
            bool shouldActivate = _sliderPosition > _containerWidth * _thresholdRatio;
            setState(() {
              // 滑动结束后,根据阈值切换状态,并让滑块平滑归位
              _isActive = shouldActivate;
              _sliderPosition = shouldActivate
                  ? _containerWidth - _sliderWidth
                  : 0;
            });
            // 回调状态变更
            if (widget.onChanged != null && shouldActivate != _isActive) {
              widget.onChanged!(shouldActivate);
            }
          },
          child: Container(
            height: 48,
            width: double.infinity,
            decoration: BoxDecoration(
              color: _isActive ? widget.activeColor : widget.inactiveColor,
              borderRadius: BorderRadius.circular(24),
            ),
            padding: const EdgeInsets.symmetric(horizontal: 8),
            child: Stack(
              alignment: Alignment.centerLeft,
              children: [
                // 状态文本
                Align(
                  alignment: Alignment.center,
                  child: Text(
                    _isActive ? widget.onText : widget.offText,
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 16,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                ),
                // 滑动滑块
                AnimatedContainer(
                  duration: const Duration(milliseconds: 200),
                  curve: Curves.easeInOut,
                  width: _sliderWidth,
                  height: 36,
                  margin: EdgeInsets.only(left: _sliderPosition),
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.circular(18),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.black.withOpacity(0.1),
                        blurRadius: 4,
                        offset: const Offset(0, 2),
                      ),
                    ],
                  ),
                  child: const Icon(
                    Icons.arrow_forward_ios,
                    size: 16,
                    color: Colors.grey,
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

// 使用示例
class DemoPage extends StatelessWidget {
  const DemoPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("滑动开关示例")),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text("滑动滑块触发状态切换"),
            const SizedBox(height: 20),
            SlideToggleSwitch(
              initialValue: false,
              onText: "确认关机",
              offText: "滑动关机",
              activeColor: Colors.red,
              onChanged: (value) {
                if (value) {
                  // 触发关机逻辑
                  print("执行关机操作");
                }
              },
            ),
          ],
        ),
      ),
    );
  }
}

关键细节说明

  • 手势监听:通过onHorizontalDragUpdate实时更新滑块位置,onHorizontalDragEnd判断是否达到触发阈值
  • 阈值控制:_thresholdRatio可自定义触发状态切换的滑动距离比例,按需调整
  • 动画过渡:AnimatedContainer实现滑块归位的平滑动画,优化交互体验
  • 自定义配置:支持修改初始状态、文本、颜色等参数,适配不同场景

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 20:12:43