一、Hero函数原型
-tag:[必须]用于关联两个Hero动画的标识
-createRectTween:定义目标Hero的边界,在从起始位置到目的位置的“飞行”过程中该如何变化
-child:[必须]定义动画所呈现的widget
实现径向hero动画
二、实现径向动画
从圆形变方形,从方形变圆形
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
import 'dart:math' as math;
void main() => runApp(MaterialApp(home: RadialExpansionDemo()));
class Photo extends StatelessWidget{
final String photo;
final VoidCallback onTap;
final double width;
const Photo({Key key, this.photo, this.onTap, this.width}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).primaryColor.withOpacity(0.25),
child: InkWell(
onTap: onTap,
child: LayoutBuilder(builder: (context,size){
return Image.network(photo,fit: BoxFit.contain);
}),
),
);
}
}
class RadialExpansionDemo extends StatelessWidget{
static const double kMinRadius=32.0;
static const double kMaxRadius=128.0;
static const opacityCurve= const Interval(0.0,0.75, curve : Curves.fastOutSlowIn);
static RectTween _createRectTween(Rect begin,Rect end){
return MaterialRectArcTween(begin: begin,end: end);
}
static Widget _buildPage(BuildContext context,String imageName,String description){
return Container(
color: Theme.of(context).canvasColor,
child: Center(
child: Card(
elevation: 8,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
width: kMaxRadius*2,
height: kMaxRadius*2,
child: Hero(
createRectTween: _createRectTween,
tag: imageName, child: RadialExpansion(
maxRadius: kMaxRadius,
child: Photo(
photo: imageName,
onTap: (){
Navigator.of(context).pop();
},
),
)
),
),
Text(description,style: TextStyle(fontWeight: FontWeight.bold),textScaleFactor: 3.0),
const SizedBox(height: 16)
],
),
),
),
);
}
Widget _buildHero(BuildContext context,String imageName,String description){
return Container(
width: kMinRadius*2,
height: kMinRadius*2,
child: Hero(
createRectTween: _createRectTween,
tag: imageName,
child: RadialExpansion(
maxRadius: kMaxRadius,
child: Photo(
photo: imageName,
onTap: (){
Navigator.of(context).push(
PageRouteBuilder<void>(
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation){
return AnimatedBuilder(animation: animation, builder: (context,child){
return Opacity(opacity:
opacityCurve.transform(animation.value),
child: _buildPage(context, imageName, description),
);
});
}
)
);
}
),
),
)
);
}
@override
Widget build(BuildContext context) {
timeDilation=5.0;
return Scaffold(
appBar: AppBar(
title: const Text('Radial Transition Demo'),
),
body:Container(
padding: EdgeInsets.all(32),
alignment: FractionalOffset.bottomLeft,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
_buildHero(context, 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/radial_hero_animation/images/chair-alpha.png', "Chair"),
_buildHero(context, 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/radial_hero_animation/images/binocular-alpha.png', "Binoculars"),
_buildHero(context, 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/radial_hero_animation/images/beachball-alpha.png', "Beach ball"),
],
),
) ,
);
}
}
class RadialExpansion extends StatelessWidget{
final double maxRadius;
final clipRectSize;
final Widget child;
const RadialExpansion({
Key key,
this.maxRadius,
this.child})
:clipRectSize=2.0*(maxRadius/math.sqrt2), super(key: key);
@override
Widget build(BuildContext context) {
return ClipOval(
child: Center(
child: SizedBox(
width: clipRectSize,
height: clipRectSize,
child: ClipRect(
child: child,
),
),
),
);
}
}
class HeroAnimation extends StatelessWidget{
@override
Widget build(BuildContext context) {
timeDilation=10.0;
return Scaffold(
appBar: AppBar(
title: const Text('Basic Hero Animation'),
),
body:Center(
child: Photo(
photo: 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/hero_animation/images/flippers-alpha.png',
width: 300.0,
onTap: (){
Navigator.of(context).push(MaterialPageRoute(builder: (context){
return Scaffold(
appBar: AppBar(
title: const Text('Basic Hero Animation'),
),
body: Container(
color: Colors.lightBlueAccent,
padding: EdgeInsets.all(16),
alignment: Alignment.topLeft,
child: Photo(
photo: 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/hero_animation/images/flippers-alpha.png',
width: 100,
onTap: (){
Navigator.of(context).pop();//关闭当前界面
},
),
),
);
}));
},
),
) ,
);
}
}
运行结果:
三、实现原理:
1、main函数中使用了StatelessWidget ,
2、
定义了两个不同大小的圆形的半径 kMinRadius、kMaxRadius
设置了opacityCurve,值为0~0.75
3、
在buildpage方法中定义了一个hero,createRectTween使用的是刚刚创建的_createRectTween
createRectTween用来描述两个动画执行过程中过渡的一个动画效果
这里由圆形径向变化到一个方形,由方形径向变化到圆形
4、createRectTween:
_createRectTween用到了MaterialRectCenterArcTween,它是flutter里面的一个类,它就是用于完成从方形到圆形变化的一个辅助类
Tag:关联两个图片之间的tag
当点击里面的页面的时候,通过pop关闭界面
5、创建界面底部三个图形,并通过buildHero生成hero
当点击底部图片的时候push了一个新的页面
这里用到了PageRouteBuilder,它用于生成一次性的路由,也就是说可以通过push()跳转到其它界面,然后再通过pop()方法返回当前界面,在界面跳转的时候定义了两个界面的过程
在过渡过程中,当前界面会淡入淡出,淡入淡出的效果是通过PageRouteBuilder这个效果实现的。它里面有Animation的回调,通过animation回调改变当前界面的alpha值。
里面调用_buildPage(), _buildPage()里面包了Hero()。
对比标准的Hero动画,这个动画就是多了一个createRectTween这样参数,也就是定义了两个动画在过渡过程中形状变化的效果。