From 61e3478a6898fa42d40d4abdfcabe95fc69aada1 Mon Sep 17 00:00:00 2001 From: wuqifeng <540416539@qq.com> Date: Fri, 2 Aug 2024 00:05:09 +0800 Subject: [PATCH] feat:封装具备答错摇晃、答对缩放动效能力的选择题选项组件 --- lib/common/widgets/option_widget.dart | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/pages/practice/topic_picture_page.dart | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------------------------------------------------------------------------------- 2 files changed, 244 insertions(+), 135 deletions(-) create mode 100644 lib/common/widgets/option_widget.dart diff --git a/lib/common/widgets/option_widget.dart b/lib/common/widgets/option_widget.dart new file mode 100644 index 0000000..f211cb8 --- /dev/null +++ b/lib/common/widgets/option_widget.dart @@ -0,0 +1,126 @@ +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 didUpdateWidget widget.isCorrect=${widget.isCorrect} oldWidget.isCorrect=${oldWidget.isCorrect} isAnimation=${_controller.isAnimating} status=${_controller.status}'); + if (widget.isCorrect != oldWidget.isCorrect) { + _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, + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/practice/topic_picture_page.dart b/lib/pages/practice/topic_picture_page.dart index 0cba6e7..799c333 100644 --- a/lib/pages/practice/topic_picture_page.dart +++ b/lib/pages/practice/topic_picture_page.dart @@ -7,10 +7,9 @@ import 'package:wow_english/common/extension/string_extension.dart'; import 'package:wow_english/common/widgets/ow_image_widget.dart'; import 'package:wow_english/models/course_process_entity.dart'; import 'package:wow_english/pages/practice/topic_type.dart'; -import 'package:wow_english/pages/practice/widgets/shake_widget.dart'; import 'package:wow_english/route/route.dart'; -import 'package:wow_english/utils/toast_util.dart'; +import '../../common/widgets/option_widget.dart'; import '../../common/widgets/recorder_widget.dart'; import '../../common/widgets/speaker_widget.dart'; import 'bloc/topic_picture_bloc.dart'; @@ -168,34 +167,30 @@ class _TopicPicturePage extends StatelessWidget { final isAnswerOption = bloc.selectItem == index; final answerCorrect = isAnswerOption && bloc.checkAnswerRight(index) == true; - final answerIncorrect = - isAnswerOption && bloc.checkAnswerRight(index) == false; return Container( padding: EdgeInsets.symmetric(horizontal: 10.w), - child: GestureDetector( + child: OptionWidget( onTap: () => bloc.add(SelectItemEvent(index)), - child: ShakeWidget( - shouldShake: answerIncorrect, + isCorrect: isAnswerOption ? bloc.checkAnswerRight(index) : null, + child: Container( + padding: const EdgeInsets.all(4.5), + decoration: BoxDecoration( + color: isAnswerOption + ? getResultColor(answerCorrect) + : Colors.white, + borderRadius: BorderRadius.circular(15), + ), + height: 143.h, + width: 143.w, child: Container( - padding: const EdgeInsets.all(4.5), decoration: BoxDecoration( - color: isAnswerOption - ? getResultColor(answerCorrect) - : Colors.white, - borderRadius: BorderRadius.circular(15), - ), - height: 143.h, - width: 143.w, - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15), - border: Border.all( - width: 1.0, color: const Color(0xFF140C10)), - image: DecorationImage( - fit: BoxFit.fitWidth, - image: NetworkImage(answerLis?.picUrl ?? ''))), - ), + color: Colors.white, + borderRadius: BorderRadius.circular(15), + border: Border.all( + width: 1.0, color: const Color(0xFF140C10)), + image: DecorationImage( + fit: BoxFit.fitWidth, + image: NetworkImage(answerLis?.picUrl ?? ''))), ), ), ), @@ -240,53 +235,49 @@ class _TopicPicturePage extends StatelessWidget { final isAnswerOption = bloc.selectItem == index; final answerCorrect = isAnswerOption && bloc.checkAnswerRight(index) == true; - final answerIncorrect = - isAnswerOption && bloc.checkAnswerRight(index) == false; return Container( padding: EdgeInsets.symmetric(horizontal: 10.w), - child: GestureDetector( + child: OptionWidget( onTap: () => bloc.add(SelectItemEvent(index)), - child: ShakeWidget( - shouldShake: answerIncorrect, - child: Container( - width: 143.w, - height: 143.h, - padding: EdgeInsets.only( - left: 13.w, right: 13.w, top: 13.h, bottom: 13.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15), - border: Border.all( - width: 1.0, color: const Color(0xFF140C10)), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Expanded( - child: Container( - alignment: Alignment.center, - child: Text(answerLis?.word ?? '', - style: TextStyle( - fontSize: 20.sp, - color: const Color(0xFF333333))), - ), - ), - Container( - height: 30.h, - width: double.infinity, - decoration: BoxDecoration( - color: isAnswerOption - ? getResultColor(answerCorrect) - : Colors.white, - borderRadius: BorderRadius.circular(15.r), - border: Border.all( - width: 1.5, color: const Color(0xFF140C10)), - ), + isCorrect: isAnswerOption ? bloc.checkAnswerRight(index) : null, + child: Container( + width: 143.w, + height: 143.h, + padding: EdgeInsets.only( + left: 13.w, right: 13.w, top: 13.h, bottom: 13.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + border: + Border.all(width: 1.0, color: const Color(0xFF140C10)), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: Container( alignment: Alignment.center, - child: Image.asset('choose'.assetPng), - ) - ], - ), + child: Text(answerLis?.word ?? '', + style: TextStyle( + fontSize: 20.sp, + color: const Color(0xFF333333))), + ), + ), + Container( + height: 30.h, + width: double.infinity, + decoration: BoxDecoration( + color: isAnswerOption + ? getResultColor(answerCorrect) + : Colors.white, + borderRadius: BorderRadius.circular(15.r), + border: Border.all( + width: 1.5, color: const Color(0xFF140C10)), + ), + alignment: Alignment.center, + child: Image.asset('choose'.assetPng), + ) + ], ), ), ), @@ -349,32 +340,28 @@ class _TopicPicturePage extends StatelessWidget { final isAnswerOption = bloc.selectItem == index; final answerCorrect = isAnswerOption && bloc.checkAnswerRight(index) == true; - final answerIncorrect = - isAnswerOption && bloc.checkAnswerRight(index) == false; - return ShakeWidget( - shouldShake: answerIncorrect, + return OptionWidget( + onTap: () => bloc.add(SelectItemEvent(index)), + isCorrect: isAnswerOption ? bloc.checkAnswerRight(index) : null, child: Container( padding: EdgeInsets.symmetric(horizontal: 20.w), - child: GestureDetector( - onTap: () => bloc.add(SelectItemEvent(index)), + child: Container( + padding: const EdgeInsets.all(4.5), + decoration: BoxDecoration( + color: isAnswerOption + ? getResultColor(answerCorrect) + : Colors.white, + borderRadius: BorderRadius.circular(15), + ), + height: 143.h, + width: 163.w, child: Container( - padding: const EdgeInsets.all(4.5), decoration: BoxDecoration( - color: isAnswerOption - ? getResultColor(answerCorrect) - : Colors.white, - borderRadius: BorderRadius.circular(15), - ), - height: 143.h, - width: 163.w, - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15), - image: DecorationImage( - fit: BoxFit.fill, - image: NetworkImage(answerList?.picUrl ?? ''))), - ), + color: Colors.white, + borderRadius: BorderRadius.circular(15), + image: DecorationImage( + fit: BoxFit.fill, + image: NetworkImage(answerList?.picUrl ?? ''))), ), ), ), @@ -426,55 +413,51 @@ class _TopicPicturePage extends StatelessWidget { final isAnswerOption = bloc.selectItem == index; final answerCorrect = isAnswerOption && bloc.checkAnswerRight(index) == true; - final answerIncorrect = - isAnswerOption && bloc.checkAnswerRight(index) == false; - return GestureDetector( + return OptionWidget( onTap: () => bloc.add(SelectItemEvent(index)), - child: ShakeWidget( - shouldShake: answerIncorrect, + isCorrect: isAnswerOption ? bloc.checkAnswerRight(index) : null, + child: Container( + width: 163.w, + height: 143.h, + padding: EdgeInsets.symmetric(horizontal: 10.w), child: Container( - width: 163.w, + width: 143.w, height: 143.h, - padding: EdgeInsets.symmetric(horizontal: 10.w), - child: Container( - width: 143.w, - height: 143.h, - padding: EdgeInsets.only( - left: 13.w, right: 13.w, top: 13.h, bottom: 13.h), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15), - border: Border.all( - width: 1.0, color: const Color(0xFF140C10)), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Expanded( - child: Container( - alignment: Alignment.center, - child: Text(answerList.word ?? '', - style: TextStyle( - fontSize: 20.sp, - color: const Color(0xFF333333))), - ), - ), - Container( - height: 30.h, - width: double.infinity, - decoration: BoxDecoration( - color: isAnswerOption - ? getResultColor(answerCorrect) - : Colors.white, - borderRadius: BorderRadius.circular(15.r), - border: Border.all( - width: 1.5, color: const Color(0xFF140C10)), - ), + padding: EdgeInsets.only( + left: 13.w, right: 13.w, top: 13.h, bottom: 13.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + border: + Border.all(width: 1.0, color: const Color(0xFF140C10)), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: Container( alignment: Alignment.center, - child: Image.asset('choose'.assetPng), - ) - ], - ), + child: Text(answerList.word ?? '', + style: TextStyle( + fontSize: 20.sp, + color: const Color(0xFF333333))), + ), + ), + Container( + height: 30.h, + width: double.infinity, + decoration: BoxDecoration( + color: isAnswerOption + ? getResultColor(answerCorrect) + : Colors.white, + borderRadius: BorderRadius.circular(15.r), + border: Border.all( + width: 1.5, color: const Color(0xFF140C10)), + ), + alignment: Alignment.center, + child: Image.asset('choose'.assetPng), + ) + ], ), ), ), -- libgit2 0.22.2