CONTENT
ここから
この章では、3Dモデルの読み込み中に、進行中であることを利用者へ伝える表示を追加します。
3Dモデルは、通信速度やファイル容量によって表示までに時間がかかることがあります。何も表示されない状態が続くと、利用者はアプリが停止しているのか、処理が進んでいるのか判断できません。
そこで、ViewerStatus.loadingの間だけ、3Dビューアーの上へローディング表示を重ねます。
13.1 読み込み表示が必要な理由
非同期処理では、処理の開始と完了の間に待ち時間が発生します。
この待ち時間に何も案内がないと、利用者が次のように感じる可能性があります。
- 画面が正しく開いていない
- タッチ操作が反応していない
- 3Dモデルの読み込みに失敗した
- アプリが停止している
読み込み表示は、処理が進行中であることを伝えるためのフィードバックです。処理時間を短くする機能ではありませんが、利用者の不安や誤操作を減らせます。
13.2 CircularProgressIndicatorを表示する
Flutterには、読み込み中を表すCircularProgressIndicatorが用意されています。
const CircularProgressIndicator()
進捗率を指定しない場合は、処理が継続中であることを示す円形アニメーションになります。
今回は、モデルの正確な読み込み率を画面へ表示せず、読み込み中であることだけを伝えます。
色と線の太さも指定できます。
const CircularProgressIndicator(
strokeWidth: 2.5,
color: Color(0xFF151515),
)
strokeWidthは円形インジケーターの線の太さです。
13.3 説明文を横に配置する
インジケーターだけでは何を読み込んでいるのか分かりにくいため、説明文を追加します。
const Row(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
strokeWidth: 2.5,
color: Color(0xFF151515),
),
SizedBox(width: 12),
Text(
'3Dモデルを読み込んでいます',
style: TextStyle(
fontWeight: FontWeight.w500,
),
),
],
)
mainAxisSizeへMainAxisSize.minを指定すると、Rowは子Widgetに必要な幅だけを使用します。
SizedBoxは、インジケーターと文字の間に余白を作ります。
13.4 半透明の背景を追加する
読み込み表示を3Dビューアー上へ直接置くと、モデルや背景と重なって読みにくくなることがあります。
そこで、ローディング表示を背景付きの領域で囲みます。
DecoratedBox(
decoration: BoxDecoration(
color: const Color(0xEFFFFFFF),
borderRadius: BorderRadius.circular(16),
),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 18,
vertical: 14,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
strokeWidth: 2.5,
color: Color(0xFF151515),
),
SizedBox(width: 12),
Text('3Dモデルを読み込んでいます'),
],
),
),
)
Color(0xEFFFFFFF)は、わずかに透過した白です。背後の表示を完全には隠さず、文字の読みやすさを確保します。
13.5 ローディング用Widgetをメソッドへ分ける
build()を読みやすく保つため、読み込み表示を返すメソッドへ分離します。
/// 3Dモデル読み込み中の案内を構築します。
///
/// 入力: なし
/// 出力: ローディング表示を含む[Widget]
Widget _buildLoadingOverlay() {
return Center(
child: DecoratedBox(
decoration: BoxDecoration(
color: const Color(0xEFFFFFFF),
borderRadius: BorderRadius.circular(16),
),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 18,
vertical: 14,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(
strokeWidth: 2.5,
color: Color(0xFF151515),
),
),
SizedBox(width: 12),
Text(
'3Dモデルを読み込んでいます',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
);
}
SizedBoxでインジケーターの大きさを固定すると、表示が大きくなりすぎるのを防げます。
13.6 Stackの中へ読み込み表示を追加する
第9章で作成したStackへ、ローディング表示を追加します。
Stack(
fit: StackFit.expand,
children: [
Flutter3DViewer(
controller: _controller,
src: ModelSource.modelUrl,
enableTouch: true,
activeGestureInterceptor: true,
progressBarColor: const Color(0xFF151515),
onLoad: _handleModelLoaded,
onError: _handleModelError,
),
if (_status == ViewerStatus.loading)
_buildLoadingOverlay(),
const Positioned(
left: 16,
right: 16,
bottom: 16,
child: IgnorePointer(
child: Text('操作案内'),
),
),
],
)
Dartのコレクション内ifを使用すると、条件を満たす場合だけWidgetをchildrenへ追加できます。
if (_status == ViewerStatus.loading)
状態がloadingの間だけローディング表示が追加され、readyやerrorへ変わると自動的に取り除かれます。
13.7 操作案内との重なりを考える
現在のStackには、ローディング表示と操作案内の両方があります。
読み込み中はまだ3Dモデルを操作できないため、操作案内を表示し続ける必要はありません。そこで、操作案内もreadyのときだけ表示するように変更します。
if (_status == ViewerStatus.ready)
const Positioned(
left: 16,
right: 16,
bottom: 16,
child: IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
color: Color(0xD9151515),
borderRadius: BorderRadius.all(
Radius.circular(16),
),
),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.swipe,
color: Colors.white,
size: 20,
),
SizedBox(width: 10),
Flexible(
child: Text(
'1本指で回転・2本指で拡大縮小',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
),
),
),
これにより、状態ごとに必要な案内だけが表示されます。
13.8 この章で更新する主要部分
_ModelViewerScreenStateへ、ローディング表示のメソッドを追加します。
/// 3Dモデル読み込み中の案内を構築します。
///
/// 入力: なし
/// 出力: ローディング表示を含む[Widget]
Widget _buildLoadingOverlay() {
return Center(
child: DecoratedBox(
decoration: BoxDecoration(
color: const Color(0xEFFFFFFF),
borderRadius: BorderRadius.circular(16),
),
child: const Padding(
padding: EdgeInsets.symmetric(
horizontal: 18,
vertical: 14,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(
strokeWidth: 2.5,
color: Color(0xFF151515),
),
),
SizedBox(width: 12),
Flexible(
child: Text(
'3Dモデルを読み込んでいます',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
),
);
}
Stackは次のように更新します。
Stack(
fit: StackFit.expand,
children: [
Flutter3DViewer(
controller: _controller,
src: ModelSource.modelUrl,
enableTouch: true,
activeGestureInterceptor: true,
progressBarColor: const Color(0xFF151515),
onProgress: (double progressValue) {
debugPrint('Loading progress: $progressValue');
},
onLoad: _handleModelLoaded,
onError: _handleModelError,
),
if (_status == ViewerStatus.loading)
_buildLoadingOverlay(),
if (_status == ViewerStatus.ready)
const Positioned(
left: 16,
right: 16,
bottom: 16,
child: IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
color: Color(0xD9151515),
borderRadius: BorderRadius.all(
Radius.circular(16),
),
),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.swipe,
color: Colors.white,
size: 20,
),
SizedBox(width: 10),
Flexible(
child: Text(
'1本指で回転・2本指で拡大縮小',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
),
),
),
],
)
13.9 動作確認
アプリを完全に停止してから再起動し、次の順序で表示が変化することを確認します。
画面を開く
↓
読み込み表示が現れる
↓
3Dモデルの読み込みが完了する
↓
読み込み表示が消える
↓
操作案内が表示される
モデルが端末や内部処理によってキャッシュされている場合、読み込み表示が短時間しか見えないことがあります。その場合でも、状態がloadingからreadyへ切り替わっていれば正常です。
13.10 この章の到達目標
この章の終了時点で、次の内容を確認します。
- 読み込み表示が必要な理由を説明できる
CircularProgressIndicatorを配置できるStackを使って3Dビューアーの上へ表示を重ねられる- コレクション内
ifでWidgetの表示条件を指定できる loadingの間だけローディング表示を出せるreadyになった後だけ操作案内を表示できる- 状態に応じて不要なWidgetを取り除ける
次章では、3Dモデルの読み込みに失敗した場合のエラー表示と、再読み込みボタンを実装します。