TEXTBOOK SECTION / AI LEARNING

状態に応じて表示を切り替える

Flutterアプリケーション開発概論の「3Dモデルと描画」より、状態に応じて表示を切り替えるを解説。生成AI、AI活用、DX、業務改善を実践しながら学べるオンライン教材です。

153Dモデルと描画Flutter / iOS / Android / MacOS / Windows / 基礎から学ぶ / 開発 / アプリ開発

OVERVIEW

この節で学べること

概要を表示する
項目内容
教材名Flutterアプリケーション開発概論
3Dモデルと描画
状態に応じて表示を切り替える
カテゴリFlutter / iOS / Android / MacOS / Windows / 基礎から学ぶ / 開発 / アプリ開発
学習内容生成AI、AI活用、DX、業務改善を実践しながら理解するための教材です。

TABLE OF CONTENTS

目次

CONTENT

ここから

この章では、これまで個別に記述してきた読み込み中、表示完了、エラー時のUIを一つのメソッドへ整理します。

状態ごとの条件分岐がbuild()内へ増え続けると、画面全体の構造が読みにくくなります。そこで、ViewerStatusを受け取り、表示すべきWidgetを返す処理へ分離します。

15.1 現在の表示処理を確認する

これまでのStackでは、状態ごとに複数のifを記述していました。

if (_status == ViewerStatus.loading)
  _buildLoadingOverlay(),

if (_status == ViewerStatus.error)
  _buildErrorOverlay(),

if (_status == ViewerStatus.ready)
  _buildGestureGuide(),

この方法でも動作しますが、状態が増えるほど条件分岐も増えます。

また、loadingreadyerrorは同時に成立しないため、「現在の状態に対応するWidgetを一つ返す」という形にまとめられます。

15.2 状態別Widgetを返すメソッドを作る

状態に応じた表示を返すメソッドを追加します。

/// 現在のビューアー状態に対応する表示を構築します。
///
/// 入力: なし
/// 出力: 読み込み状態に対応する[Widget]
Widget _buildStatusOverlay() {
  switch (_status) {
    case ViewerStatus.loading:
      return _buildLoadingOverlay();

    case ViewerStatus.ready:
      return _buildGestureGuide();

    case ViewerStatus.error:
      return _buildErrorOverlay();
  }
}

このメソッドは、現在の_statusを確認し、対応するWidgetを一つ返します。

状態判定が一か所へ集約されるため、どの状態で何が表示されるのか把握しやすくなります。

15.3 switch文を理解する

switch文は、一つの値を複数の候補と比較するときに使用します。

switch (_status) {
  case ViewerStatus.loading:
    return _buildLoadingOverlay();

  case ViewerStatus.ready:
    return _buildGestureGuide();

  case ViewerStatus.error:
    return _buildErrorOverlay();
}

_statusViewerStatus.loadingなら、読み込み表示を返します。

ViewerStatus.readyなら操作案内、ViewerStatus.errorならエラー画面を返します。

ViewerStatusに定義されているすべての値を処理しているため、defaultは必要ありません。

15.4 switch式で簡潔に記述する

Dartでは、switchを式として記述できます。

/// 現在のビューアー状態に対応する表示を構築します。
///
/// 入力: なし
/// 出力: 読み込み状態に対応する[Widget]
Widget _buildStatusOverlay() {
  return switch (_status) {
    ViewerStatus.loading => _buildLoadingOverlay(),
    ViewerStatus.ready => _buildGestureGuide(),
    ViewerStatus.error => _buildErrorOverlay(),
  };
}

=>の右側には、各状態で返す値を記述します。

今回のように、状態ごとに一つのWidgetを返す場合は、通常のswitch文よりも簡潔に表現できます。

教材では、処理の流れを理解しやすい方を選んで構いません。完成コードでは、短く読みやすいswitch式を使用します。

15.5 操作案内をメソッドへ分離する

これまでStack内へ直接書いていた操作案内も、専用メソッドへ分けます。

/// 3Dモデルの操作方法を示す案内を構築します。
///
/// 入力: なし
/// 出力: 画面下部へ配置する操作案内[Widget]
Widget _buildGestureGuide() {
  return 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,
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    ),
  );
}

これにより、build()内から長い操作案内のコードを取り除けます。

15.6 Stack内の表示を整理する

状態別の複数のifを削除し、_buildStatusOverlay()だけを配置します。

Stack(
  fit: StackFit.expand,
  children: [
    Flutter3DViewer(
      key: _viewerKey,
      controller: _controller,
      src: ModelSource.modelUrl,
      enableTouch: true,
      activeGestureInterceptor: true,
      progressBarColor: const Color(0xFF151515),
      onProgress: (double progressValue) {
        debugPrint('Loading progress: $progressValue');
      },
      onLoad: _handleModelLoaded,
      onError: _handleModelError,
    ),
    _buildStatusOverlay(),
  ],
)

Stackの構造は、次の2層に整理されました。

奥側:Flutter3DViewer
手前:現在の状態に対応する表示

どの状態でも手前に一つのWidgetが返されるため、表示条件をStack側で意識する必要がありません。

15.7 完了時に空Widgetを返す方法

完成版の設計によっては、読み込み完了後に操作案内を常時表示しない場合もあります。その場合は、readyのときに空のWidgetを返します。

Widget _buildStatusOverlay() {
  return switch (_status) {
    ViewerStatus.loading => _buildLoadingOverlay(),
    ViewerStatus.ready => const SizedBox.shrink(),
    ViewerStatus.error => _buildErrorOverlay(),
  };
}

SizedBox.shrink()は、幅と高さをほとんど持たない空のWidgetです。

nullを返すことはできないため、「何も表示しない」状態をWidgetとして表すときに使用します。

今回の教材では、操作方法を伝えるため、ready時には引き続き操作案内を表示します。

15.8 表示処理と状態変更処理を分ける

状態管理では、次の2種類の処理を分けることが重要です。

状態を変更する処理:

void _handleModelLoaded() {
  if (!mounted) {
    return;
  }

  setState(() {
    _status = ViewerStatus.ready;
    _errorMessage = '';
  });
}

状態から表示を決める処理:

Widget _buildStatusOverlay() {
  return switch (_status) {
    ViewerStatus.loading => _buildLoadingOverlay(),
    ViewerStatus.ready => _buildGestureGuide(),
    ViewerStatus.error => _buildErrorOverlay(),
  };
}

_handleModelLoaded()は、イベントを受けて状態を更新します。

_buildStatusOverlay()は、現在の状態を読み取り、表示するWidgetを決めます。

この二つを分離すると、状態変更の原因と表示結果を個別に確認できます。

15.9 この章で整理するコード

_ModelViewerScreenStateへ、操作案内と状態表示のメソッドを追加します。

/// 3Dモデルの操作方法を示す案内を構築します。
///
/// 入力: なし
/// 出力: 画面下部へ配置する操作案内[Widget]
Widget _buildGestureGuide() {
  return 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,
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    ),
  );
}

/// 現在のビューアー状態に対応する表示を構築します。
///
/// 入力: なし
/// 出力: 読み込み状態に対応する[Widget]
Widget _buildStatusOverlay() {
  return switch (_status) {
    ViewerStatus.loading => _buildLoadingOverlay(),
    ViewerStatus.ready => _buildGestureGuide(),
    ViewerStatus.error => _buildErrorOverlay(),
  };
}

Stackは次のように簡潔になります。

Stack(
  fit: StackFit.expand,
  children: [
    Flutter3DViewer(
      key: _viewerKey,
      controller: _controller,
      src: ModelSource.modelUrl,
      enableTouch: true,
      activeGestureInterceptor: true,
      progressBarColor: const Color(0xFF151515),
      onProgress: (double progressValue) {
        debugPrint('Loading progress: $progressValue');
      },
      onLoad: _handleModelLoaded,
      onError: _handleModelError,
    ),
    _buildStatusOverlay(),
  ],
)

15.10 build()を読みやすく保つ

build()には、画面全体の構造が分かる程度の記述を残します。

詳細なローディングUI、エラーUI、操作案内は、それぞれ専用メソッドへ移します。

理想的には、build()を読んだときに次の構造がすぐ分かる状態を目指します。

Scaffold
└── SafeArea
    └── Padding
        └── Column
            ├── タイトル
            ├── 3D表示領域
            └── クレジット

細かな装飾をメソッドへ分離しても、分離しすぎると処理を追いにくくなります。画面上で一つの意味を持つまとまりを基準に分けます。

15.11 この章の到達目標

この章の終了時点で、次の内容を確認します。

  • 状態ごとの表示を一つのメソッドへ集約できる
  • switch文とswitch式の違いを説明できる
  • enumのすべての状態を処理できる
  • SizedBox.shrink()で何も表示しない状態を表せる
  • 状態変更処理と表示生成処理を分離できる
  • 長いWidgetを役割ごとのメソッドへ分けられる
  • build()から画面全体の構造を読み取れる 次章では、Flutter3DControllerを使ってカメラを初期位置へ戻すボタンを実装します。

FAQ

よくある質問

状態に応じて表示を切り替えるは医療関係者向けだけの内容ですか。
医療分野の例が含まれる場合もありますが、医療関係者だけに限定した内容ではありません。生成AI、AI活用、DX、業務改善、プロトタイプ開発など、一般的なAI学習の事例として読める内容です。
AI初心者でも読めますか。
はい。AIをこれから学ぶ方、数学が苦手な方、仕事でAIを使いたい方にも読み進めやすいように、教材の章と節の流れに沿って整理しています。
サムネイル画像は必ず表示されますか。
はい。教材にcoverUrlが設定されている場合はその画像を表示し、未設定の場合は代替サムネイル画像を表示します。
Flutterアプリケーション開発概論のほかの章も読めますか。
はい。教材トップから章立てを確認でき、前後の節へもページ下部のナビゲーションから移動できます。