|
|
@@ -0,0 +1,130 @@
|
|
|
+import 'package:flutter/cupertino.dart';
|
|
|
+import 'package:flutter/material.dart';
|
|
|
+
|
|
|
+class GradientSwitch extends StatefulWidget {
|
|
|
+ final bool value;
|
|
|
+ final Future<bool> Function(bool)? onChanged;
|
|
|
+ final double width;
|
|
|
+ final double height;
|
|
|
+ final Duration animationDuration;
|
|
|
+ final double indicatorSize;
|
|
|
+ final LinearGradient selectedGradient;
|
|
|
+ final LinearGradient unselectedGradient;
|
|
|
+ final Color? loadingColor;
|
|
|
+
|
|
|
+ const GradientSwitch({
|
|
|
+ super.key,
|
|
|
+ required this.value,
|
|
|
+ this.onChanged,
|
|
|
+ this.width = 60.0,
|
|
|
+ this.height = 30.0,
|
|
|
+ this.animationDuration = const Duration(milliseconds: 300),
|
|
|
+ this.indicatorSize = 24.0,
|
|
|
+ this.selectedGradient =
|
|
|
+ const LinearGradient(colors: [Colors.blue, Colors.purpleAccent]),
|
|
|
+ this.unselectedGradient =
|
|
|
+ const LinearGradient(colors: [Color(0xFFEEEEEE), Color(0xFFBDBDBD)]),
|
|
|
+ this.loadingColor,
|
|
|
+ });
|
|
|
+
|
|
|
+ @override
|
|
|
+ State<GradientSwitch> createState() => _GradientSwitchState();
|
|
|
+}
|
|
|
+
|
|
|
+class _GradientSwitchState extends State<GradientSwitch>
|
|
|
+ with SingleTickerProviderStateMixin {
|
|
|
+ late bool _currentValue;
|
|
|
+ bool _isLoading = false;
|
|
|
+
|
|
|
+ @override
|
|
|
+ void initState() {
|
|
|
+ super.initState();
|
|
|
+ _currentValue = widget.value;
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void didUpdateWidget(GradientSwitch oldWidget) {
|
|
|
+ super.didUpdateWidget(oldWidget);
|
|
|
+ if (widget.value != _currentValue && !_isLoading) {
|
|
|
+ _currentValue = widget.value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<void> _handleTap() async {
|
|
|
+ if (_isLoading || widget.onChanged == null) return;
|
|
|
+
|
|
|
+ setState(() => _isLoading = true);
|
|
|
+
|
|
|
+ final targetValue = !_currentValue;
|
|
|
+ bool? success;
|
|
|
+
|
|
|
+ try {
|
|
|
+ success = await widget.onChanged!(targetValue);
|
|
|
+ } finally {
|
|
|
+ setState(() => _isLoading = false);
|
|
|
+
|
|
|
+ if (success != null && mounted) {
|
|
|
+ setState(() => _currentValue = success!);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void dispose() {
|
|
|
+ super.dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ return GestureDetector(
|
|
|
+ onTap: _handleTap,
|
|
|
+ child: AnimatedContainer(
|
|
|
+ duration: widget.animationDuration,
|
|
|
+ width: widget.width,
|
|
|
+ height: widget.height,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ borderRadius: BorderRadius.circular(widget.height / 2),
|
|
|
+ gradient: _currentValue
|
|
|
+ ? widget.selectedGradient
|
|
|
+ : widget.unselectedGradient,
|
|
|
+ boxShadow: [
|
|
|
+ BoxShadow(
|
|
|
+ color: Colors.black.withOpacity(0.1),
|
|
|
+ blurRadius: 4,
|
|
|
+ offset: const Offset(0, 2),
|
|
|
+ )
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ child: AnimatedAlign(
|
|
|
+ duration: widget.animationDuration,
|
|
|
+ alignment:
|
|
|
+ _currentValue ? Alignment.centerRight : Alignment.centerLeft,
|
|
|
+ child: Container(
|
|
|
+ margin: const EdgeInsets.symmetric(horizontal: 3),
|
|
|
+ width: widget.indicatorSize,
|
|
|
+ height: widget.indicatorSize,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ shape: BoxShape.circle,
|
|
|
+ color: Colors.white,
|
|
|
+ boxShadow: [
|
|
|
+ BoxShadow(
|
|
|
+ color: Colors.black.withOpacity(0.2),
|
|
|
+ blurRadius: 4,
|
|
|
+ offset: const Offset(0, 2),
|
|
|
+ )
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ child: _isLoading
|
|
|
+ ? Center(
|
|
|
+ child: CupertinoActivityIndicator(
|
|
|
+ color: widget.loadingColor,
|
|
|
+ radius: widget.indicatorSize * 0.3,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ : null, // 非加载状态不显示内容
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|