133 lines
4.2 KiB
Dart
133 lines
4.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
class MiddleExpandSlider extends StatefulWidget {
|
|
final VoidCallback onTop; // 对应 Compose 的 onTop (例如:升)
|
|
final VoidCallback onMiddle; // 对应 Compose 的 onMiddle (例如:停止/复位)
|
|
final VoidCallback onBottom; // 对应 Compose 的 onBottom (例如:降)
|
|
final IconData iconTop;
|
|
final IconData iconMiddle;
|
|
final IconData iconBottom;
|
|
|
|
const MiddleExpandSlider({
|
|
super.key,
|
|
required this.onTop,
|
|
required this.onMiddle,
|
|
required this.onBottom,
|
|
required this.iconTop,
|
|
required this.iconMiddle,
|
|
required this.iconBottom,
|
|
});
|
|
|
|
@override
|
|
State<MiddleExpandSlider> createState() => _MiddleExpandSliderState();
|
|
}
|
|
|
|
class _MiddleExpandSliderState extends State<MiddleExpandSlider> {
|
|
// 0: Top, 1: Middle, 2: Bottom
|
|
int _currentIndex = 1;
|
|
|
|
// 处理滑动更新逻辑
|
|
void _handleDragUpdate(DragUpdateDetails details, double maxHeight) {
|
|
// 将 120 的高度分为三等份
|
|
double localY = details.localPosition.dy;
|
|
int newIndex;
|
|
|
|
if (localY < maxHeight / 3) {
|
|
newIndex = 0;
|
|
} else if (localY > (maxHeight / 3) * 2) {
|
|
newIndex = 2;
|
|
} else {
|
|
newIndex = 1;
|
|
}
|
|
|
|
if (newIndex != _currentIndex) {
|
|
setState(() => _currentIndex = newIndex);
|
|
// 触发对应的指令回调
|
|
if (_currentIndex == 0) widget.onTop();
|
|
if (_currentIndex == 1) widget.onMiddle();
|
|
if (_currentIndex == 2) widget.onBottom();
|
|
|
|
// 触感反馈:对应 Android 的 VibrateOnce(current, 20)
|
|
HapticFeedback.lightImpact();
|
|
}
|
|
}
|
|
|
|
// 对应 Compose 的松手回弹逻辑
|
|
void _handleDragEnd() {
|
|
if (_currentIndex != 1) {
|
|
setState(() => _currentIndex = 1);
|
|
widget.onMiddle(); // 回到中间,停止动作
|
|
HapticFeedback.selectionClick();
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
const double sliderHeight = 120.0;
|
|
const double sliderWidth = 45.0;
|
|
|
|
return GestureDetector(
|
|
onVerticalDragUpdate: (details) =>
|
|
_handleDragUpdate(details, sliderHeight),
|
|
onVerticalDragEnd: (_) => _handleDragEnd(),
|
|
child: Container(
|
|
width: sliderWidth,
|
|
height: sliderHeight,
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.withOpacity(
|
|
0.2,
|
|
), // 对应 Color.Gray.copy(alpha = 0.4f)
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: Colors.white.withOpacity(0.2), width: 0.5),
|
|
),
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
// 1. 背景图标层:提示用户上下功能
|
|
Column(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
Icon(widget.iconTop, color: Colors.white12, size: 20),
|
|
Icon(widget.iconMiddle, color: Colors.white12, size: 20),
|
|
Icon(widget.iconBottom, color: Colors.white12, size: 20),
|
|
],
|
|
),
|
|
|
|
// 2. 活动滑块:对应 Compose 中的蓝色选中状态
|
|
AnimatedPositioned(
|
|
duration: const Duration(milliseconds: 150),
|
|
curve: Curves.easeOutBack, // 增加一点点弹簧感
|
|
top: _currentIndex == 0 ? 5 : (_currentIndex == 1 ? 40 : 75),
|
|
child: Container(
|
|
width: 38,
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xCC0078D4), // 对应 Compose 里的蓝色
|
|
borderRadius: BorderRadius.circular(8),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: const Color(0xCC0078D4).withOpacity(0.4),
|
|
blurRadius: 8,
|
|
spreadRadius: 1,
|
|
),
|
|
],
|
|
),
|
|
child: Icon(
|
|
_currentIndex == 0
|
|
? widget.iconTop
|
|
: (_currentIndex == 1
|
|
? widget.iconMiddle
|
|
: widget.iconBottom),
|
|
color: Colors.white,
|
|
size: 22,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|