import 'package:flutter/material.dart'; import 'package:wow_english/common/widgets/throttledGesture_gesture_detector.dart'; import '../../utils/log_util.dart'; /// 选择题选项组件,具备答错摇晃、答对缩放动效能力 class OptionWidget extends StatefulWidget { final Widget child; //正确缩放动画;错误摇晃动画;为空即非选中选项,无操作 final bool? isCorrect; final bool isClickable; final VoidCallback? onTap; const OptionWidget({ Key? key, required this.child, this.isCorrect, this.isClickable = true, this.onTap, }) : super(key: key); @override _OptionWidgetState createState() => _OptionWidgetState(); } class _OptionWidgetState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _shakeAnimation; late Animation _scaleAnimation; static const String TAG = 'OptionWidget'; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), ); _shakeAnimation = TweenSequence([ TweenSequenceItem( tween: Tween(begin: 0.0, end: 10.0) .chain(CurveTween(curve: Curves.easeInOut)), weight: 1, ), TweenSequenceItem( tween: Tween(begin: 10.0, end: -10.0) .chain(CurveTween(curve: Curves.easeInOut)), weight: 1, ), TweenSequenceItem( tween: Tween(begin: -10.0, end: 10.0) .chain(CurveTween(curve: Curves.easeInOut)), weight: 1, ), TweenSequenceItem( tween: Tween(begin: 10.0, end: -10.0) .chain(CurveTween(curve: Curves.easeInOut)), weight: 1, ), TweenSequenceItem( tween: Tween(begin: -10.0, end: 0.0) .chain(CurveTween(curve: Curves.easeInOut)), weight: 1, ), ]).animate(_controller); _scaleAnimation = TweenSequence([ TweenSequenceItem(tween: Tween(begin: 1.0, end: 0.8), weight: 1), TweenSequenceItem(tween: Tween(begin: 0.8, end: 1.25), weight: 1), TweenSequenceItem(tween: Tween(begin: 1.25, end: 1.0), weight: 1), ]).animate(CurvedAnimation( parent: _controller, curve: Curves.easeInOut, )); } @override void didUpdateWidget(OptionWidget oldWidget) { super.didUpdateWidget(oldWidget); Log.d( '$TAG ${identityHashCode(this)} didUpdateWidget widget.isCorrect=${widget.isCorrect} oldWidget.isCorrect=${oldWidget.isCorrect} isAnimation=${_controller.isAnimating} status=${_controller.status}'); if (widget.isCorrect == oldWidget.isCorrect && _controller.isAnimating) { return; } _controller.reset(); if (widget.isCorrect == true) { _controller.forward(); } else if (widget.isCorrect == false) { _controller.forward(); } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { double offsetX = widget.isCorrect == false ? _shakeAnimation.value : 0; double scale = widget.isCorrect == true ? _scaleAnimation.value : 1.0; return ThrottledGestureDetector( onTap: widget.isClickable ? widget.onTap : null, child: Transform.translate( offset: Offset(offsetX, 0), child: Transform.scale( scale: scale, child: Opacity( opacity: widget.isClickable ? 1.0 : 0.5, child: widget.child, ), ), ), ); }, ); } }