跨页面切换的动效 Widget (Hero animations)

在页面跳转过程中给用户加以引导是非常有用的。实现引导的一种通用做法是在页面切换时为某个组件加上转场动画,从而在两个页面间建立视觉上的锚定关联。

在 Flutter 中,可以通过 Hero widget 实现页面切换时组件的转场动画。

这个教程将包含以下步骤:

  1. 创建两个页面,展示相同的图片

  2. 在第一个页面中加入 Hero 组件

  3. 在第二个页面中加入 Hero 组件

1. 创建两个页面,展示相同的图片

#

在这个示例中,将在两个页面中展示相同的图片。当用户在第一个页面点击图片,会通过一个转场动画切换到第二个页面。现在,我们将会创建页面的视觉结构,并在后续步骤中处理动画。

dart
import 'package:flutter/material.dart';

class MainScreen extends StatelessWidget {
  const MainScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Main Screen'),
      ),
      body: GestureDetector(
        onTap: () {
          Navigator.push(context, MaterialPageRoute(builder: (context) {
            return const DetailScreen();
          }));
        },
        child: Image.network(
          'https://picsum.photos/250?image=9',
        ),
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  const DetailScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTap: () {
          Navigator.pop(context);
        },
        child: Center(
          child: Image.network(
            'https://picsum.photos/250?image=9',
          ),
        ),
      ),
    );
  }
}

2. 在第一个页面中加入 Hero 组件

#

为了通过动画在两个页面间建立联系,需要把每个页面的 Image 组件都包裹进 Hero 组件里面。 Hero 组件有两个参数:

tag
作为 Hero 组件的标识,在这两个页面中必须相同。

child
在两个屏幕直接跨越的那个 widget。

dart
Hero(
  tag: 'imageHero',
  child: Image.network(
    'https://picsum.photos/250?image=9',
  ),
)

3. 在第二个页面中加入 Hero 组件

#

为了完善与第一个页面的关联,同样需要把第二个页面中的 Image 组件包裹进 Hero 组件里面。它的 tag 也必须和第一个页面相同。

Hero 组件被应用到第二个页面后,页面的转场动画就生效了。

dart
Hero(
  tag: 'imageHero',
  child: Image.network(
    'https://picsum.photos/250?image=9',
  ),
)

交互式样例

#
import 'package:flutter/material.dart';

void main() => runApp(const HeroApp());

class HeroApp extends StatelessWidget {
  const HeroApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Transition Demo',
      home: MainScreen(),
    );
  }
}

class MainScreen extends StatelessWidget {
  const MainScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Main Screen'),
      ),
      body: GestureDetector(
        onTap: () {
          Navigator.push(context, MaterialPageRoute(builder: (context) {
            return const DetailScreen();
          }));
        },
        child: Hero(
          tag: 'imageHero',
          child: Image.network(
            'https://picsum.photos/250?image=9',
          ),
        ),
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  const DetailScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTap: () {
          Navigator.pop(context);
        },
        child: Center(
          child: Hero(
            tag: 'imageHero',
            child: Image.network(
              'https://picsum.photos/250?image=9',
            ),
          ),
        ),
      ),
    );
  }
}