我正在尝试实现顺畅的英雄交易,但是我要转换的容器有两个变体(小/大)。
大:
小:
您可以看到,小版本与大版本相同,但是缺少一些元素。使用属性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
)))
现在过渡看起来如下:
您可以看到它运行良好,但是由于我在这里使用条件渲染,因此有点活泼。同样,向后过渡也会产生错误:
A RenderFlex overflowed by 96 pixels on the bottom.
我猜这是因为在返回的途中,空间突然溢出,因为这些多余的小部件正在被渲染。
现在,我的问题是如何正确创建需要使用条件元素进行转换的英雄组件。或者,如果hero
小部件不适合此操作,我如何通过做一些自定义动画来达到相同的结果?
用SingleChildScrollView将列包裹在TicCard中
导入'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,
),
),
),
),
);
}
}
使用flightShuttleBuilder
。在此构建器中,创建一个采用TicCard
动画的新hero
。您现在可以使用此animation
在flight
(屏幕过渡)期间为所有视图设置动画。
我不满意的一件事是_animationWidget
。它的作用:将所有小部件包装在FadeTransition
和SizeTransition
内,如果没有animation
且isSmall
为true,则返回空的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);
}
}
结果:
导致慢动作: