Flutter 實現背景 Parallax 動畫

會煮咖啡的貓發表於2022-11-28

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多平臺釋出

相關文章