小部件而非屏幕之间的 Flutter 英雄动画

Flutter hero animation between widgets not screens

英雄动画最适合在屏幕之间导航,但我需要在小部件之间使用相同的动画。例如,就像一张卡片移动到另一个地方:产品卡片移动到购物车和其他东西。谢谢解答!

对于动画小部件,flutter 团队在 youtube 上提供了视频 here

你可以在他们的网站上阅读所有关于他们的信息here

试试这个,add_to_cart_animation:

import 'package:add_to_cart_animation/add_to_cart_animation.dart';
import 'package:add_to_cart_animation/add_to_cart_icon.dart';

import 'package:flutter/material.dart';

import 'list_item.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Add To Cart Animation',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Add To Cart Animation'),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  // We can detech the location of the card by this  GlobalKey<CartIconKey>
  GlobalKey<CartIconKey> gkCart = GlobalKey<CartIconKey>();
  late Function(GlobalKey) runAddToCardAnimation;
  var _cartQuantityItems = 0;

  @override
  Widget build(BuildContext context) {
    return AddToCartAnimation(
      // To send the library the location of the Cart icon
      gkCart: gkCart,
      rotation: true,
      dragToCardCurve: Curves.easeIn,
      dragToCardDuration: const Duration(milliseconds: 1000),
      previewCurve: Curves.linearToEaseOut,
      previewDuration: const Duration(milliseconds: 500),
      previewHeight: 30,
      previewWidth: 30,
      opacity: 0.85,
      initiaJump: false,
      receiveCreateAddToCardAnimationMethod: (addToCardAnimationMethod) {
        // You can run the animation by addToCardAnimationMethod, just pass trough the the global key of  the image as parameter
        this.runAddToCardAnimation = addToCardAnimationMethod;
      },
      child: Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
          centerTitle: false,
          actions: [
            // Improvement/Suggestion 4.4 -> Adding 'clear-cart-button'
            IconButton(
              icon: Icon(Icons.cleaning_services),
              onPressed: () {
                _cartQuantityItems = 0;
                gkCart.currentState!.runClearCartAnimation();
              },
            ),
            SizedBox(width: 16),
            AddToCartIcon(
              key: gkCart,
              icon: Icon(Icons.shopping_cart),
              colorBadge: Colors.red,
            ),
            SizedBox(
              width: 16,
            )
          ],
        ),
        body: ListView(
          children: [
            AppListItem(onClick: listClick, index: 1),
            AppListItem(onClick: listClick, index: 2),
            AppListItem(onClick: listClick, index: 3),
            AppListItem(onClick: listClick, index: 4),
            AppListItem(onClick: listClick, index: 5),
            AppListItem(onClick: listClick, index: 6),
            AppListItem(onClick: listClick, index: 7),
          ],
        ),
      ),
    );
  }

  // Improvement/Suggestion 4.4 -> Running AddTOCartAnimation BEFORE runCArtAnimation
  void listClick(GlobalKey gkImageContainer) async {
    await runAddToCardAnimation(gkImageContainer);
    await gkCart.currentState!.runCartAnimation((++_cartQuantityItems).toString());
  }
}

[非空安全] 这是添加到购物车的示例,add_cart_parabola:

import 'dart:ui';
import 'package:add_cart_parabola/add_cart_parabola.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(

        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  GlobalKey floatKey = GlobalKey();
  GlobalKey rootKey = GlobalKey();
  Offset floatOffset ;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_){
      RenderBox renderBox = floatKey.currentContext.findRenderObject();
      floatOffset = renderBox.localToGlobal(Offset.zero);
    });
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(

        title: Text(widget.title),
      ),
      body: Container(
        key: rootKey,
        width: double.infinity,
        height: double.infinity,
        color: Colors.grey,
        child: ListView(
          children: List.generate(40, (index){
            return generateItem(index);
          }).toList(),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.yellow,
        key: floatKey,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
  Widget generateItem(int index){
    Text text = Text("item $index",style: TextStyle(fontSize:
    25),);

    Offset temp;
    return GestureDetector(
      onPanDown: (details){
        temp = new Offset(details.globalPosition.dx, details.globalPosition
            .dy);
      },
      onTap: (){
        Function callback ;
        setState(() {
          OverlayEntry entry = OverlayEntry(
              builder: (ctx){
                return ParabolaAnimateWidget(rootKey,temp,floatOffset,
                    Icon(Icons.cancel,color: Colors.greenAccent,),callback,);
              }
          );

          callback = (status){
            if(status == AnimationStatus.completed){
              entry?.remove();
            }
          };

          Overlay.of(rootKey.currentContext).insert(entry);
        });
      },
      child: Container(
        color: Colors.orange,
        child: text,
      ),
    );
  }
}

对于同一屏幕中的动画小部件,您可以使用 AnimatedPositioned 小部件,请参见下面的代码

import 'dart:math';

import 'package:flutter/material.dart';

class AnimatedPositionedDemo extends StatefulWidget {
  const AnimatedPositionedDemo({Key? key}) : super(key: key);
  static String routeName = 'animated_positioned';

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

class _AnimatedPositionedDemoState extends State<AnimatedPositionedDemo> {
  late double topPosition;
  late double leftPosition;

  double generateTopPosition(double top) => Random().nextDouble() * top;

  double generateLeftPosition(double left) => Random().nextDouble() * left;

  @override
  void initState() {
    super.initState();
    topPosition = generateTopPosition(30);
    leftPosition = generateLeftPosition(30);
  }

  void changePosition(double top, double left) {
    setState(() {
      topPosition = generateTopPosition(top);
      leftPosition = generateLeftPosition(left);
    });
  }

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    final appBar = AppBar(title: const Text('AnimatedPositioned'));
    final topPadding = MediaQuery.of(context).padding.top;
    // AnimatedPositioned animates changes to a widget's position within a Stack
    return Scaffold(
      appBar: appBar,
      body: SizedBox(
        height: size.height,
        width: size.width,
        child: Stack(
          children: [
            AnimatedPositioned(
              top: topPosition,
              left: leftPosition,
              duration: const Duration(seconds: 1),
              child: InkWell(
                onTap: () => changePosition(
                    size.height -
                        (appBar.preferredSize.height + topPadding + 50),
                    size.width - 150),
                child: Container(
                  alignment: Alignment.center,
                  width: 150,
                  height: 50,
                  child: Text(
                    'Click Me',
                    style: TextStyle(
                      color:
                          Theme.of(context).buttonTheme.colorScheme!.onPrimary,
                    ),
                  ),
                  color: Theme.of(context).primaryColor,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

希望对你有用