Flutter 实现背景 Parallax 动画
原文 https://arkapp.medium.com/background-parallax-animation-in-flutter-4aa9e23d6cfb
前言
我们将创建我们的 Flutter 项目惊人的 Parallax 动画。
在本文中,我们将实现一个简单的实用工具 widget ,它将在任何 widget 之上添加 Parallax 效果。
正文
创建 Base Widget
让我们创建我们的基础 widget ,我们将添加 Parallax 动画。在 BaseWidget 中,我们将从 Asset 目录添加一个图像。稍后,我们将添加 Parallax 效果到这个图像。
import 'package:flutter/material.dart';
class BaseWidget extends StatelessWidget {
const BaseWidget({super.key});
Widget build(BuildContext context) {
///We will add parallax effect to this image
return Image.asset(
'assets/moon.webp',
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
);
}
}
创建 Parallax widget
现在我们将创建一个实用工具 widget ,它将为上面的 BaseWidget 添加 Parallax 效果。这将是采用子窗口 widget 作为构造函数参数的状态窗口 widget 。
import 'package:flutter/material.dart';
class ParallaxAnimationWidget extends StatefulWidget {
final Widget child;
const ParallaxAnimationWidget({required this.child, super.key});
State<ParallaxAnimationWidget> createState() => _WidgetState();
}
class _WidgetState extends State<ParallaxAnimationWidget> {
Widget build(BuildContext context) {
return Container();
}
}
现在我们将增加子窗口 widget 的宽度。为此,我们将首先确定子 widget 的宽度。我们将把 GlobalKeyto 添加到子 widget 中,使用该键将获取 widget 的宽度。
import 'package:flutter/material.dart';
class ParallaxAnimationWidget extends StatefulWidget {
final Widget child;
const ParallaxAnimationWidget({required this.child, super.key});
State<ParallaxAnimationWidget> createState() => _WidgetState();
}
class _WidgetState extends State<ParallaxAnimationWidget> {
final childKey = GlobalKey();
late Widget childWithKey;
double? childBaseWidth;
void initState() {
super.initState();
childWithKey = SizedBox(key: childKey, child: widget.child);
fetchChildWidth();
}
Widget build(BuildContext context) {
return Stack(
children: [
childWithKey,
],
);
}
fetchChildWidth() {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
final RenderBox renderBoxRed =
childKey.currentContext?.findRenderObject() as RenderBox;
final childSize = renderBoxRed.size;
childBaseWidth = childSize.width;
},
);
}
}
这里我们添加了 StackWidget,然后在其中添加了子 Widget。现在,我们将增加我们的子窗口 widget 的宽度,以实现横跨该宽度的 Parallax 效果。
import 'package:flutter/material.dart';
class ParallaxAnimationWidget extends StatefulWidget {
final Widget child;
const ParallaxAnimationWidget({required this.child, super.key});
State<ParallaxAnimationWidget> createState() => _WidgetState();
}
class _WidgetState extends State<ParallaxAnimationWidget> {
///We are increasing the widget width by 20% you can change according
///to your needs.
final parallaxWidthPercent = 20;
final childKey = GlobalKey();
late Widget childWithKey;
double? childBaseWidth;
double? totalAdditionalParallaxWidth;
double? rightPosition;
double maxEndPosition = 0;
double maxStartPosition = 0;
void initState() {
super.initState();
childWithKey = SizedBox(key: childKey, child: widget.child);
fetchChildWidth();
}
Widget build(BuildContext context) {
return Stack(
children: [
///Validating the width and setting new increased width.
SizedBox(
width:
(childBaseWidth != null && totalAdditionalParallaxWidth != null)
? (childBaseWidth! + totalAdditionalParallaxWidth!)
: null,
child: childWithKey,
),
],
);
}
fetchChildWidth() {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
final RenderBox renderBoxRed =
childKey.currentContext?.findRenderObject() as RenderBox;
final childSize = renderBoxRed.size;
childBaseWidth = childSize.width;
initChildPosition();
},
);
}
initChildPosition() {
totalAdditionalParallaxWidth = childBaseWidth! * parallaxWidthPercent / 100;
rightPosition = -totalAdditionalParallaxWidth! / 2;
maxEndPosition = -childBaseWidth! + totalAdditionalParallaxWidth!;
maxStartPosition = totalAdditionalParallaxWidth!;
setState(() {});
}
}
我们已经增加了 20% 的宽度部件,您可以根据您的需要改变。我们还使用新的宽度计算了动画位置参数(right position、 maxEndposition、 maxStartposition)。这个新参数将在下一步中用于添加动画。
添加动画
我们将使用 Animatedposition 来创建美丽的 Parallax 动画。我们将创建一个定时器,它将不断改变我们的子窗口 widget 的位置,以创建 Parallax 效果。
import 'dart:async';
import 'package:flutter/material.dart';
class ParallaxAnimationWidget extends StatefulWidget {
final Widget child;
const ParallaxAnimationWidget({required this.child, super.key});
State<ParallaxAnimationWidget> createState() => _WidgetState();
}
class _WidgetState extends State<ParallaxAnimationWidget> {
///You can change the duration accoridng to your needs
Duration animationDuration = const Duration(seconds: 15);
final initialDelay = Future.delayed(const Duration(seconds: 1));
Timer? animationTimer;
final parallaxWidthPercent = 20;
final childKey = GlobalKey();
late Widget childWithKey;
double? childBaseWidth;
double? totalAdditionalParallaxWidth;
double? rightPosition;
double maxEndPosition = 0;
double maxStartPosition = 0;
void initState() {
super.initState();
childWithKey = SizedBox(key: childKey, child: widget.child);
fetchChildWidth();
initTimer();
}
Widget build(BuildContext context) {
return Stack(
children: [
///This will animate our widget between edge positions.
AnimatedPositioned(
right: rightPosition,
duration: animationDuration,
child: SizedBox(
width:
(childBaseWidth != null && totalAdditionalParallaxWidth != null)
? (childBaseWidth! + totalAdditionalParallaxWidth!)
: null,
child: childWithKey,
),
),
],
);
}
initTimer() async {
animationTimer?.cancel();
await initialDelay;
updateChildPosition();
animationTimer = Timer.periodic(
animationDuration,
(_) => updateChildPosition(),
);
}
///This method will animate our widget horizontally
updateChildPosition() async {
if (rightPosition == 0) {
rightPosition = maxEndPosition;
} else if (rightPosition == maxEndPosition) {
rightPosition = maxStartPosition;
} else if (rightPosition == maxStartPosition) {
rightPosition = maxEndPosition;
} else {
rightPosition = maxEndPosition;
}
setState(() {});
}
fetchChildWidth() {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
final RenderBox renderBoxRed =
childKey.currentContext?.findRenderObject() as RenderBox;
final childSize = renderBoxRed.size;
childBaseWidth = childSize.width;
initChildPosition();
},
);
}
initChildPosition() {
totalAdditionalParallaxWidth = childBaseWidth! * parallaxWidthPercent / 100;
rightPosition = -totalAdditionalParallaxWidth! / 2;
maxEndPosition = -childBaseWidth! + totalAdditionalParallaxWidth!;
maxStartPosition = totalAdditionalParallaxWidth!;
setState(() {});
}
void dispose() {
super.dispose();
///Closing timer on widget dispose.
animationTimer?.cancel();
}
}
使用 Parallax 动画
我们已经实现了 ParallaxAnimationWidget,现在我们只需要将它添加到 BaseWidget 中就可以看到它的神奇之处了。
import 'package:parallax/base_widget.dart';
import 'package:parallax/parallax_animation_widget.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return const MaterialApp(
///Addding animation to our base widget
home: ParallaxAnimationWidget(
child: BaseWidget(),
),
);
}
}
就是这样!您已经成功地添加 Parallax 动画到您的 Flutter 项目。您可以在项目中的任何地方使用此 widget 来创建令人惊叹的 UI。
结束语
如果本文对你有帮助,请转发让更多的朋友阅读。
也许这个操作只要你 3 秒钟,对我来说是一个激励,感谢。
祝你有一个美好的一天~
© 猫哥
微信 ducafecat
https://wiki.ducafecat.tech
https://ducafecat.com