Flutter hero动画

发布时间 2023-12-26 13:52:52作者: angelwgh

在 Flutter 中,图像从当前页面转到另一个页面称为 hero 动画,相同的动作有时也被称为 共享元素过渡。

hero 动画基本结构

  • 在不同页面分别使用两个 hero widgets,同时使用配对的标签来实现动画
  • Navigator 管理含有 app 页面的堆栈。
  • 推送一个页面或弹出一个 Navigator 堆栈中的页面会触发动画。
  • Flutter 框架设置了一个 RectTween 类,用来定义 hero 从原页面飞至目标页面的边界。在飞翔过程中,hero 移动到一个应用图层,这样它可以在两个页面上方显示。

Hero 动画需要使用两个 Hero widgets 来实现:一个用来在原页面中描述 widget,另一个在目标页面中描述 widget。从用户角度来说,hero 似乎是分享的,只有程序员需要了解实施细节。

标准 hero 动画

  • 使用 MaterialPageRoute,CupertinoPageRoute 指定页面,或使用 PageRouteBuilder 创建自定义页面。
  • 在过渡的最后,通过在 SizedBox 中裹挟目标图像来改变图像大小。
  • 通过在布局 widget 中放置目标图像来改变图像位置。

PhotoHero 类

自定义的 PhotoHero 类保留了 hero 以及其大小,图像,和点击时的动作。

import 'package:flutter/material.dart';

class PhotoHero extends StatelessWidget {
  const PhotoHero({
    super.key,
    required this.width,
    this.height,
    required this.imageUrl,
    this.onTap,
  });
  final double width;
  final double? height;
  final String imageUrl;
  final VoidCallback? onTap;
  @override
  Widget build(BuildContext context) {
    return Container(
      width: width,
      height: height,
      color: Colors.transparent,
      child: Hero(
        tag: imageUrl,
        child: Material(// Provides a canvas for the InkWell's splash during Hero's flight. Otherwise, the splash would be left behind.
          child: InkWell(
            onTap: onTap,
            child: Image.asset(
              imageUrl,
              fit: BoxFit.cover,
            ),
          ),
        ),
      ),
    );
  }
}

hero_page.dart

import 'package:flutter/material.dart';

import '../components/photo_hero.dart';

class HeroPage extends StatelessWidget {
  const HeroPage({super.key});
  static const url =
      'images/products/O1CN01yvMCj11GkmaVukfRK_!!2652950661.jpg_Q75.jpg';
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('HeroPage'),
      ),
      body: Center(
        child: PhotoHero(
          width: 60,
          height: 60,
          imageUrl: url,
          onTap: () {
            Navigator.pushNamed(context, '/herophoto', arguments: url);
          },
        ),
      ),
    );
  }
}

import 'package:flutter/material.dart';

import '../components/photo_hero.dart';

class HeroPhoto extends StatelessWidget {
  final String url;

  const HeroPhoto({super.key, required this.url});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('HeroPhoto'),
      ),
      body: Container(
        color: Colors.transparent,
        padding: const EdgeInsets.all(16),
        alignment: Alignment.center,
        child: PhotoHero(
          width: 300,
          height: 300,
          imageUrl: url,
          onTap: () {
            Navigator.pop(context);
          },
        ),
      ),
    );
  }
}