如何使用Hero动画?
什么是Hero动画?
[Flutter Hero动画开发实用教程] (https://cloud.tencent.com/developer/article/1551853)
在 Flutter中可以用 Hero widget创建这个动画。当 hero 通过动画从源页面飞到目标页面时,目标页面逐渐淡入视野。通常, hero 是用户界面的一小部分,如图片,它通常在两个页面都有。从用户的角度来看, hero 在页面之间“飞翔”。接下来我们一起来学习如何创建Hero动画:
实现标准hero动画
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
class PhotoHero extends StatelessWidget {
const PhotoHero({Key? key, required this.photo, required this.onTap, required this.width }) : super(key: key);
final String photo;
final VoidCallback onTap; // 点击回调
final double width;
Widget build(BuildContext context) {
return SizedBox(
width: width,
child: Hero(
tag: photo, // 动画关联,关联2个动画。
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
child: Image.network(photo, fit: BoxFit.contain),
),
),
),
);
}
}
class HeroAnimation extends StatelessWidget {
const HeroAnimation({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
timeDilation = 10.0; // 1.0 means normal animation speed.
String url = 'https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F1109%2F59f0e407j00rl2ziu00a2c001au00v8g.jpg&thumbnail=660x2147483647&quality=80&type=jpg';
return Scaffold(
appBar: AppBar(
title: const Text('Basic Hero Animation'),
),
body: Center(
child: PhotoHero(
photo: url,
width: 300.0,
onTap: () {
// 点击页面跳转。
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flippers Page'),
),
body: Container(
// Set background to blue to emphasize that it's a new route.
color: Colors.lightBlueAccent,
padding: const EdgeInsets.all(16.0),
alignment: Alignment.topLeft,
child: PhotoHero(
photo: url,
width: 100.0,
onTap: () {
// 跳出当前页面
Navigator.of(context).pop();
}, key: null,
),
),
);
}
));
}, key: null,
),
),
);
}
}
void main() {
runApp(const MaterialApp(home: HeroAnimation()));
}
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);
- tag:[必须]用于关联两个Hero动画的标识;
- createRectTween:[可选]定义目标Hero的边界,在从起始位置到目的位置的“飞行”过程中该如何变化;
- child:[必须]定义动画所呈现的widget;
实现径向hero动画
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
class Photo extends StatelessWidget {
const Photo({ Key? key, required this.photo, required this.color, required this.onTap }) : super(key: key);
final String photo;
final Color? color;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Material(
// Slightly opaque color appears where the image has transparency.
color: Theme.of(context).primaryColor.withOpacity(0.25),
child: InkWell(
onTap: onTap,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints size) {
return Image.network( photo, fit: BoxFit.contain);
},
),
),
);
}
}
class RadialExpansion extends StatelessWidget {
const RadialExpansion({
Key? key,
required this.maxRadius,
required this.child,
}) : clipRectSize = 2.0 * (maxRadius / math.sqrt2),
super(key: key);
final double maxRadius;
final double clipRectSize;
final Widget child;
@override
Widget build(BuildContext context) {
return ClipOval(
child: Center(
child: SizedBox(
width: clipRectSize,
height: clipRectSize,
child: ClipRect(
child: child,
),
),
),
);
}
}
class RadialExpansionDemo extends StatelessWidget {
static const double kMinRadius = 32.0;
static const double kMaxRadius = 128.0;
static const opacityCurve = Interval(0.0, 0.75, curve: Curves.fastOutSlowIn);
const RadialExpansionDemo({Key? key}) : super(key: key);
static RectTween _createRectTween(Rect? begin, Rect? end) {
// 方形到原型变化的辅助类。
return MaterialRectCenterArcTween(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.0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: kMaxRadius * 2.0,
height: kMaxRadius * 2.0,
child: Hero(
createRectTween: _createRectTween,
tag: imageName,
child: RadialExpansion(
maxRadius: kMaxRadius,
child: Photo(
photo: imageName,
onTap: () {
// 子页面图片点击,关闭页面。动画回前页面。
Navigator.of(context).pop();
}, color: null,
),
),
),
),
Text(
description,
style: const TextStyle(fontWeight: FontWeight.bold),
textScaleFactor: 3.0,
),
const SizedBox(height: 16.0),
],
),
),
),
);
}
// 构建主页面的3个item
Widget _buildHero(BuildContext context, String imageName, String description) {
return SizedBox(
width: kMinRadius * 2.0,
height: kMinRadius * 2.0,
child: Hero(
createRectTween: _createRectTween, // 动画转换的形状效果。
tag: imageName,
child: RadialExpansion(
maxRadius: kMaxRadius,
child: Photo(
photo: imageName,
onTap: () {
// 点击底部item,进入图片详情页面,PageRouteBuilder带淡入淡出效果。
Navigator.of(context).push(
PageRouteBuilder<void>(
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return AnimatedBuilder(
animation: animation,
builder:(BuildContext context, Widget? child) {
return Opacity(
// 改变透明度
opacity: opacityCurve.transform(animation.value),
child: _buildPage(context, imageName, description),
);
}
);
},
),
);
}, color: null,
),
),
),
);
}
@override
Widget build(BuildContext context) {
timeDilation = 5.0; // 1.0 is normal animation speed.
return Scaffold(
appBar: AppBar(
title: const Text('Radial Transition Demo'),
),
body: Container(
padding: const EdgeInsets.all(32.0),
alignment: FractionalOffset.bottomLeft, // 底部放3个图片。
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_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/binoculars-alpha.png', 'Binoculars'),
_buildHero(context, 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/radial_hero_animation/images/beachball-alpha.png', 'Beach ball'),
],
),
),
);
}
}
void main() {
runApp(const MaterialApp(home: RadialExpansionDemo()));
}
Animation:是Flutter动画库中的一个核心类,它生成指导动画的值;
CurvedAnimation:Animation的一个子类,将过程抽象为一个非线性曲线;
AnimationController:Animation的一个子类,用来管理Animation;
-
Tween:在正在执行动画的对象所使用的数据范围之间生成值。例如,Tween可生成从红到蓝之间的色值,或者从0到255;
Animation还可以生成除double之外的其他类型值,如:Animation<Color> 或 Animation<Size>;
Animation对象有状态。可以通过访问其value属性获取动画的当前值;
Animation对象本身和UI渲染没有任何关系;forward():启动动画;
reverse({double from}):倒放动画;
reset():重置动画,将其设置到动画的开始位置;
stop({ bool canceled = true }):停止动画;addListener:动画的值发生变化时被调用;
addStatusListener:动画状态发生变化时被调用;显示logo
定义Animation对象
渲染过渡效果