Flutter英雄动画容器与条件小部件

3

我正在尝试实现一个英雄过渡效果,目前已经很顺利,但是我要转换的容器有两个变体(small/big)。

Big:

big container

Small:

small container

可以看到,小版本与大版本相同,只是缺少一些元素。需要呈现的版本由属性isSmall设置。

该组件的外观如下:

class TicPackage extends StatelessWidget {
  TicPackage({this.package, this.onTap, this.isSmall = false});

  final Package package;
  final bool isSmall;
  final Function() onTap;

  final NumberFormat currencyFormatter =
      NumberFormat.currency(locale: "nl", decimalDigits: 2, symbol: "€");

  @override
  Widget build(BuildContext context) {
    Widget titleText = Text(
      package.name,
      style: TextStyle(
          color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold),
    );

    return TicCard(
      color: package.color,
      elevation: 4,
      onTap: onTap,
      children: <Widget>[
        Row(
          children: <Widget>[
            isSmall
                ? titleText
                : Text("${package.eventCount} evenementen",
                    style:
                        TextStyle(color: Color.fromRGBO(255, 255, 255, 0.5))),
            Text(
              "${currencyFormatter.format(package.price)}",
              style: TextStyle(
                  color: Colors.white,
                  fontSize: 22,
                  fontWeight: FontWeight.bold),
            ),
          ],
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
        ),
        if (!isSmall)
          Padding(padding: EdgeInsets.only(top: 10), child: titleText),
        Padding(
            padding: EdgeInsets.only(top: 2),
            child: Text(package.description,
                style: TextStyle(color: Colors.white))),
        if (!isSmall)
          Padding(
              padding: EdgeInsets.only(top: 12),
              child: Text(package.goods,
                  style: TextStyle(
                      color: Colors.white, fontStyle: FontStyle.italic))),
        if (!isSmall)
          Padding(
              padding: EdgeInsets.only(top: 10),
              child: Container(
                child: Padding(
                    padding: EdgeInsets.symmetric(horizontal: 10, vertical: 3),
                    child: Text(
                      "${currencyFormatter.format(package.discount)} korting",
                      style: TextStyle(color: Colors.white),
                    )),
                decoration: BoxDecoration(
                    border:
                        Border.all(color: Color.fromRGBO(255, 255, 255, 0.5)),
                    borderRadius: BorderRadius.circular(100)),
              ))
      ],
    );
  }
}

屏幕 A:

Hero(
    tag: "package_${args.package.id}",
    child: TicPackage(
      isSmall: false,
      package: args.package
)))

屏幕B:

Hero(
    tag: "package_${args.package.id}",
    child: TicPackage(
      isSmall: true,
      package: args.package
)))

现在转换的效果如下所示:

hero transition

您可以看到它的运行效果相当不错,但由于我在这里使用了条件渲染,因此它有点卡顿。而且回退转换会出现错误:

A RenderFlex overflowed by 96 pixels on the bottom.

我想这是因为在返回的路上,由于渲染了额外的小部件,空间突然溢出了。
现在我的问题是如何正确地创建一个需要与条件元素过渡的英雄组件。或者如果一个名叫“hero”的小部件不适用于此,如何通过进行一些自定义动画来实现相同的结果?
2个回答

2

将你的列包裹在SingleChildScrollView中,再使用TicCard进行包装

enter image description here

导入 'package:flutter/material.dart' 包

import 'page2.dart';

class TicCard extends StatelessWidget {
  final List<Widget> children;
  final double elevation;
  final Color color;

  const TicCard({
    Key key,
    this.children,
    this.elevation,
    this.color,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => Navigator.of(context).push(
        MaterialPageRoute(
          builder: (_) => Page2(),
        ),
      ),
      child: Card(
        elevation: elevation,
        color: color,
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: SingleChildScrollView(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: children,
            ),
          ),
        ),
      ),
    );
  }
}

确实解决了问题!但是仍然不够顺畅,因为一些小部件仍然会立即消失。正在寻找解决方案 :) - toonvanstrijp
1
你是对的,flightShuttleBuilder 提供更多的自定义解决方案,而SingleChildScrollView则更像是hacky。 - Kherel

2

利用flightShuttleBuilder。在此生成器中创建一个新的TicCard,并使用hero动画。现在您可以使用此animation来动画化flight(屏幕转换)期间的所有视图。

有一件事我不太舒服,那就是_animationWidget。它的作用是:如��没有animation并且isSmall为true,则将所有小部件包装在FadeTransitionSizeTransition中,并返回一个空的Container

小部件:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart';
import 'package:ticketapp_pakket/components/tic-card.dart';
import 'package:ticketapp_pakket/models/package.dart';

class TicPackage extends StatelessWidget {
  TicPackage(
      {this.heroTag,
      this.package,
      this.onTap,
      this.isSmall = false,
      this.animation});

  final String heroTag;
  final Animation<double> animation;
  final Package package;
  final bool isSmall;
  final Function() onTap;

  final NumberFormat currencyFormatter =
      NumberFormat.currency(locale: "nl", decimalDigits: 2, symbol: "€");

  Widget _animationWidget({Widget child}) {
    return animation != null
        ? FadeTransition(
            opacity: animation,
            child: SizeTransition(
                axisAlignment: 1.0, sizeFactor: animation, child: child))
        : !isSmall ? child : Container();
  }

  @override
  Widget build(BuildContext context) {
    Widget eventCountText = _animationWidget(
        child: Padding(
            padding: EdgeInsets.only(bottom: 10),
            child: Text("${package.eventCount} evenementen",
                style: TextStyle(color: Color.fromRGBO(255, 255, 255, 0.5)))));

    Widget goodsText = _animationWidget(
      child: Padding(
          padding: EdgeInsets.only(top: 12),
          child: Text(package.goods,
              style:
                  TextStyle(color: Colors.white, fontStyle: FontStyle.italic))),
    );

    Widget discountText = _animationWidget(
        child: Padding(
            padding: EdgeInsets.only(top: 10),
            child: Container(
              child: Padding(
                  padding: EdgeInsets.symmetric(horizontal: 10, vertical: 3),
                  child: Text(
                    "${currencyFormatter.format(package.discount)} korting",
                    style: TextStyle(color: Colors.white),
                  )),
              decoration: BoxDecoration(
                  border: Border.all(color: Color.fromRGBO(255, 255, 255, 0.5)),
                  borderRadius: BorderRadius.circular(100)),
            )));

    Widget titleText = Text(
      package.name,
      style: TextStyle(
          color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold),
    );

    Widget card = TicCard(
        color: package.color,
        borderRadius: BorderRadius.circular(10),
        margin: EdgeInsets.only(left: 20, right: 20, bottom: 10, top: 5),
        onTap: onTap,
        child: Container(
          padding: EdgeInsets.all(15),
          child: Stack(
            children: <Widget>[
              Column(
                mainAxisAlignment: MainAxisAlignment.start,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  eventCountText,
                  titleText,
                  Padding(
                      padding: EdgeInsets.only(top: 2),
                      child: Text(package.description,
                          style: TextStyle(color: Colors.white))),
                  goodsText,
                  discountText,
                ],
              ),
              Positioned(
                  child: Text(
                    "${currencyFormatter.format(package.price)}",
                    style: TextStyle(
                        color: Colors.white,
                        fontSize: 22,
                        fontWeight: FontWeight.bold),
                  ),
                  top: 0,
                  right: 0)
            ],
          ),
        ));

    if (heroTag == null) {
      return card;
    }

    return Hero(
        tag: heroTag,
        flightShuttleBuilder: (
          BuildContext flightContext,
          Animation<double> animation,
          HeroFlightDirection flightDirection,
          BuildContext fromHeroContext,
          BuildContext toHeroContext,
        ) {
          return TicPackage(
            package: package,
            animation: ReverseAnimation(animation),
          );
        },
        child: card);
  }
}

如何使用小部件:

在两个屏幕上都使用TicPackage小部件,并使用相同的heroTag

TicPackage(
  heroTag: "package_1",
  package: package,
  onTap: () {
    Navigator.pushNamed(context, '/package-detail',
      arguments: PackageDetailPageArguments(package: package));
  })

结果:

结果

减速后的结果:

减速后的结果


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接