如何在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
相关产品推荐
相关产品推荐

