Last active
August 14, 2022 10:52
-
-
Save jixiaoyong/8b6584e73abe430d3c1ae926b80a86bd to your computer and use it in GitHub Desktop.
hero动画示例
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:flutter/scheduler.dart'; | |
import 'dart:math' as math; | |
/// @author : jixiaoyong | |
/// @description : Hero动画的进阶使用 | |
/// 这个示例中,First和Second页面中的正方形Hero.child内部被ClipOval和ClipRect相交部分裁剪 | |
/// ClipRect的边长为也即Hero.child最大边长,是与直径为maxClipOvalDiameter的圆的内切正方形的边长 | |
/// 也即:maxClipOvalDiameter / 2 * 根号2 | |
/// 而Hero.child最小的时候,则为半径为minClipOvalDiameter/2的圆 | |
/// @email : jixiaoyong1995@gmail.com | |
/// @date : 8/14/2022 | |
main() => runApp(const MaterialApp( | |
home: FirstHeroPage(), | |
)); | |
const Size maxClipOvalDiameter = Size.square(200); | |
const Size minClipOvalDiameter = Size.square(100); | |
class FirstHeroPage extends StatelessWidget { | |
const FirstHeroPage({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
// 放慢30倍 | |
timeDilation = 30; | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text("First Hero Page"), | |
), | |
body: Stack( | |
children: [ | |
// 只是为了标记下面的Hero.child的实际大小 | |
Align( | |
alignment: Alignment.bottomCenter, | |
child: Container( | |
width: minClipOvalDiameter.width, | |
height: minClipOvalDiameter.height, | |
color: Colors.cyanAccent, | |
), | |
), | |
Align( | |
alignment: Alignment.bottomCenter, | |
child: Hero( | |
tag: "HeroTag", | |
child: HeroClippedChildWidget( | |
size: minClipOvalDiameter, | |
name: "First", | |
onTap: () { | |
Navigator.push(context, | |
MaterialPageRoute(builder: (context) { | |
return const SecondHeroPage(); | |
})); | |
}, | |
)), | |
) | |
], | |
), | |
); | |
} | |
} | |
class SecondHeroPage extends StatelessWidget { | |
const SecondHeroPage({ | |
Key? key, | |
}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text("Second Hero Page"), | |
), | |
body: Stack( | |
children: [ | |
// 只是为了标记下面的Hero.child的实际大小 | |
Align( | |
alignment: Alignment.topCenter, | |
child: Container( | |
width: maxClipOvalDiameter.width, | |
height: maxClipOvalDiameter.height, | |
color: Colors.cyanAccent, | |
), | |
), | |
Align( | |
alignment: Alignment.topCenter, | |
child: Hero( | |
tag: "HeroTag", | |
child: HeroClippedChildWidget( | |
size: maxClipOvalDiameter, | |
name: "Second", | |
onTap: () { | |
Navigator.pop(context); | |
}, | |
)), | |
) | |
], | |
), | |
); | |
} | |
} | |
/// 原理 https://flutter.cn/assets/images/docs/ui/animations/radial-hero-animation.png | |
/// ClipOval的大小根据Hero的切换而变化,ClipRect的大小则一直是maxRadius的内切正方形大小 | |
/// 从而当Hero从大变小的时候,ClipOval变小,ClipRect不变,看起来是图片逐渐变小并且变圆 | |
/// 当Hero从小变大的时候,ClipOval变大,ClipRect不变,看起来是图片逐渐变大,当ClipOval变得与 | |
/// ClipRect内切并更大时,图片就会从圆逐渐变为ClipRect的形状 | |
class HeroClippedChildWidget extends StatelessWidget { | |
const HeroClippedChildWidget( | |
{Key? key, required this.size, required this.name, required this.onTap}) | |
: super(key: key); | |
final Size size; | |
final String name; | |
final VoidCallback onTap; | |
@override | |
Widget build(BuildContext context) { | |
// 与圆内切的正方形边长s = 圆半径 * 根号2 = 直径 * 根号2 / 2 | |
var clipRectSize = maxClipOvalDiameter.width * math.sqrt2 / 2; | |
return SizedBox( | |
height: size.height, | |
width: size.width, | |
child: GestureDetector( | |
onTap: onTap, | |
// 当Hero动画变到最小时,ClipOval与ClipRect相交部分是ClipOval形状 | |
child: ClipOval( | |
child: Center( | |
// 当Hero动画变到最大时,ClipOval与ClipRect相交部分是ClipRect形状 | |
child: ClipRect( | |
child: SizedBox( | |
height: clipRectSize, | |
width: clipRectSize, | |
child: Container( | |
color: Colors.blueAccent, | |
child: Center( | |
child: Text(name), | |
), | |
), | |
), | |
), | |
), | |
), | |
), | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:flutter/scheduler.dart'; | |
/// @author : jixiaoyong | |
/// @description : 一个简单的Hero动画使用示例 | |
/// 这个示例中,Hero的child是HeroChildWidget,其中有文本,因为Hero动画实现其实是将child放到 | |
/// overlay中,所以文本如果没有被包裹Material的话,就会出现展示样式异常 | |
/// @email : jixiaoyong1995@gmail.com | |
/// @date : 8/14/2022 | |
main() => runApp(const MaterialApp( | |
home: FirstHeroPage(), | |
)); | |
class FirstHeroPage extends StatelessWidget { | |
const FirstHeroPage({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
// 放慢30倍 | |
timeDilation = 30; | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text("First Hero Page"), | |
), | |
body: Stack( | |
children: [ | |
Align( | |
alignment: Alignment.bottomLeft, | |
child: Hero( | |
tag: "HeroTag", | |
child: HeroChildWidget( | |
size: const Size.square(100), | |
name: "First", | |
onTap: () { | |
Navigator.push(context, | |
MaterialPageRoute(builder: (context) { | |
return const SecondHeroPage(); | |
})); | |
}, | |
)), | |
) | |
], | |
), | |
); | |
} | |
} | |
class SecondHeroPage extends StatelessWidget { | |
const SecondHeroPage({ | |
Key? key, | |
}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text("Second Hero Page"), | |
), | |
body: Stack( | |
children: [ | |
Align( | |
alignment: Alignment.topCenter, | |
child: Hero( | |
tag: "HeroTag", | |
child: HeroChildWidget( | |
size: const Size.square(200), | |
name: "Second", | |
onTap: () { | |
Navigator.pop(context); | |
}, | |
)), | |
) | |
], | |
), | |
); | |
} | |
} | |
class HeroChildWidget extends StatelessWidget { | |
const HeroChildWidget( | |
{Key? key, required this.size, required this.name, required this.onTap}) | |
: super(key: key); | |
final Size size; | |
final String name; | |
final VoidCallback onTap; | |
@override | |
Widget build(BuildContext context) { | |
return GestureDetector( | |
onTap: onTap, | |
child: Container( | |
height: size.height, | |
width: size.width, | |
color: Colors.blueAccent, | |
child: Center( | |
child: Text(name), | |
), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment