Files
flutterApp/packages/cc_ui_kit/lib/src/cc_expand_slider.dart

304 lines
8.7 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:vibration/vibration.dart';
enum SliderAxis { horizontal, vertical }
class CCExpandSlider extends StatefulWidget {
final SliderAxis axis;
final bool isSpring;
final String label;
final String svgStart;
final String svgCenter;
final String svgEnd;
final double iconSize;
final double borderRadius;
final VoidCallback? onStart;
final VoidCallback? onCenter;
final VoidCallback? onEnd;
const CCExpandSlider({
super.key,
this.axis = SliderAxis.vertical,
this.isSpring = true,
required this.svgStart,
required this.svgCenter,
required this.svgEnd,
required this.label,
this.iconSize = 22.0,
this.borderRadius = 12.0,
this.onStart,
this.onCenter,
this.onEnd,
});
@override
State<CCExpandSlider> createState() => _CCExpandSliderState();
}
class _CCExpandSliderState extends State<CCExpandSlider> {
double _alignmentValue = 0.0;
bool _isExpanded = false;
Timer? _tickTimer;
int? _lastVibrationState;
Timer? _continuousVibrateTimer;
bool _hasCheckedVibrator = false;
bool? _hasVibrator;
@override
void initState() {
super.initState();
_checkVibrator();
}
Future<void> _checkVibrator() async {
_hasVibrator = await Vibration.hasVibrator();
setState(() {
_hasCheckedVibrator = true;
});
debugPrint('🔍 震动器检测:${_hasVibrator == true ? "有" : "无"}');
}
@override
void dispose() {
_tickTimer?.cancel();
_continuousVibrateTimer?.cancel();
super.dispose();
}
Future<void> _triggerVibration(int duration, String reason) async {
if (!_hasCheckedVibrator) return;
try {
if (_hasVibrator == true) {
await Vibration.vibrate(duration: duration);
debugPrint('📳 $reason - 震动 ${duration}ms');
} else {
debugPrint('⚠️ $reason - 设备不支持震动');
}
} catch (e) {
debugPrint('❌ $reason - 震动失败:$e');
}
}
void _vibrate(int state) {
if (state != _lastVibrationState) {
_triggerVibration(50, '状态变化');
_lastVibrationState = state;
}
}
void _startContinuousVibration() {
_continuousVibrateTimer?.cancel();
_continuousVibrateTimer = Timer.periodic(const Duration(milliseconds: 300), (_) {
if (!_isExpanded) return;
if (_alignmentValue < -0.5 || _alignmentValue > 0.5) {
_triggerVibration(30, '连续震动');
}
});
}
void _stopContinuousVibration() {
_continuousVibrateTimer?.cancel();
_continuousVibrateTimer = null;
}
void _startTicking() {
_tickTimer?.cancel();
_tickTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
if (!_isExpanded) return;
int currentState;
if (_alignmentValue < -0.5) {
currentState = -1;
widget.onStart?.call();
} else if (_alignmentValue > 0.5) {
currentState = 1;
widget.onEnd?.call();
} else {
currentState = 0;
widget.onCenter?.call();
}
_vibrate(currentState);
});
_startContinuousVibration();
}
void _safeSetExpanded(bool expanded) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
_isExpanded = expanded;
if (!expanded) {
_alignmentValue = 0.0;
_tickTimer?.cancel();
_continuousVibrateTimer?.cancel();
_lastVibrationState = null;
}
});
if (expanded) _startTicking();
}
});
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
final isVertical = widget.axis == SliderAxis.vertical;
final double maxLength = isVertical ? constraints.maxHeight : constraints.maxWidth;
final double minLength = isVertical ? constraints.maxWidth : constraints.maxHeight;
final double currentLength = _isExpanded ? maxLength : minLength;
final double dragArea = (maxLength - minLength) / 2;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onPanDown: (_) {
debugPrint('👆 按下');
if (_hasCheckedVibrator) {
_triggerVibration(40, '按下');
}
_safeSetExpanded(true);
},
onPanUpdate: (details) {
if (!_isExpanded) return;
setState(() {
final delta = isVertical ? details.delta.dy : details.delta.dx;
_alignmentValue = (_alignmentValue + delta / (dragArea > 0 ? dragArea : 1)).clamp(-1.0, 1.0);
});
},
onPanEnd: (_) {
debugPrint('✋ 松开');
if (widget.isSpring) widget.onCenter?.call();
if (_hasCheckedVibrator) {
_triggerVibration(40, '松开');
}
_lastVibrationState = null;
_stopContinuousVibration();
_safeSetExpanded(false);
},
onPanCancel: () {
debugPrint('❌ 取消');
_safeSetExpanded(false);
},
child: Stack(
alignment: Alignment.center,
clipBehavior: Clip.none,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOutCubic,
width: isVertical ? minLength : currentLength,
height: isVertical ? currentLength : minLength,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0),
borderRadius: BorderRadius.circular(widget.borderRadius),
border: Border.all(color: Colors.white.withOpacity(0.3)),
),
child: _buildSegmentedContent(isVertical, maxLength, minLength),
),
if (!_isExpanded)
IgnorePointer(
child: SizedBox(
width: minLength,
height: minLength,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Positioned(
bottom: -22,
child: Text(
widget.label,
style: TextStyle(
color: Colors.white.withValues(alpha: 0.5),
fontSize: 10,
fontWeight: FontWeight.w500
),
),
),
],
),
),
),
],
),
);
});
}
Widget _buildSegmentedContent(bool isVertical, double maxLength, double minLength) {
final double segmentHeight = maxLength / 3;
return Stack(
alignment: Alignment.center,
children: [
if (_isExpanded) ...[
Positioned(
top: 0,
left: 0,
right: 0,
height: segmentHeight,
child: Center(child: _svgItem(widget.svgStart, -1)),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
height: segmentHeight,
child: Center(child: _svgItem(widget.svgEnd, 1)),
),
],
Center(
child: SizedBox(
width: minLength,
height: minLength,
child: Center(child: _svgItem(widget.svgCenter, 0)),
),
),
if (_isExpanded)
Padding(
padding: const EdgeInsets.all(4.0),
child: Align(
alignment: isVertical ? Alignment(0, _alignmentValue) : Alignment(_alignmentValue, 0),
child: Container(
width: minLength * 0.82,
height: minLength * 0.82,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.28),
borderRadius: BorderRadius.circular(widget.borderRadius * 0.7),
),
),
),
),
],
);
}
Widget _svgItem(String assetPath, int type) {
Color color = Colors.white30;
if (_isExpanded) {
if (type == -1 && _alignmentValue < -0.5) color = const Color(0xFF0076F9);
else if (type == 1 && _alignmentValue > 0.5) color = const Color(0xFF0076F9);
else if (type == 0 && _alignmentValue.abs() <= 0.5) color = const Color(0xFF0076F9);
else color = Colors.white;
} else {
color = Colors.white;
}
return SvgPicture.asset(
assetPath,
width: widget.iconSize,
height: widget.iconSize,
fit: BoxFit.contain,
colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
);
}
}