Flutter Hero 动画路径覆盖

问题描述 投票:0回答:1

我正在构建一个带有

Hero
动画的应用程序,可以从主屏幕转换到第二个屏幕。这是我的代码的简化版本:

// A single tile, placed in a grid
class BeverageTile extends StatelessWidget {
  final Beverage beverage;

  const BeverageTile({
    super.key,
    required this.beverage,
  });

  @override
  Widget build(BuildContext context) {
    final String price = beverage.price == null ? 'Free' : '\$${beverage.price}';

    return GestureDetector(
      onTap: () => Navigator.of(context).push(
        FadePageRoute(
          page: BeverageSelectionScreen(beverage: beverage),
        ),
      ),
      child: Container(
        decoration: BoxDecoration(
          color: Colors.white.withOpacity(0.1),
          borderRadius: BorderRadius.circular(16.0),
        ),
        child: Column(
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                Container(
                  decoration: BoxDecoration(
                    color: Colors.white.withOpacity(0.2),
                    borderRadius: const BorderRadius.only(
                      bottomLeft: Radius.circular(16.0),
                      topRight: Radius.circular(16.0),
                    ),
                  ),
                  child: Padding(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 12.0,
                      vertical: 6.0,
                    ),
                    child: Text(
                      price,
                      style: const TextStyle(
                        fontSize: 14.0,
                        fontWeight: FontWeight.w700,
                      ),
                    ),
                  ),
                ),
              ],
            ),
            Expanded(
              child: Padding(
                padding: const EdgeInsets.only(
                  top: 16.0,
                  left: 16.0,
                  right: 16.0,
                ),
                // SETUP OF THE HERO
                child: Hero(
                  createRectTween: (begin, end) {
                    return RectTween(begin: begin, end: end);
                  },
                  tag: 'beverage_${beverage.name}',
                  child: SvgPicture.asset(
                    beverage.imagePath,
                    fit: BoxFit.contain,
                  ),
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.symmetric(
                vertical: 16.0,
                horizontal: 8.0,
              ),
              child: Text(
                beverage.name,
                textAlign: TextAlign.center,
                style: const TextStyle(fontSize: 16.0),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// Second screen
class BeverageSelectionScreen extends StatelessWidget {
  final Beverage beverage;

  const BeverageSelectionScreen({super.key, required this.beverage});

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: const BoxDecoration(
        color: Color(0xFF1a1718),
      ),
      child: Padding(
        padding: const EdgeInsets.all(72.0),
        child: Hero(
          tag: 'beverage_${beverage.name}',
          child: SvgPicture.asset(
            beverage.imagePath,
            fit: BoxFit.contain,
          ),
        ),
      ),
    );
  }
}

我还实现了一个自定义 PageRouteBuilder 来处理页面之间的过渡动画。 Hero 动画按预期工作,但图像当前遵循弯曲路径,这是根据 Material Design 运动规范的默认行为。

问题:如何自定义英雄动画的路径,让图像沿着直线路径移动,而不是默认的曲线轨迹?

flutter user-interface animation
1个回答
0
投票

默认情况下,

Hero
使用
MaterialRectArcTween
,它会创建弯曲的动画路径。要使英雄遵循线性路径,请在
RectTween
属性中使用
createRectTween

createRectTween: (begin, end) => RectTween(begin: begin, end: end),

重要:
确保覆盖两个

createRectTween
小部件(两个页面上)中的
Hero
,以确保反向动画也是线性的。

Example GIF

完整示例:

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

void main() {
  // Slow down the animation to better see the Hero flight.
  timeDilation = 15.0;

  runApp(
    const MaterialApp(
      home: HeroLinearPathExample(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Linear Hero Animation Example'),
      ),
      body: Align(
        alignment: Alignment.bottomLeft,
        child: GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (_) => const SecondPage(),
              ),
            );
          },
          child: Hero(
            tag: 'hero-tag',
            createRectTween: (begin, end) {
              // Enforce a linear animation
              return RectTween(begin: begin, end: end);
            },
            child: const FlutterLogo(size: 100),
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Second Page'),
      ),
      body: Align(
        alignment: Alignment.topRight,
        child: Hero(
          tag: 'hero-tag',
          createRectTween: (begin, end) {
            // Enforce a linear animation for the reverse transition
            return RectTween(begin: begin, end: end);
          },
          child: const FlutterLogo(size: 100),
        ),
      ),
    );
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.