Flutter(十六)——Hero动画

举报
择城终老 发表于 2021/07/27 00:51:45 2021/07/27
【摘要】 本文目录 前言基本用法实现原理 前言 在前面实践组件的开发中,我们做了一个登录的界面,里面有一个组件Hero,不知道大家是否记得?当时没有展开来说,是因为它属于动画的内容,本文就要重点讲解Hero动画。 做过Java开发Android的程序员应该都清楚,Shared Element Transition可以让Activity或Fragment做出流...

前言

在前面实践组件的开发中,我们做了一个登录的界面,里面有一个组件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())); }, ), ), );
  }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

代码很简单,就是监听点击事件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, ), ), );
  }

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

这段代码也很简单,就是常用的组件,只是在外层套了一层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);
	}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在初始化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);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

文章来源: liyuanjinglyj.blog.csdn.net,作者:李元静,版权归原作者所有,如需转载,请联系作者。

原文链接:liyuanjinglyj.blog.csdn.net/article/details/104143372

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。