跨頁面切換的動畫 Widget (Hero animations)

在頁面跳轉過程中給使用者加以引導是非常有用的。實現引導的一種通用做法是在頁面切換時為某個元件加上轉場動畫,從而在兩個頁面間建立視覺上的錨定關聯。

在 Flutter 中,可以透過 Hero widget 實現頁面切換時元件的轉場動畫。

這個教程將包含以下步驟:

  1. 建立兩個頁面,展示相同的圖片

  2. 在第一個頁面中加入 Hero 元件

  3. 在第二個頁面中加入 Hero 元件

1. 建立兩個頁面,展示相同的圖片

在這個範例中,將在兩個頁面中展示相同的圖片。當用戶在第一個頁面點選圖片,會通過一個轉場動畫切換到第二個頁面。現在,我們將會建立頁面的視覺結構,並在後續步驟中處理動畫。

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。

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

3. 在第二個頁面中加入 Hero 元件

為了完善與第一個頁面的關聯,同樣需要把第二個頁面中的 Image 元件包裹進 Hero 元件裡面。它的 tag 也必須和第一個頁面相同。

Hero 元件被應用到第二個頁面後,頁面的轉場動畫就生效了。

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',
            ),
          ),
        ),
      ),
    );
  }
}