Flutter 实现背景 Parallax 动画
原文 https://arkapp.medium.com/bac...
前言
我们将创建我们的 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});
@override
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});
@override
State createState() => _WidgetState();
}
class _WidgetState extends State {
@override
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});
@override
State createState() => _WidgetState();
}
class _WidgetState extends State {
final childKey = GlobalKey();
late Widget childWithKey;
double? childBaseWidth;
@override
void initState() {
super.initState();
childWithKey = SizedBox(key: childKey, child: widget.child);
fetchChildWidth();
}
@override
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});
@override
State createState() => _WidgetState();
}
class _WidgetState extends State {
///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;
@override
void initState() {
super.initState();
childWithKey = SizedBox(key: childKey, child: widget.child);
fetchChildWidth();
}
@override
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});
@override
State createState() => _WidgetState();
}
class _WidgetState extends State {
///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;
@override
void initState() {
super.initState();
childWithKey = SizedBox(key: childKey, child: widget.child);
fetchChildWidth();
initTimer();
}
@override
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(() {});
}
@override
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);
@override
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://video.ducafecat.tech
本文由mdnice多平台发布