Flutter Hero动画组件的详细介绍与示例代码

Flutter Hero widget 属性介绍

属性 说明
tag 不可为空,用于标识Hero Widget,其在不同页面之间的Hero Widget需要使用相同的tag
child 不可为空,Hero Widget中要进行动画过渡的子组件
flightShuttleBuilder 可以为空,在进行Hero Widget的过渡动画时,可以使用此属性来构建自定义的过渡Widget
placeholderBuilder 可以为空,当Hero Widget从一个页面移动到另一个页面时,可以使用此属性来构建一个占位Widget
transitionOnUserGestures 用于指定是否只在用户手势触发时执行Hero Widget的过渡动画,默认值false
createRectTween 可以为空,用于 定义 Hero 组件的边界,以及定义 Hero 组件在界面切换时,从 源界面的起始位置 到 目的界面的最终位置,动画执行的变化过程

使用非常简单,只要hero组件的tag一样,既可以。例子如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 第一个界面
Hero(
  tag: 'imageHero',
  child: Image.network(
    'https://img2.baidu.com/it/u=3202947311,1179654885&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500',
    width: 50,
    height: 50,
  ),
),
// 第二个界面
Hero(
  tag: 'imageHero',
  child: Image.network(
    'https://img2.baidu.com/it/u=3202947311,1179654885&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500',
    width: 230,
    height: 230,
  ),
),

除了在Hero组件内包裹图片以外,还可以包裹任何widget,包裹Container示例如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 第一个界面
Hero(
  tag: 'hero',
  child: Container(
    width: 150,
    height: 150,
    decoration: BoxDecoration(
        color: Colors.purple,
        borderRadius: BorderRadius.circular(80)),
    child: const Center(
      child: DefaultTextStyle(
        style: TextStyle(fontSize: 20, color: Colors.white),
        child: Text(
          '点我 ->',
        ),
      ),
    ),
  ),
),

// 第二个界面
Hero(
  tag: 'hero',
  child: Container(
    width: _size.width,
    height: _size.height / 2,
    color: Colors.redAccent,
    child: const Center(
      child: DefaultTextStyle(
        style: TextStyle(fontSize: 20, color: Colors.white),
        child: Text(
          '<- 点击屏幕返回',
        ),
      ),
    ),
  ),
),
  • 注意事项,如果包裹的是Text有可能在界面跳转的时候,文本出现黄色的双下滑线,是由于Text组件是属于Material风格的,而在Hero动画期间,Text组件丢失了包裹它的Material风格组件,例如Scaffold等。
  • 解决办法:使用 DefaultTextStyle 包裹Text,把Text的 style 属性放到 DefaultTextStyle 的 style 属性中。

完整代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import 'package:flutter/material.dart';

class HeroMainPage extends StatelessWidget {
  const HeroMainPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Main'),
      ),
      body: Stack(
        children: [
          Positioned(
            left: 20,
            top: 50,
            child: Hero(
              tag: 'imageHero',
              child: Image.network(
                'https://img2.baidu.com/it/u=3202947311,1179654885&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500',
                width: 50,
                height: 50,
              ),
            ),
          ),
          Center(
            child: GestureDetector(
              onTap: () {
                Navigator.of(context).push(MaterialPageRoute(
                  builder: (context) {
                    return const HeroDetailPage();
                  },
                ));
              },
              child: Hero(
                tag: 'hero',
                child: Container(
                  width: 150,
                  height: 150,
                  decoration: BoxDecoration(
                      color: Colors.purple,
                      borderRadius: BorderRadius.circular(80)),
                  child: const Center(
                    child: DefaultTextStyle(
                      style: TextStyle(fontSize: 20, color: Colors.white),
                      child: Text(
                        '点我 ->',
                      ),
                    ),
                  ),
                ),
              ),
            ),
          )
        ],
      ),
    );
  }
}

class HeroDetailPage extends StatelessWidget {
  const HeroDetailPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    var _size = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        title: const Text('Detail'),
      ),
      body: Stack(
        children: [
          Positioned.fill(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                const Spacer(),
                const Text(
                  'lickscreen.com',
                ),
                GestureDetector(
                  onTap: () {
                    Navigator.of(context).pop();
                  },
                  child: Hero(
                    tag: 'hero',
                    child: Container(
                      width: _size.width,
                      height: _size.height / 2,
                      color: Colors.redAccent,
                      child: const Center(
                        child: DefaultTextStyle(
                          style: TextStyle(fontSize: 20, color: Colors.white),
                          child: Text(
                            '<- 点击屏幕返回',
                          ),
                        ),
                      ),
                    ),
                  ),
                )
              ],
            ),
          ),
          Positioned(
            right: 40,
            top: 50,
            child: Hero(
              tag: 'imageHero',
              child: Image.network(
                'https://img2.baidu.com/it/u=3202947311,1179654885&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500',
                width: 230,
                height: 230,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Hero效果如下

hero-demo