英雄动画在 Flutter 中不起作用

Posted

技术标签:

【中文标题】英雄动画在 Flutter 中不起作用【英文标题】:Hero Animation Not Working in Flutter 【发布时间】:2018-11-01 18:31:22 【问题描述】:

所以,我正在尝试为一家餐厅制作送货上门应用程序,但我似乎无法让我的英雄动画正常工作。首先,我制作了一个启动屏幕,其中显示了徽标,然后导航到徽标应该进行英雄转换的主页。初始屏幕和主页位于两个单独的 dart 文件中。这是我的初始屏幕的代码:

import 'package:flutter/material.dart';
import 'home_page.dart';
import 'dart:async';

class Splash extends StatefulWidget 
  @override
  _SplashState createState() => new _SplashState();


class _SplashState extends State<Splash> with SingleTickerProviderStateMixin 


  Animation<double> _mainLogoAnimation;
  AnimationController _mainLogoAnimationController;

  @override
  void initState() 
    super.initState();
    goToHomePage();
    _mainLogoAnimationController = new AnimationController(duration: new Duration(milliseconds: 2500) ,vsync: this);
    _mainLogoAnimation = new CurvedAnimation(parent: 
    _mainLogoAnimationController, curve: Curves.easeIn);
    _mainLogoAnimation.addListener(() => (this.setState(() )));
    _mainLogoAnimationController.forward();
  

  Future goToHomePage() async 
    await new Future.delayed(const Duration(milliseconds: 4000));
    Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context) => new HomePage()));
  

  @override
  Widget build(BuildContext context) 
    return new Material(
      color: Colors.black,
      child: new Center(
        child: new Opacity(
          opacity: 1.0 * _mainLogoAnimation.value,
          child: new Hero(
            tag: 'tbh_logo',
            child: new Image(
              image: new AssetImage('assets/images/tbh_main_logo.png'),
              width: 300.0
            )
          )
        )
      )
    );
  

这是主页的代码:

import 'package:flutter/material.dart';
import '../ui/drawer.dart';
import 'splash.dart';

class HomePage extends StatefulWidget 
  @override
  _HomePageState createState() => new _HomePageState();


class _HomePageState extends State<HomePage> 
  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("The Barni House"),
        backgroundColor: Colors.black,
      ),
      drawer: new Drawer(child: MyDrawer()),
      body: new Center(
        child: Column(
          children: <Widget>[
            new Container(
              child: new Hero(
                tag: 'tbh_logo',
                child: new Image(
                  image: new AssetImage('assets/images/tbh_main_logo.png'),
                  width: 300.0
                )
              )
            )
          ],
        )
      ),
    );
  

【问题讨论】:

【参考方案1】:

Hero 动画在 Flutter 中很容易实现。您只需在登录页面中导入包 package:flutter/scheduler.dart。见下面代码

import 'package:flutter/scheduler.dart' show timeDilation;

记得添加 timeDilation 变量并为其分配一个值,以秒为单位加速或减慢动画。在您的构建方法之后添加这些,如下例所示

Widget build(BuildContext context) 

    timeDilation = 2;

为了更好的动画,只需使用自定义动画

【讨论】:

【参考方案2】:

检查了您的代码,因此英雄动画正在运行,但由于过渡持续时间仅为 300 毫秒,因此它发生得很快。

要实现以下结果,您可以制作 Custom MaterialPageRoute

之前

@override
  Duration get transitionDuration => const Duration(milliseconds: 300);

之后

@override
  Duration get transitionDuration => const Duration(milliseconds: 1000);

此外,您还可以使用 CurvedAnimation

new CurvedAnimation(
                parent: routeAnimation,
                curve: Curves.elasticIn,
              )

CustomRoute.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

final Tween<Offset> _kBottomUpTween = new Tween<Offset>(
  begin: const Offset(0.0, 1.0),
  end: Offset.zero,
);

// Offset from offscreen to the right to fully on screen.
final Tween<Offset> _kRightMiddleTween = new Tween<Offset>(
  begin: const Offset(1.0, 0.0),
  end: Offset.zero,
);

// Offset from offscreen below to fully on screen.
class AppPageRoute extends MaterialPageRoute<String> 
  @override
  final bool maintainState;

  @override
  final WidgetBuilder builder;
  CupertinoPageRoute<String> _internalCupertinoPageRoute;

  AppPageRoute(
    @required this.builder,
    RouteSettings settings: const RouteSettings(),
    this.maintainState: true,
    bool fullscreenDialog: false,
  )  : assert(builder != null),
        assert(settings != null),
        assert(maintainState != null),
        assert(fullscreenDialog != null),
        super(
          settings: settings,
          fullscreenDialog: fullscreenDialog,
          builder: builder,
        ) 
    assert(opaque); // PageRoute makes it return true.
  

  @override
  Color get barrierColor => null;

  @override
  Duration get transitionDuration => const Duration(milliseconds: 1000);

  CupertinoPageRoute<String> get _cupertinoPageRoute 
    assert(_useCupertinoTransitions);
    _internalCupertinoPageRoute ??= new CupertinoPageRoute<String>(
      builder: builder,
      fullscreenDialog: fullscreenDialog,
      hostRoute: this,
    );
    return _internalCupertinoPageRoute;
  

  bool get _useCupertinoTransitions 
    return _internalCupertinoPageRoute?.popGestureInProgress == true ||
        Theme.of(navigator.context).platform == TargetPlatform.ios;
  

  @override
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) 
    final Widget result = builder(context);
    assert(() 
      if (result == null) 
        throw new FlutterError('The builder for route "$settings.name" returned null.\n'
            'Route builders must never return null.');
      
      return true;
    ());
    return result;
  

  @override
  Widget buildTransitions(
      BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) 
    if (_useCupertinoTransitions) 
      return _cupertinoPageRoute.buildTransitions(context, animation, secondaryAnimation, child);
    

    return new _CustomPageTransition(routeAnimation: animation, child: child, fullscreenDialog: fullscreenDialog);
  


class _CustomPageTransition extends StatelessWidget 
  final Animation<Offset> _positionAnimation;

  final Widget child;
  final bool fullscreenDialog;

  _CustomPageTransition(
    Key key,
    @required Animation<double> routeAnimation,
    @required this.child,
    @required this.fullscreenDialog,
  )  : _positionAnimation = !fullscreenDialog
            ? _kRightMiddleTween.animate(new CurvedAnimation(
                parent: routeAnimation,
                curve: Curves.elasticIn,
              ))
            : _kBottomUpTween.animate(new CurvedAnimation(
                parent: routeAnimation, // The route's linear 0.0 - 1.0 animation.
                curve: Curves.elasticIn,
              )),
        super(key: key);

  @override
  Widget build(BuildContext context) 
    return new SlideTransition(
      position: _positionAnimation,
      child: child,
    );
  

推新路线

 Future goToHomePage() async 
    await new Future.delayed(const Duration(milliseconds: 4000));
    Navigator.of(context).push(new AppPageRoute(builder: (BuildContext context) => new HomePage()));
  

您可以将自定义 MaterialPageRoute 用于启动屏幕和其他路由 MaterialPageRoute。

希望对你有帮助

【讨论】:

谢谢!从未真正考虑过制作自定义页面路由。 这对我帮助很大! :)【参考方案3】:
// main.dart class
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:flutter_test7/photo_hero.dart';
import 'package:flutter_test7/second_page.dart';

class HeroAnimation extends StatelessWidget 
  Widget build(BuildContext context) 
    timeDilation = 2.5; // 1.0 means normal animation speed.

    return new Scaffold(
      appBar: new AppBar(
        title: const Text('Basic Hero Animation'),
      ),
      body: new Center(
        child: new PhotoHero(
          photo: 'images/flippers-alpha.png',
          width: 300.0,
          onTap: () 
            Navigator.of(context).pushNamed('/second_page');
          ,
        ),
      ),
    );
  


void main() 
  runApp(
    new MaterialApp(
      home: new HeroAnimation(),
      routes: <String, WidgetBuilder>
        '/second_page': (context) => new SecondPage()
      ,
    ),
  );


// photo_hero.dart class
import 'package:flutter/material.dart';

class PhotoHero extends StatelessWidget 
  const PhotoHero(Key key, this.photo, this.onTap, this.width)
      : super(key: key);

  final String photo;
  final VoidCallback onTap;
  final double width;

  Widget build(BuildContext context) 
    return new SizedBox(
      width: width,
      child: new Hero(
        tag: photo,
        child: new Material(
          color: Colors.transparent,
          child: new InkWell(
            onTap: onTap,
            child: new Image.asset(
              photo,
              fit: BoxFit.contain,
            ),
          ),
        ),
      ),
    );
  


// second_page.dart class
import 'package:flutter/material.dart';
import 'package:flutter_test7/photo_hero.dart';

class SecondPage extends StatefulWidget 
  @override
  _SecondPageState createState() => new _SecondPageState();


class _SecondPageState extends State<SecondPage> 
  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      appBar: new AppBar(
        title: const Text('Flippers Page'),
      ),
      body: new Container(
        color: Colors.lightBlueAccent,
        padding: const EdgeInsets.all(16.0),
        alignment: Alignment.topLeft,
        child: new PhotoHero(
          photo: 'images/flippers-alpha.png',
          width: 100.0,
          onTap: () 
            Navigator.of(context).pop();
          ,
        ),
      ),
    );
  

希望这会有所帮助。

【讨论】:

时间膨胀是一个不错且简单的想法。酷:) 英雄在使用Navigator.push(context, MaterialPageRoute(builder: (context) =&gt; SeondPage()));时有效,但在使用PageRouteBuilder时无效

以上是关于英雄动画在 Flutter 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

方法通道在 Flutter 模块中不起作用

为啥进入动画在新线程中不起作用?

颤振补间基本动画在“FutureBuilder”中不起作用

为啥动画内容在 Firefox 中不起作用?

为啥我的动画在 Firefox 中不起作用?

Flutter Web 在 Firebase 托管中不起作用