优化动画效果

This commit is contained in:
yuanjunyao 2023-11-30 10:16:09 +08:00
parent e24be85f04
commit d950901497
6 changed files with 214 additions and 103 deletions

View File

@ -66,6 +66,7 @@ class FirstTimeDialog extends StatelessWidget {
left: 0,
right: 0,
child: AnimatedVisibilityWidget(
animationWidgetBuilder: AnimatedVisibilityWidget.fadeAnimationWidgetBuilder,
isVisible: isTextShown.value,
child: const Center(
child: TouchHintWidget(),

View File

@ -68,6 +68,15 @@ class SelectController extends GetxController {
occupations.add(Occupation(
id: 4,
gender: 2,
name: '运动员',
enName: 'athletes',
vImage: 'btn_male_pic_athletes',
hImage: 'pic_male_athletes',
selected: false,
));
occupations.add(Occupation(
id: 5,
gender: 2,
name: '画家',
enName: 'artist',
vImage: 'btn_male_pic_artist',
@ -75,16 +84,6 @@ class SelectController extends GetxController {
selected: true,
enable: true,
));
occupations.add(Occupation(
id: 5,
gender: 2,
name: '运动员',
enName: 'athletes',
vImage: 'btn_male_pic_athletes',
hImage: 'pic_male_athletes',
selected: false,
));
occupations.add(Occupation(
id: 6,
gender: 2,

View File

@ -168,6 +168,10 @@ class PartnerWidget extends HookWidget {
return HookBuilder(builder: (context) {
final isDialogShown = useState(false);
final isTextShown = useState(false);
final fadeOutAnimationController = useAnimationController(
initialValue: 1.0,
duration: const Duration(milliseconds: 500),
);
return Stack(
children: [
Positioned.fill(
@ -180,82 +184,94 @@ class PartnerWidget extends HookWidget {
),
),
),
Positioned(top: 42.h, left: 149.w, child: Images.ip),
Positioned(
top: 42.h,
left: 149.w,
child: FadeTransition(
opacity: fadeOutAnimationController,
child: Images.ip,
),
),
Positioned(
top: 76.h,
left: 451.w,
child: AnimatedVisibilityWidget(
isVisible: true,
duration: const Duration(milliseconds: 500),
animationWidgetBuilder:
AnimatedVisibilityWidget.fadeAnimationWidgetBuilder,
isInitAnimated: true,
onDone: (visible) {
if (visible) {
isDialogShown.value = true;
}
},
child: Container(
width: 464.w,
height: 270.h,
decoration: const BoxDecoration(
image: DecorationImage(
image: Images.questionDialog,
fit: BoxFit.fill,
child: FadeTransition(
opacity: fadeOutAnimationController,
child: AnimatedVisibilityWidget(
isVisible: true,
duration: const Duration(milliseconds: 500),
animationWidgetBuilder:
AnimatedVisibilityWidget.fadeAnimationWidgetBuilder,
isInitAnimated: true,
onDone: (visible) {
if (visible) {
isDialogShown.value = true;
}
},
child: Container(
width: 464.w,
height: 270.h,
decoration: const BoxDecoration(
image: DecorationImage(
image: Images.questionDialog,
fit: BoxFit.fill,
),
),
),
padding: REdgeInsets.only(top: 45.0, left: 45.0, right: 45.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: SizedBox(
width: double.infinity,
child: Visibility(
visible: isDialogShown.value,
child: AnimatedTextKit(
totalRepeatCount: 1,
animatedTexts: [
TypewriterAnimatedText(
'每个来到建木之境的探梦者,都需要先选定自己的【梦想职业】,才能继续探索哦。准备好了吗?',
textStyle: TextStyles.mediumWhiteShadow18_034,
textAlign: TextAlign.start,
speed: const Duration(milliseconds: 150),
cursor: '',
),
],
onFinished: () {
isTextShown.value = true;
},
padding: REdgeInsets.only(top: 45.0, left: 45.0, right: 45.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: SizedBox(
width: double.infinity,
child: Visibility(
visible: isDialogShown.value,
child: AnimatedTextKit(
totalRepeatCount: 1,
pause: Duration.zero,
animatedTexts: [
TypewriterAnimatedText(
'每个来到建木之境的探梦者,都需要先选定自己的【梦想职业】,才能继续探索哦。准备好了吗?',
textStyle: TextStyles.mediumWhiteShadow18_034,
textAlign: TextAlign.start,
speed: const Duration(milliseconds: 150),
cursor: '',
),
],
onFinished: () {
isTextShown.value = true;
},
),
),
),
),
),
const RSizedBox(
height: 36.0,
),
AnimatedVisibilityWidget(
isVisible: isTextShown.value,
duration: const Duration(milliseconds: 500),
animationWidgetBuilder:
AnimatedVisibilityWidget.fadeAnimationWidgetBuilder,
child: ImageTxtButton(
text: '准备好了',
imgName: 'question/btn_icon_begin',
textStyle: TextStyles.mediumWhite20_014,
width: 186.0,
height: 54.0,
onPressed: () async {
await controller.confirm();
await Get.offAllNamed(Routes.SELECT);
},
const RSizedBox(
height: 36.0,
),
),
const RSizedBox(
height: 64.0,
),
],
AnimatedVisibilityWidget(
isVisible: isTextShown.value,
duration: const Duration(milliseconds: 500),
animationWidgetBuilder:
AnimatedVisibilityWidget.fadeAnimationWidgetBuilder,
child: ImageTxtButton(
text: '准备好了',
imgName: 'question/btn_icon_begin',
textStyle: TextStyles.mediumWhite20_014,
width: 186.0,
height: 54.0,
onPressed: () async {
await controller.confirm();
await fadeOutAnimationController.reverse();
await Get.offAllNamed(Routes.SELECT);
},
),
),
const RSizedBox(
height: 64.0,
),
],
),
),
),
),

View File

@ -41,6 +41,7 @@ class WelcomeView extends GetView<WelcomeController> {
: AnimatedVisibilityWidget(
isVisible: !finishInput.value,
duration: const Duration(milliseconds: 800),
curve: Curves.fastOutSlowIn,
animationWidgetBuilder: AnimatedVisibilityWidget
.fadeAnimationWidgetBuilder,
onDone: (visible) {

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../utils/image_utils.dart';
@ -17,10 +18,13 @@ class Images {
width: 35.w,
);
static Widget up = LoadAssetImage(
'btn_icon_up',
height: 47.w,
width: 47.w,
static Widget up = _UpAndDown(
child: LoadAssetImage(
'btn_icon_up',
fit: BoxFit.contain,
height: 47.w,
width: 47.w,
),
);
static Widget ip = LoadAssetImage(
@ -43,16 +47,20 @@ class Images {
fit: BoxFit.fill,
);
static Widget splashBegin = LoadAssetImage(
'splash/btn_icon_begin',
height: 105.h,
width: 378.w,
static Widget splashBegin = _FadeInAndOut(
child: LoadAssetImage(
'splash/btn_icon_begin',
height: 105.h,
width: 378.w,
),
);
static Widget welcomeBegin = LoadAssetImage(
'welcome/btn_label_bg_up',
height: 114.h,
width: 270.w,
static Widget welcomeBegin = _FadeInAndOut(
child: LoadAssetImage(
'welcome/btn_label_bg_up',
height: 114.h,
width: 270.w,
),
);
static Widget pullDown = LoadAssetImage(
@ -61,10 +69,12 @@ class Images {
width: 21.w,
);
static Widget pullUp = LoadAssetImage(
'welcome/btn_icon_up',
height: 21.w,
width: 21.w,
static Widget pullUp = _UpAndDown(
child: LoadAssetImage(
'welcome/btn_icon_up',
height: 21.w,
width: 21.w,
),
);
static Widget selectPre = LoadAssetImage(
@ -79,16 +89,20 @@ class Images {
width: 24.w,
);
static Widget questionRecommended = LoadAssetImage(
'question/label_recommended',
height: 105.h,
width: 390.w,
static Widget questionRecommended = _FadeInAndOut(
child: LoadAssetImage(
'question/label_recommended',
height: 105.h,
width: 390.w,
),
);
static Widget homeCreate = LoadAssetImage(
'home/label_label_create',
height: 105.h,
width: 378.w,
static Widget homeCreate = _FadeInAndOut(
child: LoadAssetImage(
'home/label_label_create',
height: 105.h,
width: 378.w,
),
);
static Widget homeReset = LoadAssetImage(
@ -319,3 +333,83 @@ class Images {
static const AssetImage avatarUser =
AssetImage("assets/images/label_avatar_user.png");
}
class _FadeInAndOut extends HookWidget {
const _FadeInAndOut({
super.key,
required this.child,
});
final Widget child;
@override
Widget build(BuildContext context) {
final controller = useAnimationController(
duration: const Duration(milliseconds: 300),
reverseDuration: const Duration(milliseconds: 500),
);
useEffect(() {
void onStateChange(AnimationStatus state) {
if (state == AnimationStatus.completed) {
controller.reverse();
} else if (state == AnimationStatus.dismissed) {
controller.forward();
}
}
controller.addStatusListener(onStateChange);
controller.forward();
return () {
controller.removeStatusListener(onStateChange);
};
}, [controller]);
final opacityTween = Tween(
begin: 0.7,
end: 1.0,
);
return FadeTransition(
opacity: opacityTween.animate(controller),
child: child,
);
}
}
class _UpAndDown extends HookWidget {
const _UpAndDown({
super.key,
required this.child,
});
final Widget child;
@override
Widget build(BuildContext context) {
final controller = useAnimationController(
duration: const Duration(milliseconds: 300),
reverseDuration: const Duration(milliseconds: 500),
);
useEffect(() {
void onStateChange(AnimationStatus state) {
if (state == AnimationStatus.completed) {
controller.reverse();
} else if (state == AnimationStatus.dismissed) {
controller.forward();
}
}
controller.addStatusListener(onStateChange);
controller.forward();
return () {
controller.removeStatusListener(onStateChange);
};
}, [controller]);
final offsetTween = Tween(
begin: const Offset(0, 0.2),
end: const Offset(0, 0),
);
return SlideTransition(
position: offsetTween.animate(controller),
child: child,
);
}
}

View File

@ -182,7 +182,7 @@ class SwipeNextPageHandler extends StatelessWidget {
}
},
onPanCancel: () {
controller.animateTo(controller.lowerBound, curve: Curves.easeIn);
},
onPanUpdate: (details) {
controller.value -= details.delta.dy;