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<ParallaxAnimationWidget> createState() => _WidgetState();
}
class _WidgetState extends State<ParallaxAnimationWidget> {
@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<ParallaxAnimationWidget> createState() => _WidgetState();
}
class _WidgetState extends State<ParallaxAnimationWidget> {
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<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;
@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<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;
@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 秒鐘,對我來說是一個激勵,感謝。
祝你有一個美好的一天~
© 貓哥
本文由mdnice多平臺釋出