精华内容
下载资源
问答
  • 用于在同一屏幕中的两个小部件之间创建类似Hero动画的小部件
  • 【Flutter】Hero 动画 ( Hero 实现径向动画 | Hero 组件 createRectTween 设置 ) https://hanshuliang.blog.csdn.net/article/details/115309530 博客源码快照
  • 小部件flutter_sidekick用于在同一屏幕上的两个小部件之间创建类似Hero动画的小部件。 具有类似英雄的动画。 允许您为每个Sidekick指定不同的动画。 用于管理两个多子级小部件的子级之间的动画的小部件。 入门在...
  • Flutter动画 4 - Hero动画

    2021-04-22 14:51:37
    概述 花瓣App的转场动画,这么多年还是没变,还是图片转场动画. 网上有很多人实现过相关效果,先前骚栋18年在做iOS项目的时候,也实现的类似的效果,但是全程实现起来还是比较麻烦,...,是两个路由需要实现Hero动画组件..


    概述


    花瓣App的转场动画,这么多年还是没变,还是图片转场动画.

    网上有很多人实现过相关效果,先前骚栋18年在做iOS项目的时候,也实现的类似的效果,但是全程实现起来还是比较麻烦,需要自己来定义转场动画.在Flutter中也提供了相关效果的组件,那就是 Hero . 接下来我们就来看一下Hero组件实现这种转场动画效果,整体来说还是比较简单的.


    Hero动画


    首先看一下Hero的构建方法.

    其中有两个参数是必须要传入的,一个是final Object tag;,是两个路由需要实现Hero动画组件的唯一标识.另外一个就是final Widget child;,是需要执行动画的具体视图组件,可以是个头像,图片,文字等等都是可以的.

      const Hero({
        Key key,
        @required this.tag,
        this.createRectTween,
        this.flightShuttleBuilder,
        this.placeholderBuilder,
        this.transitionOnUserGestures = false,
        @required this.child,
      }) : assert(tag != null),
           assert(transitionOnUserGestures != null),
           assert(child != null),
           super(key: key);
    

    接下来我们就以一个普通Container组件为例,看一下具有应该怎么实现Hero动画.

    首先,我们先创建一个StatelessWidget - FlutterHeroAnimationFirstPage,当做起始路由.在build构建方法中,我们创建一个Hero组件和一个用于点击跳转的Button组件.Hero组件定义Tag值为HeroAnimationTag.代码如下所示,

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Hero(
                  tag: "HeroAnimationTag",
                  child: Container(
                    width: 100,
                    height: 100,
                    margin: EdgeInsets.only(bottom: 10),
                    color: Colors.redAccent,
                  ),
                ),
                FlatButton(
                  onPressed: (){_startHeroAnimation(context);},
                  child: Text(
                    "点击执行Hero动画",
                    style: TextStyle(color: Colors.black38),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    

    对于路由调转方案,虽然使用其他路由形式也是可以的,但是这里我推荐使用 PageRouteBuilder + FadeTransition 两者配合使用,具体路由跳转方法_startHeroAnimation代码如下所示.

      void _startHeroAnimation(BuildContext context) {
        Navigator.push(context, PageRouteBuilder(
            pageBuilder: (BuildContext context, Animation animation,
                Animation secondaryAnimation) {
              return new FadeTransition(
                opacity: animation,
                child: FlutterHeroAnimationSecondPage(),
              );
            })
        );
      }
    

    然后第二路由中的Widget就比较简单了. 在build构建方法中, 依然需要创建一个Hero组件,并且Tag值要与先前的保持一致.当然了,这里我也创建了一个Button,用于返回上一个路由.具体代码如下所示.

    class FlutterHeroAnimationSecondPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                Hero(
                  tag: "HeroAnimationTag",
                  child: Container(
                    height: 100,
                    margin: EdgeInsets.only(bottom: 10, left: 0, right: 0),
                    color: Colors.redAccent,
                  ),
                ),
                FlatButton(
                  onPressed: () {Navigator.pop(context);},
                  child: Text(
                    "点击返回",
                    style: TextStyle(color: Colors.black38),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    Hero动画实现效果如下所示.


    整体示例代码


    整体代码实现比较简单,这里就不传Github了,直接贴出来了.具体如下所示.

    
    class FlutterHeroAnimationFirstPage extends StatelessWidget {
    
      void _startHeroAnimation(BuildContext context) {
        Navigator.push(context, PageRouteBuilder(
            pageBuilder: (BuildContext context, Animation animation,
                Animation secondaryAnimation) {
              return new FadeTransition(
                opacity: animation,
                child: FlutterHeroAnimationSecondPage(),
              );
            })
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Hero(
                  tag: "HeroAnimationTag",
                  child: Container(
                    width: 100,
                    height: 100,
                    margin: EdgeInsets.only(bottom: 10),
                    color: Colors.redAccent,
                  ),
                ),
                FlatButton(
                  onPressed: (){_startHeroAnimation(context);},
                  child: Text(
                    "点击执行Hero动画",
                    style: TextStyle(color: Colors.black38),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class FlutterHeroAnimationSecondPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                Hero(
                  tag: "HeroAnimationTag",
                  child: Container(
                    height: 100,
                    margin: EdgeInsets.only(bottom: 10, left: 0, right: 0),
                    color: Colors.redAccent,
                  ),
                ),
                FlatButton(
                  onPressed: () {Navigator.pop(context);},
                  child: Text(
                    "点击返回",
                    style: TextStyle(color: Colors.black38),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    结语


    Hero动画实现过程比较简单,所有的工作都是由Flutter内部帮我们完成的,Flutter Framework知道新旧路由页中共享元素的位置和大小,所以根据这两个端点,在动画执行过程中求出过渡时的插值(中间态)即可.

    欢迎持续关注骚栋,有任何问题欢迎联系骚栋.


    展开全文
  • Flutter Hero动画(2.5)

    2020-11-14 18:03:15
    今天给大家介绍的是Flutter中独有的动画,Hero动画,非常的炫酷,接下来就步入正题吧~Hero动画介绍 Hero动画介绍 Hero指的是可以在路由(页面)之间“飞行”的widget,简单来说Hero动画就是在路由切换时,有一个共享的...

    今天给大家介绍的是Flutter中独有的动画,Hero动画,非常的炫酷,接下来就步入正题吧~

    Hero动画介绍

    Hero指的是可以在路由(页面)之间“飞行”的widget,简单来说Hero动画就是在路由切换时,有一个共享的widget可以在新旧路由间切换。由于共享的widget在新旧路由页面上的位置、外观可能有所差异,所以在路由切换时会从旧路逐渐过渡到新路由中的指定位置,这样就会产生一个Hero动画。

    Flutter中文网参考

    先来看看要完成的效果吧:

    效果图(1.1):

    在这里插入图片描述

    分析:

    通过效果图可以看出,点击图片或者文字,完成了图片的由小到大的动画效果,并且完成了页面的切换,以及文字布局在不同位置的展示, 水波纹点击效果

    好了,咋们先不考虑Hero动画如何使用,先把2个页面的布局,图片,文字给写出来,以及水波纹效果

    class HerpPracticePage extends StatefulWidget {
      @override
      _HerpPracticePageState createState() => _HerpPracticePageState();
    }
    
    class _HerpPracticePageState extends State<HerpPracticePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Hero动画练习"),
          ),
          body: Container(
            color: Colors.transparent,
            //左右排列布局
            child: Material(
           	   //水波纹效果
                child: InkWell(
              onTap: () {
              },
              child: Row(
                //主轴方向开始对齐 在这里是左对齐
                mainAxisAlignment: MainAxisAlignment.start,
                //交叉轴上开始对齐 在这里是顶部对齐
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  //左侧图片
                  initLeftIcon(),
                  //右侧文字
                  initRightTxt(),
                ],
              ),
            )),
          ),
        );
      }
    
      /**
       * 左侧图片
       */
      Widget initLeftIcon() {
        return Container(
            child: Image.network(
              "https://raw.githubusercontent.com/flutter/website/master/examples/_animation/hero_animation/images/flippers-alpha.png",
              width: 150,
              height: 80,
            ),
        );
      }
    
      /**
       * 右侧文字
       */
      Widget initRightTxt() {
        //建议使用Expanded包裹,这样防止上下布局Text文本溢出导致错误!
        return Expanded(
            child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text("今天礼拜六"),
            Text("我在公司认真的学习flutter,正在学习Hero动画,有点难,不过我会克服的!!"),
          ],
        ));
      }
    
    }
    

    这段代码十分简单,先通过Row()组件完成布局的左右(水平)排列,

    将图片和文字区分开,

    然后在在文字区域使用Cloumn()组件完成布局的上下(垂直)排列,

    将文字上下排列开

    然后在使用InkWell设置水波纹效果

    这里有2处地方需要注意:

    • 使用Inkwell一定要写OnTap(){}单击事件属性
    • 在使用Column()组件时建议用Expanded()包裹一下,若Column()组件屏幕溢出,则会报错,Expanded()会展开,常用于占满父容器垂直高度,同样在使用Row()组件的时候也可以使用

    跳转到的页面:

    class JumpPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("这是跳转页面"),
          ),
          body: initJumpPageBody(context),
        );
      }
    
      /**
       * JumpPage的Body布局
       */
      initJumpPageBody(BuildContext context) {
        return Container(
          child: Column(
            children: [
              //图片
              initJumpIcon(context),
              //文本
              initJumpTxt(context),
            ],
          ),
        );
      }
    
      /**
       * 初始化Jump图片布局
       */
      initJumpIcon(BuildContext context) {
        return Material(
          child: InkWell(
              onTap: () {
              //返回上一页面
              Navigator.of(context).pop();
              },
                child: Image.network(
    			"https://raw.githubusercontent.com/flutter/website/master/examples/_animation/hero_animation/images/flippers-alpha.png",
                  width: 300,
                  height: 160,
                ),
             ),
        );
      }
    
    /*
    	文字布局
    */
      initJumpTxt(BuildContext context) {
        return Container(
          padding: EdgeInsets.all(20),
          child: Text(
            "我在公司认真的学习flutter,正在学习Hero动画,有点难,不过我会克服的!!",
          ),
        );
      }
    }
    
    

    这段代码更不需要过多解释,就是一张图片,和一段文字

    接下来就到了关键的时候,两个界面都初始化完成了,之后使用Hero()组件进行包裹

    我们需要进行的是2个图片之间的’飞’,所以只需要对图片使用Hero()组件即可

    第一个页面图片添加Hero()组件,并添加Tag

     /**
       * 左侧图片
       */
      Widget initLeftIcon() {
        return Container(
          child: Hero(
            tag: "HeroPractice",
            child: Image.network(
              "https://raw.githubusercontent.com/flutter/website/master/examples/_animation/hero_animation/images/flippers-alpha.png",
              width: 150,
              height: 80,
            ),
          ),
        );
      }
    

    跳转到的页面图片:

     /**
       * 初始化Jump图片布局
       */
      initJumpIcon(BuildContext context) {
        return InkWell(
          onTap: () {
            Navigator.of(context).pop();
          },
          child: Hero(
            tag: "HeroPractice",
            child: Image.network(
              "https://raw.githubusercontent.com/flutter/website/master/examples/_animation/hero_animation/images/flippers-alpha.png",
              width: 300,
              height: 160,
            ),
          ),
        );
      }
    

    这里需要注意的是:

    • Hero()中tag参数是2个页面之间的唯一标识,不能重复且唯一的!!!
    • 我看有些博客写Hero()中子组件必须用Material()包裹,可能版本不一样,我使用的过程中不包裹也可以.如若报错,加上试试吧~
    • 在使用Hero()的时候,一定不能包裹太多的子组件,否则的话就会有这种效果,请看效果图(1.2)

    效果图(1.2):
    在这里插入图片描述
    从效果图(1.2)中可以看出,点击跳转时,图片背景会有一层白色的状态,然而返回的时候并没有,这就是Hero()包裹太多子组件导致的,别问我怎么知道的,我心里苦o(╥﹏╥)o…(下一章这里有妙用!!!)

    现在已经完成了70%,现在只需要完成在2秒内跳转中的动画,由不透明==>>透明即可大功告成!
    这里只需要在跳转页面时使用PageRouteBuilder自定义路由即可

    
     	 onTap: () {
                //跳转页面
                Navigator.of(context).push(initPageRouteBuilder());
              },
      /**
       * 初始化跳转页面参数
       */
      Route<Object> initPageRouteBuilder() {
        return PageRouteBuilder(
          pageBuilder: (BuildContext context, Animation<double> animation,
              Animation<double> secondaryAnimation) {
            //设置跳转到的页面
            return JumpPage();
          },
          //打开新页面的时间
          transitionDuration: Duration(seconds: 2),
          //关闭页面的时间
          reverseTransitionDuration: Duration(seconds: 2),
          //跳转页面的动画
          transitionsBuilder: (BuildContext context, Animation<double> animation,
              Animation<double> secondaryAnimation, Widget child) {
              //跳转页面动画
    
    > 这里是引用
    
            return initFadeTransition(animation, child);
          },
        );
      }
    

    PageRouteBuilder自定义路由,加粗是必加参数

    PageRouteBuilder参数类型说明
    pageBuilderRoutePageBuilder一般用来返回跳转页面
    transitionDurationDuration用来设置打开新页面的时间
    reverseTransitionDurationDuration关闭页面的时间
    transitionsBuilderRoutePageBuilder用来设置动画

    跳转页面动画:

      /**
       * 透明动画
       */
      Widget initFadeTransition(Animation<double> animation, Widget child) {
        return FadeTransition(
          opacity: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
            parent: animation,
            curve: Curves.linear,
          )),
          child: child,
        );
      }
    

    这段代码在Flutter AnimatedWidget,AnimatedBuilder动画(2.4)讲过类似的,当时是获取的帧,现在只是透明度变化,没有什么区别,这里就不在重复说啦

    完整代码

    上一章:Flutter AnimatedWidget,AnimatedBuilder动画(2.4)

    下一章:Flutter Hero动画(2.6)

    原创不易,您的点赞就是对我最大的支持,留下您的点赞吧~

    展开全文
  • Flutter(十六)——Hero动画

    万次阅读 2020-02-06 17:42:20
    本文目录前言基本用法实现原理 前言 在前面实践组件的开发中,...做过Java开发Android的程序员应该都清楚,Shared Element Transition可以让Activity或Fragment做出流畅的动画,同样,在Flutter开发中,Hero动画...

    前言

    在前面实践组件的开发中,我们做了一个登录的界面,里面有一个组件Hero,不知道大家是否记得?当时没有展开来说,是因为它属于动画的内容,本文就要重点讲解Hero动画。
    在这里插入图片描述
    做过Java开发Android的程序员应该都清楚,Shared Element Transition可以让Activity或Fragment做出流畅的动画,同样,在Flutter开发中,Hero动画也能实现类似的效果。简单来说,Hero的作用就是在路由之间做出流畅的转场动画。

    基本用法

    Hero组件的用法是需要同时定义源组件和目标组件,其中源组件和目标组件被Hero包裹在需要动画控制的组件外面,如果有一方不指定,在有些情况下,界面就会卡死,我们先来看看它的基本用法,首先是main.dart代码:

    class _MyHomePageState extends State<MyHomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("我是第一个界面"),
          ),
          body: Center(
            child: GestureDetector(
              child: Hero(
                tag: "tag1",
                child: FlutterLogo(
                  size: 200,
                ),
              ),
              onTap: () {
                Navigator.push(context, MaterialPageRoute(builder: (BuildContext context)=>CustomFlutterLogoPage()));
              },
            ),
          ),
        );
      }
    }
    

    代码很简单,就是监听点击事件ontap,hero包裹FlutterLogo组件,然后点击跳转到第二个界面。接着我们再来看看第二个页面CustomFlutterLogo.dart的代码:

    import 'package:flutter/material.dart';
    
    class CustomFlutterLogoPage extends StatefulWidget {
      CustomFlutterLogoPage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _CustomFlutterLogoState createState() => _CustomFlutterLogoState();
    }
    
    class _CustomFlutterLogoState extends State<CustomFlutterLogoPage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("我是第二个页面"),),
          body: Center(
            child: Hero(
              tag: "tag2",
              child: CustomFlutterLogo(
                size: 400,
                name: "我是第二个页面",
              ),
            ),
          ),
        );
      }
    
    }
    
    class CustomFlutterLogo extends StatelessWidget{
    
      final double size;
      final String name;
    
      CustomFlutterLogo({this.size=200.0,this.name});
    
      @override
      Widget build(BuildContext context) {
        return Container(
          child: Center(
            child: FlutterLogo(
              size: this.size,
            ),
          ),
        );
      }
    
    }
    

    这段代码也很简单,就是常用的组件,只是在外层套了一层Hero动画组件,不过这里有一点我们需要注意,hero里面有一个tag属性,必须写上,不然会报错,不信的读者,可以删除后运行试试。

    实现原理

    我们基本已经掌握了Hero路由跳转动画的用法,但我们不能只看表面,不明其原理,因为后面讲解的动画也会涉及到这些知识,所以我们必须掌握。

    Hero动画,它的整个运动过程分为3个步骤,即动画开始(t=0.0),动画进行中,动画结束(t=1.0),下面是Hero动画运动示意图:
    示意图如上图所示,两个路由之间还有一个Overlay层。在动画开始时,Flutter会计算出Hero的位置并复制一份,然后绘制到Overlay层上。复制的Hero和源Hero的大小是一致的,并且该Hero是在所有路由之上。在动画实现的过程中,Flutter会逐渐把源Hero移除屏幕。在动画进行中Flutter是依靠Tween来实现,通过createRectTween属性把Tween传给Hero。Hero内部默认使用MeterialRectArcTween的曲线路径进行移动动画的操作。在动画结束时,Flutter将Overlay中的Hero移除,且完成了Hero在目标路由上的显示,这时Overlay是空白的。

    Hero中所有变换都是通过HeroController来实现的,HeroController是在MeterialApp中通过initState和didUpdateWidget方法来完成初始化的,源码如下所示:

    class _MaterialAppState extends State<MaterialApp>{
    	HeroController heroController;
    	@override
    	void initState(){
    		super.initState();
    		_heroController=HeroController(createRectTween:_createRectTween);
    		_updateNavigator();
    	}
    	
    	@override
    	void didUpdateWidget(MaterialApp oldWidget){
    		super.didUpdateWidget(oldWidget);
    		if(widget.navigatorKey!=oldWidget.navigatorKey){
    			_heroController=HeroController(createRectTween:_createRectTween);
    		}
    		_updateNavigator();
    	}
    	RectTween _createRectTween(Rect begin,Rect end){
    		return MaterialRectArcTween(begin:begin,end:end);
    	}
    }
    

    在初始化HeroController时,Flutter携带了一个参数,就是_createRectTween,该参数返回的默认项就是MaterialRectArcTween。Flutter源码里还为我们实现了第二种RectTween返回值,即MaterialRectCenterArcTween。由此可见,可以对createRectTween进行自定义。我们再看看HeroController的具体内容,代码如下:

    @override
    void didPush(Route<dynamic> route,Route<dynamic> previousRoute){
    	assert(navigator!=null);
    	assert(route!=null);
    	_maybeStartHeroTransition(previousRoute,route,HeroFlightDirection.push,false);
    }
    
    @override
    void didPop(Route<dynamic> route,Route<dynamic> previousRoute){
    	assert(navigator!=null);
    	assert(route!=null);
    	_maybeStartHeroTransition(route,previousRoute,HeroFlightDirection.pop,false);
    }
    

    HeroController其实继承的是NavigatorObserver。在路由操作的didPush和didPop回调方法里,可以调用_maybeStartHeroTransition,并通过WidgetsBinding把源路由,目标路由,HeroController关联起来。在使用didPush和didPop回调时,通过调用_startHeroTransition方法让Hero动起来,只不过前者是正向的,后者是逆向的。

    展开全文
  • ◯、Hero 构造函数、 一、圆形方形组件、 二、创建页面 1 的组件 ( Hero 组件 1 )、 三、创建页面 2 的组件 ( Hero 组件 2 )、 四、完整代码示例、 五、相关资源、





    ◯、Hero 构造函数



    Hero 构造函数 :

      /// 创建一个 Hero 组件 ;
      ///
      /// tag , child 参数不能为空 ; 
      /// child 参数的值不能是 Hero 组件以及 Hero 组件子类 ;
      const Hero({
        Key? key,
        required this.tag,
        this.createRectTween,
        this.flightShuttleBuilder,
        this.placeholderBuilder,
        this.transitionOnUserGestures = false,
        required this.child,
      }) : assert(tag != null),
           assert(transitionOnUserGestures != null),
           assert(child != null),
           super(key: key);
    

    required this.tag : 不能为空 , 用于 关联两个界面的 Hero 组件 , 两个 Hero 组件有关联关系 , 则设置相同的 tag 字符串 ;

    this.createRectTween : 可以为空 , 用于 定义 Hero 组件的边界 , 以及定义 Hero 组件在界面切换时 , 从 源界面的起始位置目的界面的最终位置 , 动画执行的变化过程 ;

    required this.child : 不能为空 , 普通的 Widget 组件 , Hero 动画作用的组件 ;


    Hero 动画可以实现径向动画 , 径向动画指的是组件形状可变的动画 , 如圆形变方形 , 方形变三角形 ;

    Hero 径向动画 与 普通动画的区别就是是否设置了 createRectTween 参数 ;





    一、圆形方形组件



    圆形方形变化的组件 : 该组件可以根据不同的参数实现圆形到方形的变化 , 或方形到圆形的变化 ;

    /// Hero 组件 , 径向动画扩展
    /// 该组件主要用于裁剪组件用的
    class OvalRectWidget extends StatelessWidget {
    
      /// 这里的裁剪大小 clipRectSize 最大半径 / 2 的开方值 再乘以 2
      const OvalRectWidget({Key key, this.maxRadius, this.child})
          : clipRectSize = 2.0 * (maxRadius / math.sqrt2),
            super(key: key);
    
      // 最大半径值
      final double maxRadius;
    
      /// 该值需要动态计算
      final clipRectSize;
      final Widget child;
    
      /// 这里特别注意该圆形裁剪组件
      /// 如果整个组件的宽高都是 maxRadius ,
      /// 内部的方形组件宽高是 2.0 * (maxRadius / math.sqrt2)
      /// 并且该方形组件居中显示
      /// 那么该方形组件的四个顶点正好处于圆形组件的裁剪半径位置
      /// 也就是方形组件完整显示 , 没有裁剪到
      @override
      Widget build(BuildContext context) {
        /// 布局裁剪组件 , 可以将布局裁剪成圆形
        return ClipOval(
    
          /// 可用于约束布局大小的组件
          /// 这里的居中显示是关键 , 如果不居中显示 , 最终还是圆形
          child: Center(
            child: SizedBox(
              width: clipRectSize,
              height: clipRectSize,
    
              /// 用于裁剪圆角矩形的组件
              child: ClipRect(
                child: child,
              ),
            ),
          ),
        );
      }
    }
    

    组件形状显示分析 :

    ① 方形裁剪组件 : ClipOval 组件区域是 红色 矩形所在位置 , 其裁剪区域是蓝色组件位置 , 如果正好有个方形的组件 ClipRect 处于下面橙色区域内 , 那么该方形组件正好躲过了被外围红色区域 ClipOval 裁剪的操作 ; 显示的仍然是方形的组件 ;

    在这里插入图片描述

    ② 圆形裁剪组件 : 如果 ClipOval 圆形裁剪组件 ( 红色 ) 与 ClipRect 方形的裁剪组件 ( 橙色 ) 位置重叠 , 那么该方形的裁剪组件肯定就被裁剪成圆形的了 ;

    在这里插入图片描述

    上面两个组件就是 Hero 径向动画的主要作用组件 , 该动画执行前 , 组件是圆形的 , 执行后组件是方形的 , 这就是改变了外层的 ClipOval 组件的大小 , 导致形状改变 ;





    二、创建页面 1 的组件 ( Hero 组件 1 )



    页面 1 的 Hero 组件显示的圆形的 , 跳转到页面 2 后 , 相同 tag 的 Hero 组件显示方形 ;

    控制 OvalRectWidget 是圆形还是方形 , 主要是控制 OvalRectWidget 组件的宽高 , 这里设置的宽高设置 , 相当于上面的 " ② 圆形裁剪组件 " 情况 , 整个组件被裁剪成圆形的组件 ;

    创建页面 1 的组件 :

      /// 创建在界面 1 显示的图标 , 点击后跳转到界面 2
      /// 页面的核心组件是 Hero 组件 , 而且是 3 个
      Widget _buildFirstPagWidget(
          BuildContext context, String imageName, String description) {
        return Container(
          /// 界面 1 中的显示的 Hero 组件是小图标
          /// 图标大小就是半径的两倍
          width: minRadius * 2.0,
          height: minRadius * 2.0,
    
          /// 主界面的核心 Hero 动画
          child: Hero(
            /// 这是 Hero 径向动画与标准 Hero 动画的区别
            /// 如果没有这个动画 , 中间过程会变成椭圆
            createRectTween: _createRectTween,
    
            /// Hero 动画标签
            tag: imageName,
    
            child: OvalRectWidget(
              maxRadius: maxRadius,
    
              /// 最内层显示的是网络图片组件
              child: ImageWidget(
                /// 设置网络图片地址
                imageUrl: imageName,
    
                // 设置点击事件
                onTap: () {
                  /// 点击后跳转到新界面中
    
                  Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder:
                      (BuildContext context, Animation<double> animation,
                          Animation<double> secondaryAnimation) {
                    // 创建一个 RoutePageBuilder
                    return AnimatedBuilder(
                        animation: animation,
                        builder: (context, child) {
    
                          /// 设置透明度组件
                          return Opacity(
                            /// 当前的透明度值 , 取值 0.0 ~ 1.0
                            opacity: opacityCurve.transform(animation.value),
    
                            // 主要显示的使用透明度控制的组件
                            // 页面 2 组件
                            child: _buildSecondPageWidget(context, imageName, description),
                          );
                        });
                  }));
                },
              ),
            ),
          ),
        );
      }
    




    三、创建页面 2 的组件 ( Hero 组件 2 )



    页面 1 的 Hero 组件显示的圆形的 , 跳转到页面 2 后 , 相同 tag 的 Hero 组件显示方形 ;

    控制 OvalRectWidget 是圆形还是方形 , 主要是控制 OvalRectWidget 组件的宽高 , 这里设置的宽高相当于上面的 " ① 方形裁剪组件 " 设置 , 整个组件没有被裁剪到 , 显示的是方形组件 ;

    创建页面 2 的组件 :

      /// 创建页面 2 , 这是点击后跳转到的页面
      /// 三个参数分别是 : 上下文 , 图片名称 , 页面描述
      /// 页面的核心组件是 Hero 组件 , 只有 1 个
      static Widget _buildSecondPageWidget(
          BuildContext context, String imageName, String description) {
        return Container(
          color: Theme.of(context).canvasColor,
          child: Center(
            child: Card(
              /// 设置卡片布局阴影大小
              elevation: 8,
    
              /// 卡片布局中显示图片和图片的描述
              child: Column(
                /// 在主轴方向 , 也就是垂直方向 , 应该占用多少空间
                /// Colum 主轴方向是垂直方向
                /// Row 主轴方向是水平方向
                mainAxisSize: MainAxisSize.min,
    
                children: [
                  SizedBox(
                    /// 约束布局大小的组件的宽高定义为最大半径的两倍
                    width: maxRadius * 2,
                    height: maxRadius * 2,
    
                    /// 核心 Hero 组件
                    child: Hero(
                      /// 创建径向动画
                      /// 如果没有这个动画 , 中间过程会变成椭圆
                      createRectTween: _createRectTween,
    
                      /// Hero 动画标签 ID
                      tag: imageName,
    
                      /// Hero 动画作用的组件
                      child: OvalRectWidget(
                        /// 这里的半径设置为最大半径值 ,
                        maxRadius: maxRadius,
    
                        /// 最内层显示的是网络图片组件
                        child: ImageWidget(
                          imageUrl: imageName,
                          onTap: () {
                            /// 点击后关闭当前页面
                            Navigator.of(context).pop();
                          },
                        ),
                      ),
                    ),
                  ),
    
                  /// 图片描述文本
                  Text(
                    // 设置文本内容
                    description,
                    // 设置文本样式, 粗体
                    style: TextStyle(fontWeight: FontWeight.bold),
                    textScaleFactor: 3.0,
                  ),
    
                  /// 空白间隔 , 无实际意义
                  const SizedBox(
                    height: 16,
                  ),
                ],
              ),
            ),
          ),
        );
      }
    




    四、完整代码示例



    完整代码示例 :

    import 'package:flutter/material.dart';
    import 'package:flutter/scheduler.dart' show timeDilation;
    import 'dart:math' as math;
    
    void main() {
      runApp(MaterialApp(
        // 该组件本质是 StatelessWidget 组件子类
        home: RadialHeroAnimation(),
      ));
    }
    
    /// Hero 组件 , 跳转前后两个页面都有该组件
    class ImageWidget extends StatelessWidget {
      /// 构造方法
      const ImageWidget({Key key, this.imageUrl, this.onTap}) : super(key: key);
    
      /// Hero 动画之间关联的 ID , 通过该标识
      /// 标识两个 Hero 组件之间进行动画过渡
      /// 同时该字符串也是图片的 url 网络地址
      final String imageUrl;
    
      /// 点击后的回调事件
      final VoidCallback onTap;
    
      @override
      Widget build(BuildContext context) {
        return Material(
          /// 获取主题颜色 , 并将透明度设置为 0.25
          color: Colors.green,
    
          /// 按钮
          child: InkWell(
            /// 按钮点击事件
            onTap: onTap,
            child: LayoutBuilder(
              builder: (BuildContext context, BoxConstraints size) {
                return Image.network(
                  imageUrl,
                  fit: BoxFit.contain,
                );
              },
            ),
          ),
        );
      }
    }
    
    /// Hero 组件 , 径向动画扩展
    /// 该组件主要用于裁剪组件用的
    class OvalRectWidget extends StatelessWidget {
    
      /// 这里的裁剪大小 clipRectSize 最大半径 / 2 的开方值 再乘以 2
      const OvalRectWidget({Key key, this.maxRadius, this.child})
          : clipRectSize = 2.0 * (maxRadius / math.sqrt2),
            super(key: key);
    
      // 最大半径值
      final double maxRadius;
    
      /// 该值需要动态计算
      final clipRectSize;
      final Widget child;
    
      /// 这里特别注意该圆形裁剪组件
      /// 如果整个组件的宽高都是 maxRadius ,
      /// 内部的方形组件宽高是 2.0 * (maxRadius / math.sqrt2)
      /// 并且该方形组件居中显示
      /// 那么该方形组件的四个顶点正好处于圆形组件的裁剪半径位置
      /// 也就是方形组件完整显示 , 没有裁剪到
      @override
      Widget build(BuildContext context) {
        /// 布局裁剪组件 , 可以将布局裁剪成圆形
        return ClipOval(
    
          /// 可用于约束布局大小的组件
          /// 这里的居中显示是关键 , 如果不居中显示 , 最终还是圆形
          child: Center(
            child: SizedBox(
              width: clipRectSize,
              height: clipRectSize,
    
              /// 用于裁剪圆角矩形的组件
              child: ClipRect(
                child: child,
              ),
            ),
          ),
        );
      }
    }
    
    class RadialHeroAnimation extends StatelessWidget {
      /// 最小半径
      /// 使用该半径作为组件大小时 , 组件被裁剪成圆形
      static const double minRadius = 32.0;
    
      /// 最大半径
      /// 使用该半径作为组件大小时 , 组件被裁剪成方形
      static const double maxRadius = 128.0;
    
      /// 动画差速器
      static const opacityCurve = Interval(0.0, 0.75, curve: Curves.fastOutSlowIn);
    
      /// 创建径向动画
      static RectTween _createRectTween(Rect begin, Rect end) {
        /// MaterialRectCenterArcTween 就是从方形到圆形变化的辅助类
        return MaterialRectCenterArcTween(begin: begin, end: end);
      }
    
      /// 创建页面 2 , 这是点击后跳转到的页面
      /// 三个参数分别是 : 上下文 , 图片名称 , 页面描述
      /// 页面的核心组件是 Hero 组件 , 只有 1 个
      static Widget _buildSecondPageWidget(
          BuildContext context, String imageName, String description) {
        return Container(
          color: Theme.of(context).canvasColor,
          child: Center(
            child: Card(
              /// 设置卡片布局阴影大小
              elevation: 8,
    
              /// 卡片布局中显示图片和图片的描述
              child: Column(
                /// 在主轴方向 , 也就是垂直方向 , 应该占用多少空间
                /// Colum 主轴方向是垂直方向
                /// Row 主轴方向是水平方向
                mainAxisSize: MainAxisSize.min,
    
                children: [
                  SizedBox(
                    /// 约束布局大小的组件的宽高定义为最大半径的两倍
                    width: maxRadius * 2,
                    height: maxRadius * 2,
    
                    /// 核心 Hero 组件
                    child: Hero(
                      /// 创建径向动画
                      /// 如果没有这个动画 , 中间过程会变成椭圆
                      createRectTween: _createRectTween,
    
                      /// Hero 动画标签 ID
                      tag: imageName,
    
                      /// Hero 动画作用的组件
                      child: OvalRectWidget(
                        /// 这里的半径设置为最大半径值 ,
                        maxRadius: maxRadius,
    
                        /// 最内层显示的是网络图片组件
                        child: ImageWidget(
                          imageUrl: imageName,
                          onTap: () {
                            /// 点击后关闭当前页面
                            Navigator.of(context).pop();
                          },
                        ),
                      ),
                    ),
                  ),
    
                  /// 图片描述文本
                  Text(
                    // 设置文本内容
                    description,
                    // 设置文本样式, 粗体
                    style: TextStyle(fontWeight: FontWeight.bold),
                    textScaleFactor: 3.0,
                  ),
    
                  /// 空白间隔 , 无实际意义
                  const SizedBox(
                    height: 16,
                  ),
                ],
              ),
            ),
          ),
        );
      }
    
      /// 创建在界面 1 显示的图标 , 点击后跳转到界面 2
      /// 页面的核心组件是 Hero 组件 , 而且是 3 个
      Widget _buildFirstPagWidget(
          BuildContext context, String imageName, String description) {
        return Container(
          /// 界面 1 中的显示的 Hero 组件是小图标
          /// 图标大小就是半径的两倍
          width: minRadius * 2.0,
          height: minRadius * 2.0,
    
          /// 主界面的核心 Hero 动画
          child: Hero(
            /// 这是 Hero 径向动画与标准 Hero 动画的区别
            /// 如果没有这个动画 , 中间过程会变成椭圆
            createRectTween: _createRectTween,
    
            /// Hero 动画标签
            tag: imageName,
    
            child: OvalRectWidget(
              maxRadius: maxRadius,
    
              /// 最内层显示的是网络图片组件
              child: ImageWidget(
                /// 设置网络图片地址
                imageUrl: imageName,
    
                // 设置点击事件
                onTap: () {
                  /// 点击后跳转到新界面中
    
                  Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder:
                      (BuildContext context, Animation<double> animation,
                          Animation<double> secondaryAnimation) {
                    // 创建一个 RoutePageBuilder
                    return AnimatedBuilder(
                        animation: animation,
                        builder: (context, child) {
    
                          /// 设置透明度组件
                          return Opacity(
                            /// 当前的透明度值 , 取值 0.0 ~ 1.0
                            opacity: opacityCurve.transform(animation.value),
    
                            // 主要显示的使用透明度控制的组件
                            // 页面 2 组件
                            child: _buildSecondPageWidget(context, imageName, description),
                          );
                        });
                  }));
                },
              ),
            ),
          ),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        /// 时间膨胀系数 , 用于降低动画运行速度
        /// 1.0 是标准速度
        timeDilation = 5.0;
    
        /// 主界面显示内容
        return Scaffold(
          appBar: AppBar(
            title: Text("Hero 径向动画演示"),
          ),
          body: Container(
            padding: EdgeInsets.all(32),
            alignment: FractionalOffset.bottomLeft,
    
            /// 横向列表显示 3 个图标
            child: Row(
    
              /// 排列方式 : 平分空间
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                _buildFirstPagWidget(context,
                    "https://img-blog.csdnimg.cn/20210330094257242.png", "蜂王"),
                _buildFirstPagWidget(context,
                    "https://img-blog.csdnimg.cn/20210330093526559.png", "蜜蜂"),
                _buildFirstPagWidget(context,
                    "https://img-blog.csdnimg.cn/2021033009353553.png", "工蜂"),
              ],
            ),
          ),
        );
      }
    }
    
    

    运行效果 :

    在这里插入图片描述





    五、相关资源



    参考资料 :


    重要的专题 :


    博客源码下载 :

    展开全文
  • Flutter Hero动画(2.6)

    2020-11-16 16:22:20
    今天给大家带来的是径向Hero动画,我本来对动画就不敏感,刚看到的时候都准备放弃了,准备放弃时干了一碗鸡汤,终于通过一上午的时间,搞出来了,我感觉我是太牛了!!,接下来就看看效果吧~效果图分析步骤页面搭建正切方形...
  • ◯、Hero 动画简介、 一、创建 Hero 动画核心组件、 二、创建源页面、 三、创建目的页面、 四、页面跳转、 五、完整代码示例、 六、相关资源、
  • Flutter学习-Hero动画

    2019-11-18 19:22:41
    '动画路由-FirstPage' ) , ) , body : Center ( child : Container ( alignment : Alignment . topCenter , child : InkWell ( child : Hero ( tag : 'avatar' , child : ClipOval ( ...
  • https://flutter.dev/docs/development/ui/animations/hero-animations https://api.flutter.dev/flutter/widgets/Hero-class.html
  • 一、 ...这个图标有从大图逐渐变成小图的位置,这就是Hero标准动画的实现 代码: import 'package:flutter/material.dart'; import 'package:flutter_color_plugin/flutter_color_plugin.dart';...
  • Flutter Hero动画 回顾

    2021-07-14 11:21:58
    前言:日常项目开发中,点击item跳转到下一个页面时候,经常带有那种转场动画,而不是直接切换,体验感会好很多,在Flutter中hero就可以实现这样的效果 需求:点击图片,转场滑动到下一个页面,点击文字 转场返回 1...
  • 英雄英雄 例子 要运行示例项目,请克隆存储库,然后首先从Example目录运行pod install 。 要求 安装 LWHeroOC可通过。 要安装它,只需将以下行添加到您的Podfile中: pod 'LWHeroOC' 作者 罗薇 执照 LWHeroOC在MIT...
  • 31 Hero 动画的使用 1.源代码 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState();...
  • 文章目录Hero 动画效果示例更多动画效果展示 Hero 动画 在 Android中有共享元素动画,能够实现页面之间共享元素的切换效果。Flutter 中也提供 相应的 Hero widget 实现该效果。 既然 Hero是个widget,按照惯例,...
  • 文章目录一丶补间动画二丶Hero动画三丶交错动画喜欢记得点个赞哟,我叫王睿很高兴认识大家! 学习来源 一丶补间动画 1、动画通知—addListener()和addStatusListener()来添加 import 'packageflutteranimation.dart'...
  • Hero动画就是在路由切换时,有一个共享的Widget可以在新旧路由间切换,由于共享的Widget在新旧路由页面上的位置、外观可能有所差异,所以在路由切换时会逐渐过渡,这样就会产生一个Hero动画。 要触发Hero动画,Hero...
  • Hero - 让你通过Storyboard轻易制作界面的切换动画。就像Keynote的Magic Move一样。Hero会自动配对相同的视图。此外你还可以加入其他的动画特效。 Hero还自带一个Debug调试器。可以以3D方式回放你的动画
  • -tag:[必须]用于关联两个Hero动画的标识 -createRectTween:定义目标Hero的边界,在从起始位置到目的位置的“飞行”过程中该如何变化 -child:[必须]定义动画所呈现的widget 实现径向hero动画 二、实现径向...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,694
精华内容 1,477
关键字:

Hero动画