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多平台发布


独立开发者_猫哥
666 声望126 粉丝