Flutter(十六)——Hero动画

本文目录

    • 前言
    • 基本用法
    • 实现原理

前言

在前面实践组件的开发中,我们做了一个登录的界面,里面有一个组件Hero,不知道大家是否记得?当时没有展开来说,是因为它属于动画的内容,本文就要重点讲解Hero动画。
Flutter(十六)——Hero动画_第1张图片
做过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动画运动示意图:
Flutter(十六)——Hero动画_第2张图片如上图所示,两个路由之间还有一个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动起来,只不过前者是正向的,后者是逆向的。

你可能感兴趣的:(Flutter开发手机应用,android)