1,471
社区成员
发帖
与我相关
我的任务
分享本文是关于 Flutter 中的 Hero 小部件和动画。我们将看一眼这些东西的本质,然后研究一个在实践中使用它们的完整示例。
简单来说,Hero动画是一个页面(屏幕)的元素在用户导航时飞到另一个页面的动画。要设置Hero动画,您需要在 2 个页面上放置 2 个Hero小部件,如下所示:
// implement a Hero widget on source page
InkWell(
// navigate to the detail page on tap
onTap: () {
Navigator.of(context).push(/* destination route */);
},
// implement the Hero widget
child: Hero(
tag: 'id-123',
child: /* ... */,
),
);
和这个:
// implement the corresponding Hero widget on destination page Hero( tag: 'id-123', child: /* ... */, ),
2 个不同页面上的 2 个Hero小部件链接在一起,仅仅是因为它们具有相同的标签。这个非常重要。标签可以是任意 Dart 对象,如字符串、数字等。
Hero 小部件的构造函数:
Hero({
Key? key,
required Object tag,
CreateRectTween? createRectTween,
HeroFlightShuttleBuilder? flightShuttleBuilder,
HeroPlaceholderBuilder? placeholderBuilder,
bool transitionOnUserGestures = false,
required Widget child
})
有 2 个属性不能忽略:
tag:Hero的标识符。出发页面上的Hero小部件的标签和目的地页面上的Hero小部件的标签必须相同。
child : 在过渡期间从一个页面飞到另一个页面的小部件
一堵文字墙可能很无聊,让你感到困惑。继续下一部分,通过编写一个工作演示应用程序来更好地理解。
我们要构建的演示项目包含 2 个页面:
主屏幕:显示缩略图网格。您可以通过点击缩略图导航到详细信息页面。
详细信息页面:显示有关图像的详细信息,包括全尺寸图像和标题。您可以通过按后退按钮或关闭按钮返回主页(这两个按钮做同样的事情但有不同的实现)
下面的动画 GIF 截图清楚地描绘了我们的最终产品。在页面之间导航时将您的意图转向动画(图像被轻轻放大/缩小):
以下是main.dart中的完整源代码,并在注释中进行了解释。为简单起见,我将所有内容都放在一个文件中。但是,您可以通过将每个屏幕放在一个单独的文件中来保持代码的良好组织(这不是必须做的,但被认为是很好的做法)。
// main.dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
// Remove the debug banner
debugShowCheckedModeBanner: false,
title: 'KindaCode.com',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const KindaCodeDemo(),
);
}
}
// implement the main page
class KindaCodeDemo extends StatefulWidget {
const KindaCodeDemo({Key? key}) : super(key: key);
@override
State<KindaCodeDemo> createState() => _KindaCodeDemoState();
}
class _KindaCodeDemoState extends State<KindaCodeDemo> {
// dummy data to feed the grid view
// list of images
final List<Map<String, dynamic>> _images = [
{
'id': 'i1',
'title': 'One',
'url': 'https://www.kindacode.com/wp-content/uploads/2022/08/1.png'
},
{
'id': 'i2',
'title': 'Two',
'url': 'https://www.kindacode.com/wp-content/uploads/2022/08/2.png'
},
{
'id': 'i3',
'title': 'Three',
'url': 'https://www.kindacode.com/wp-content/uploads/2022/08/3.jpg'
},
{
'id': 'i4',
'title': 'Four',
'url': 'https://www.kindacode.com/wp-content/uploads/2022/08/4.jpg'
},
{
'id': 'i5',
'title': 'Five',
'url': 'https://www.kindacode.com/wp-content/uploads/2022/08/5.jpg'
},
{
'id': 'i6',
'title': 'Six',
'url': 'https://www.kindacode.com/wp-content/uploads/2022/08/6.jpg'
}
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('KindaCode.com')),
body: Container(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
// implement the grid view
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
childAspectRatio: 3 / 2,
crossAxisSpacing: 20,
mainAxisSpacing: 20),
itemCount: _images.length,
itemBuilder: (BuildContext ctx, index) {
return InkWell(
key: ValueKey(_images[index]['id']),
// navigate to the detail page on tap
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
// pass the image data to the detail page
builder: (context) => DetailPage(_images[index])));
},
// implement the Hero widget
child: Hero(
// important: the tag
tag: _images[index]['id'],
// display the image
child: Image.network(
_images[index]['url'],
fit: BoxFit.cover,
),
),
);
}),
),
);
}
}
// detail page
class DetailPage extends StatelessWidget {
// get the image data that was passed from the main page
final Map image;
const DetailPage(this.image, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
// implement the app bar
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text(image['title']),
actions: [
// this button does the same thing as the back button
// I put it here to show you how to go back to the previous screen by implementing your own code
IconButton(
onPressed: () {
Navigator.of(context).pop();
},
icon: const Icon(Icons.close))
],
),
body: Center(
// implement the Hero widget
child: Hero(
// important: the tag
tag: image['id'],
child: Image.network(image['url']),
),
),
);
}
}
您已经了解了有关 Hero 小部件的基础知识,这并不难实现,但会产生丰硕的成果。同样,这可能是多余的,但重要的是要记住您必须为 Hero 小部件设置标签。