如何在Flutter中创建旗帜动画?

问题描述 投票:0回答:1

我正在寻找一种方法在 Flutter 中做这样的事情:

标志本身可以是任何小部件,而不仅仅是图像。

我有将 sin 函数与有状态 Widget 结合使用的想法,但我基本上有两个问题:

  1. 我不知道如何访问Widget的每个像素。
  2. 如果我确实可以访问每个像素,我不知道如何更改其标高(我知道我可以更改整个 Widget 的标高,但如何仅在 Widget 的一部分中执行此操作?)。

如果我能解决这两点,那么我想我可以尝试根据正弦波函数的输出来提升每个像素(并稍微改变其颜色)并复制上述效果。

有人知道 Flutter 中是否可以实现这样的功能吗?谁能指出我正确的方向来实现这样的效果?

animation flutter pixel flutter-animation
1个回答
0
投票

你好,你可以看看我的例子,它以一个标志作为输入并对其进行挥动效果,还要注意它上面有一个附加的阴影效果图像(重复的阴影从左到右移动)你可以删除如果你不需要它

`

import 'dart:async';
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class FlagWaveAnimation extends StatefulWidget {
  final double width, height, top, left;
  final String imagePath;
  final String shadeImagePath = 'assets/flags/shade.png'; // Ensure this path is correct

  const FlagWaveAnimation({
    Key? key,
    required this.width,
    required this.height,
    required this.top,
    required this.left,
    required this.imagePath,
  }) : super(key: key);

  @override
  _FlagWaveAnimationState createState() => _FlagWaveAnimationState();
}

class _FlagWaveAnimationState extends State<FlagWaveAnimation> with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController controller;
  ui.Image? image;
  ui.Image? shadeImage;

  @override
  void initState() {
    super.initState();
    // Doubling the duration to decrease the speed of the shade animation
    controller = AnimationController(
      duration: const Duration(seconds: 2), // Adjusted from 1 second to 2 seconds
      vsync: this,
    )..repeat();
    animation = Tween<double>(begin: 0, end: 2 * math.pi).animate(controller)
      ..addListener(() {
        setState(() {});
      });

    loadImages();
  }

  Future<void> loadImages() async {
    image = await loadImage(widget.imagePath);
    shadeImage = await loadImage(widget.shadeImagePath);
  }

  Future<ui.Image> loadImage(String path) async {
    final ByteData data = await rootBundle.load(path);
    final ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    final ui.FrameInfo fi = await codec.getNextFrame();
    return fi.image;
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (image == null || shadeImage == null) return Container(); // Handle images not loaded state
    return Positioned(
      top: widget.top,
      left: widget.left,
      child: CustomPaint(
        painter: FlagPainter(image!, shadeImage!, animation.value, widget.width, widget.height),
        size: Size(widget.width, widget.height),
      ),
    );
  }
}

class FlagPainter extends CustomPainter {
  final ui.Image image;
  final ui.Image shadeImage;
  final double wavePhase;
  final double width;
  final double height;

  FlagPainter(this.image, this.shadeImage, this.wavePhase, this.width, this.height);

  @override
  void paint(Canvas canvas, Size size) {
    // Original flag waving effect
    _paintWaveEffect(canvas, size);
    // Shade effect overlay
    _paintShadeEffect(canvas, size);
  }

  void _paintWaveEffect(Canvas canvas, Size size) {
    final Path path = Path();
    final int waveCount = 4;
    final double curvinessLevel = 1.1;
    final double effectiveHeight = size.height * 0.95; // Using 95% of height for the wave effect
    final double yOffset = (size.height - effectiveHeight) / 2;

    path.moveTo(0, size.height);
    for (double x = 0; x <= size.width; x++) {
      double y = size.height - yOffset - math.sin((x / size.width) * waveCount * math.pi + wavePhase + math.pi) * curvinessLevel;
      path.lineTo(x, y);
    }
    path.lineTo(size.width, yOffset);
    for (double x = size.width; x >= 0; x--) {
      double y = yOffset + math.sin((x / size.width) * waveCount * math.pi + wavePhase) * curvinessLevel;
      path.lineTo(x, y);
    }
    path.close();

    canvas.clipPath(path);

    paintImage(
      canvas: canvas,
      rect: Rect.fromLTWH(0, 0, size.width, size.height),
      image: image,
      fit: BoxFit.fill,
    );
  }

  void _paintShadeEffect(Canvas canvas, Size size) {
    // Adjusting the speed of the shade effect
    final double shadeWidth = width / 4;
    final double animationOffset = wavePhase * width / (2 * math.pi); // Convert wavePhase to a linear translation
    for (int i = 0; i < 4; i++) {
      double leftPosition = (i * shadeWidth + animationOffset) % width - shadeWidth; // Ensure looping within bounds
      paintImage(
        canvas: canvas,
        rect: Rect.fromLTWH(leftPosition, 0, shadeWidth, height),
        image: shadeImage,
        fit: BoxFit.fill,
        opacity: 0.5, // Adjust for visual preference
      );
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

`

© www.soinside.com 2019 - 2024. All rights reserved.