AnimatedCrossFadeで切り替える画像の全面表示がうまくいかない問題の解決方法
AnimatedCrossFadeの画像を全面に引き伸ばして表示することができるようにする方法
はじめに
Flutterのアプリのトップ画面において、横長の画像を以下のコードで画面いっぱいに表示するようにしています。
//top_page.dart
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
body: Stack(
children: [
SizedBox(
height: double.infinity,
child: Image.asset(
'images/top_background.jpg',
fit: BoxFit.fitHeight,
),
),
...
],
),
),
}
fitHeightを指定して、アスペクト比を変えずに画面いっぱいに収まるように表示しています。
この形を維持したまま、2つの画像をクロスフェードで自動で交互に表示し続けるようなリッチが表示にしたくなりました。
そこで、標準のウィジェットとして使うことができる「AnimatedCrossFade」を使って対応することにしました。
タイマーで一定時間で定期的に切り替わる仕組みを作らなければならないものの、表示自体は、以下のコードのとおり、firstChildとsecondChildに表示したいウィジェットを設定すればよいだけです。
そして右の動画のとおりの画像の切り替わりが実現すると思っていました。
//top_page.dart
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
body: Stack(
children: [
SizedBox(
height: double.infinity,
child: AnimatedCrossFade(
firstChild: SizedBox(
height: double.infinity,
child: Image.asset(
'images/top_background01.jpg',
fit: BoxFit.fitHeight,
),
),
secondChild: SizedBox(
height: double.infinity,
child: Image.asset(
'images/top_background02.jpg',
fit: BoxFit.fitHeight,
),
),
duration: const Duration(milliseconds: 1500),
crossFadeState: ref.watch(topPageProvider).isFirstImage
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
),
),
...
],
),
);
}
しかしながら、Runコンソールに以下のようなエラー表示が出てしまいました。
======== Exception caught by rendering library =====================================================
The following assertion was thrown during performLayout():
BoxConstraints forces an infinite height.
These invalid constraints were provided to RenderSemanticsAnnotations's layout() function by the following function, which probably computed the invalid constraints in question:
RenderConstrainedBox.performLayout (package:flutter/src/rendering/proxy_box.dart:291:14)
The offending constraints were: BoxConstraints(w=393.0, h=Infinity)
The relevant error-causing widget was:
SizedBox SizedBox:file:///Users/.../lib/screen/10_top/top_page.dart:41:27
When the exception was thrown, this was the stack:
#0 BoxConstraints.debugAssertIsValid.<anonymous closure>.throwError (package:flutter/src/rendering/box.dart:526:9)
...
#41 _drawFrame (dart:ui/hooks.dart:283:31)
The following RenderObject was being processed when the exception was fired: RenderConstrainedBox#1c82d relayoutBoundary=up9
... parentData: <none> (can use size)
... constraints: BoxConstraints(w=393.0, 0.0<=h<=Infinity)
... size: Size(393.0, 852.0)
... additionalConstraints: BoxConstraints(0.0<=w<=Infinity, h=Infinity)
RenderObject: RenderConstrainedBox#1c82d relayoutBoundary=up9
parentData: <none> (can use size)
constraints: BoxConstraints(w=393.0, 0.0<=h<=Infinity)
size: Size(393.0, 852.0)
additionalConstraints: BoxConstraints(0.0<=w<=Infinity, h=Infinity)
... child: RenderSemanticsAnnotations#27f6b relayoutBoundary=up10
... parentData: <none> (can use size)
... constraints: BoxConstraints(0.0<=w<=393.0, h=852.0)
... semantics node: SemanticsNode#12
... Rect.fromLTRB(0.0, 0.0, 393.0, 852.0)
... flags: isImage
... textDirection: ltr
... size: Size(393.0, 852.0)
... child: RenderImage#8ccc5 relayoutBoundary=up11
... parentData: <none> (can use size)
... constraints: BoxConstraints(0.0<=w<=393.0, h=852.0)
... size: Size(393.0, 852.0)
... image: [1024×768]
... fit: fitHeight
... alignment: Alignment.center
... invertColors: false
... filterQuality: medium
====================================================================================================
どうも、AnimatedCrossFadeのfirstChildとsecondChildに設定している SizedBox(height: double.infinity) の指定が適さないようです。
AnimatedCrossFadeを介したことで、そのままダイナミックに指定することができなくなってしまいました。
もちろん、この場合全画面を想定しているので、SizedBox(height: double.infinity) の部分を SizedBox(
height: MediaQuery.of(context).size.height) にしてしまう手はあるのですが、上下にウィジェットを配置しているなどで全画面でない場合には崩れてしまいます。
そしてなるべくダイナミックに変動する形が望ましいです。
Expandedを使ったり試行錯誤しますが、同様のエラーが出てしまったり、右の画面のように全面表示でなくなったりしてしまいます。
対処方法
AnimatedCrossFadeのfirstChildとsecondChildに設定していたSizedBoxの代わりに、Container(decoration: BoxDecoration) を使用することで解決することができました。
つまりAnimatedCrossFadeの親のSizedBox(height: double.infinity)が、AnimatedCrossFadeのfirstChildとsecondChildのContainerまでは効いているので、そのContainerの装飾として背景画像を指定してあげればよいということみたいです。
実際のコードは以下のとおりです。
//top_page.dart
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
body: Stack(
children: [
//AnimatedCrossFadeを使用して2つの画像が1.5秒で自動的に切り替わる
SizedBox(
height: double.infinity,
child: AnimatedCrossFade(
duration: const Duration(milliseconds: 1500),
firstChild: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/top_background01.jpg'),
fit: BoxFit.cover,
),
),
),
secondChild: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/top_background02.jpg'),
fit: BoxFit.cover,
),
),
),
crossFadeState: ref.watch(topPageProvider).isFirstImage
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
),
),
...
],
),
);
}
これで無事イメージどおりのクロスフェードで2つの画像を自動ループで切り替えるリッチなUIができました。
AnimatedCrossFadeでなくとも、今回のようなSizedBox(height: double.infinity) の指定によるレンダリングのエラーは発生しがちなので、同様に解決できるかもしれません。
AnimatedCrossFadeの使い方について詳しく解説した記事はこちらです。
また、詳しい説明は以下の公式動画にもあります。