diff --git a/lib/flutter_icons.dart b/lib/flutter_icons.dart index 8e88658..866906d 100644 --- a/lib/flutter_icons.dart +++ b/lib/flutter_icons.dart @@ -14,3 +14,4 @@ export 'zocial.dart'; export 'foundation.dart'; export 'font_awesome_5.dart'; export 'flutter_icon_data.dart' show IconWeight; +export 'icon_toggle.dart'; diff --git a/lib/icon_toggle.dart b/lib/icon_toggle.dart new file mode 100644 index 0000000..c11e1a5 --- /dev/null +++ b/lib/icon_toggle.dart @@ -0,0 +1,143 @@ +/// +/// +/// 自定义的bool选择组件 +/// +/// +import 'package:flutter/material.dart'; +import 'dart:math' as math; + +Widget _defaultTransitionBuilder(Widget child, Animation animation) => + ScaleTransition( + scale: animation, + child: child, + ); + +class IconToggle extends StatefulWidget { + IconToggle({ + this.uncheckedIconData = Icons.radio_button_unchecked, + this.checkedIconData = Icons.radio_button_checked, + this.activeColor = Colors.blue, + this.inactiveColor = Colors.grey, + this.value = false, + this.onChanged, + this.transitionBuilder = _defaultTransitionBuilder, + this.duration = const Duration(milliseconds: 100), + this.reverseDuration, + }); + final IconData checkedIconData; + final IconData uncheckedIconData; + final Color activeColor; + final Color inactiveColor; + final bool value; + final ValueChanged onChanged; + final AnimatedSwitcherTransitionBuilder transitionBuilder; + final Duration duration; + final Duration reverseDuration; + @override + _IconToggleState createState() => _IconToggleState(); +} + +class _IconToggleState extends State + with SingleTickerProviderStateMixin { + AnimationController _controller; + Animation _position; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: Duration(milliseconds: 100), + reverseDuration: Duration(milliseconds: 50)); + _position = CurvedAnimation(parent: _controller, curve: Curves.linear); + _position.addStatusListener((status) { + if (status == AnimationStatus.dismissed && widget.onChanged != null) { + widget.onChanged(!widget.value); + } + }); + } + + @override + void dispose() { + _controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTapDown: (event) { + _controller?.forward(); + }, + onTapUp: (event) { + _controller?.reverse(); + }, + child: _IconToggleable( + listenable: _position, + activeColor: widget.activeColor, + inactiveColor: widget.inactiveColor, + child: AnimatedSwitcher( + duration: widget.duration, + reverseDuration: widget.reverseDuration, + transitionBuilder: widget.transitionBuilder, + child: Icon( + widget.value ? widget.checkedIconData : widget.uncheckedIconData, + color: widget.value ? widget.activeColor : widget.inactiveColor, + size: 22, + key: ValueKey(widget.value), + ), + ), + ), + ); + } +} + +class _IconToggleable extends AnimatedWidget { + _IconToggleable({ + Animation listenable, + this.activeColor, + this.inactiveColor, + this.child, + }) : super(listenable: listenable); + final Color activeColor; + final Color inactiveColor; + final Widget child; + @override + Widget build(BuildContext context) { + return CustomPaint( + painter: _IconPainter( + position: listenable, + activeColor: activeColor, + inactiveColor: inactiveColor, + ), + child: child, + ); + } +} + +class _IconPainter extends CustomPainter { + _IconPainter({ + @required this.position, + this.activeColor, + this.inactiveColor, + }); + final Animation position; + final Color activeColor; + final Color inactiveColor; + + double get _value => position != null ? position.value : 0; + + @override + void paint(Canvas canvas, Size size) { + final Paint paint = Paint() + ..color = Color.lerp(inactiveColor, activeColor, _value) + .withOpacity(math.min(_value, 0.1)) + ..style = PaintingStyle.fill + ..strokeWidth = 2.0; + canvas.drawCircle( + Offset(size.width / 2, size.height / 2), 20 * _value, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +}