この記事は
FlutterのPackageであるanimationsを使った時に少しハマったことがあったので書きました
経緯
比較的リッチなアニメーションを簡単に実装できるanimationsを試してみたく、exampleを参考に以下のようなコードを書きました。
class MainScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF7F7F8),
appBar: AppBar(
title: const Text('猫ちゃん'),
),
body: ListView(
children: <Widget>[
_OpenContainerWrapper(
openPage: const CatPage(),
closedBuilder: (BuildContext _, VoidCallback openContainer) {
return ReusableCard(
imagePath: 'assets/images/cat.jpg',
contentTitle: '猫ちゃん',
onPress: openContainer,
);
},
),
],
),
);
}
}
class _OpenContainerWrapper extends StatelessWidget {
const _OpenContainerWrapper({
this.closedBuilder,
this.openPage,
});
final OpenContainerBuilder closedBuilder;
final Widget openPage;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
child: OpenContainer(
transitionType: ContainerTransitionType.fade,
openBuilder: (BuildContext context, VoidCallback _) {
return openPage;
},
tappable: false,
closedBuilder: closedBuilder,
),
);
}
}
Itemをタップすることで、アニメーションしながらCatPage()
へ遷移する想定です。
ここで私は「ListView
の子には角丸のCardを使いたいから、ReusableCard
はCard()
を使うべきだろう」と安直に考えて、以下のように書きました。
class ReusableCard extends StatelessWidget {
const ReusableCard({
@required this.imagePath,
@required this.contentTitle,
@required this.onPress,
});
final String imagePath;
final String contentTitle;
final VoidCallback onPress;
@override
Widget build(BuildContext context) {
return Card(
child: InkWell(
onTap: onPress,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Image.asset(
imagePath,
fit: BoxFit.fill,
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 24, 0, 24),
child: Text(
contentTitle,
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
}
}
そしてbuildしてみると、、、
なぜCardが二重に。。。
原因
原因はOpenContainer
のclosedShape
でした。
const OpenContainer({
Key key,
this.closedColor = Colors.white,
this.openColor = Colors.white,
this.closedElevation = 1.0,
this.openElevation = 4.0,
// ↓これ
this.closedShape = const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
this.openShape = const RoundedRectangleBorder(),
@required this.closedBuilder,
@required this.openBuilder,
this.tappable = true,
this.transitionDuration = const Duration(milliseconds: 300),
this.transitionType = ContainerTransitionType.fade,
}) : assert(closedColor != null),
assert(openColor != null),
assert(closedElevation != null),
assert(openElevation != null),
assert(closedShape != null),
assert(openShape != null),
assert(closedBuilder != null),
assert(openBuilder != null),
assert(tappable != null),
assert(transitionType != null),
super(key: key);
closedShapeとは、タップアニメーション前の形(この場合ListのItemの形)を指定するパラメータです。
closedShapeにはデフォルトで角丸4dpのRoundedRectangleBorder
が指定されており、その子にCardを入れていたがために、角丸のものが二重で配置されてしまったという訳でした。
今回の場合は、ReusableCardのCard部分を取り除いてあげれば解決です。
class ReusableCard extends StatelessWidget {
const ReusableCard({
@required this.imagePath,
@required this.contentTitle,
@required this.onPress,
});
final String imagePath;
final String contentTitle;
final VoidCallback onPress;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onPress,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Image.asset(
imagePath,
fit: BoxFit.fill,
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 24, 0, 24),
child: Text(
contentTitle,
style: Theme.of(context).textTheme.bodyText2.copyWith(
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
}
別のshapeにする際は、closedShapeへ明示的に指定してあげましょう。
終わりに
今思えば何故こんな単純な間違いに気づかなかったのかと恥ずかしい気持ちですが、同じようにつまづいた方がいるかもしれないので、その時の助けになれば幸いです。