以下大部分内容源于官方文档及Demo
文档:https://docs.flutter.dev/development/ui/animations/hero-animations#radial-hero-animations
Demo:
官方介绍:https://material.io/guidelines/motion/transforming-material.html
意思触摸圆形,然后圆形变换为其它形状的一种动画效果。
建议的2种展示方式:
Hero 是Flutter提供的一个可以实现子Widget在页面切换时带有飞行效果的Widget,一般用于图片。
可看博客:Flutter Hero 实现共享元素转场动画
Radial transformation 径向变换动画效果一般用于圆形变矩形。
实现变换圆形变矩形的转场动画原理(来源于官方文档)。
蓝色渐变代表图像,表示剪辑形状相交的位置。
在动画开始前,相交的结果是一个圆形剪辑 ( ClipOval)。
在动画执行过程中,ClipRect保持恒定大小,ClipOval开始缩放。
在动画结束时,圆形和矩形剪辑的交点产生一个与Hero Widget相同大小的矩形。即图像不再被剪裁。
裁剪Widget的代码实现
import 'dart:math' as math;
import 'package:flutter/material.dart';
class RadialExpansionWidget extends StatelessWidget {
const RadialExpansionWidget({
super.key,
required this.maxRadius,
this.child,
}) : clipRectSize = 2.0 * (maxRadius / math.sqrt2);
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),
),
),
);
}
}
自定义的RadialExpansionWidget
实现裁剪形状,使用Hero
实现飞行效果。
第一页展示4个半径为30的圆
class FirstPage extends StatelessWidget {
const FirstPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// import 'package:flutter/scheduler.dart' show timeDilation;
// 使转场速度变慢,便于观察转场动画形状的变化过程
timeDilation = 3.0;
return Scaffold(
appBar: AppBar(title: const Text('FirstPage')),
body: Align(
alignment: Alignment.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(4, (index) => _buildItem(context, index)),
),
),
);
}
Widget _buildItem(BuildContext context, int index) {
return CupertinoButton(
child: _buildHeroWidget(index),
onPressed: () {
Navigator.of(context).push(
PageRouteBuilder<void>(
pageBuilder: (context, animation, secondaryAnimation) {
return SecondPage(index: index);
},
),
);
},
);
}
///目标实现半径 30的圆,转换为半径120的圆包裹的矩形
Widget _buildHeroWidget(int index) {
const radius = 30;
return SizedBox(
width: radius * 2,
height: radius * 2,
child: Hero(
tag: 'hero_tag_$index',
child: RadialExpansionWidget(
maxRadius: 120,
child: Container(
color: Colors.red,
child: LayoutBuilder(
builder: (context, constraints) {
return FlutterLogo(size: constraints.maxWidth);
},
),
),
),
),
);
}
}
第二页展示一个卡片,圆形的图变成了矩形的图。点击页面内容回到上一页。
class SecondPage extends StatelessWidget {
final int index;
const SecondPage({Key? key, required this.index}) : super(key: key);
@override
Widget build(BuildContext context) {
// 图片矩形是由半径为120的圆得来
const maxRadius = 120.0;
return GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
color: Theme.of(context).canvasColor,
height: double.infinity,
width: double.infinity,
alignment: Alignment.center,
child: Card(
elevation: 8.0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: maxRadius * 2,
height: maxRadius * 2,
child: Hero(
tag: 'hero_tag_$index',
child: RadialExpansionWidget(
maxRadius: maxRadius,
child: Container(
color: Colors.red,
child: LayoutBuilder(
builder: (context, constraints) {
return FlutterLogo(size: constraints.maxWidth);
},
),
),
),
),
),
Text(
'第$index个Item',
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 16.0),
],
),
),
),
);
}
}
此时的动画效果
动画的形状变成了椭圆,Hero在MaterialApp中默认使用MaterialRectArcTween
,要实现动画过程中裁剪形状为圆形,需要使用MaterialRectCenterArcTween
。
在使用了Hero的地方修改Hero的路径动画
Hero(
tag: 'hero_tag_$index',
createRectTween: (begin, end) {
return MaterialRectCenterArcTween(begin: begin, end: end);
},
child: ...
)
此时的效果
在 Navigator.of(context).push()
PageRouteBuilder中,添加透明度动画,让页面切换更自然。
Widget _buildItem(BuildContext context, int index) {
return CupertinoButton(
child: _buildHeroWidget(index),
onPressed: () {
Navigator.of(context).push(
PageRouteBuilder<void>(
pageBuilder: (context, animation, secondaryAnimation) {
// 透明度变换Widget
return FadeTransition(
opacity: CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn, // 非曲线动画,慢进快出
),
child: SecondPage(index: index),
);
},
),
);
},
);
}
最终动画效果
文中的代码基本是参考的官方DEMO:
https://github.com/flutter/website/tree/main/examples/_animation/radial_hero_animation