TEXTBOOK SECTION / AI LEARNING

3D表示領域のUIを整える

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

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

OVERVIEW

この節で学べること

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

TABLE OF CONTENTS

目次

CONTENT

ここから

この章では、3Dモデルの表示領域に角丸、背景色、操作案内を追加します。

3Dモデルの操作機能は前章で完成しましたが、利用者が操作方法を知らなければ、モデルが動かせることに気づかない可能性があります。そこで、3Dビューアーの上に案内を重ね、見た目と使いやすさを整えます。

9.1 ClipRRectで表示領域の角を丸くする

ClipRRectは、子Widgetを角丸の形に切り抜くWidgetです。

ClipRRect(
  borderRadius: BorderRadius.circular(28),
  child: Flutter3DViewer(
    controller: _controller,
    src: ModelSource.modelUrl,
    enableTouch: true,
    activeGestureInterceptor: true,
  ),
)

BorderRadius.circular(28)では、四隅へ半径28の丸みを設定しています。

背景だけを角丸にしても、内側の3Dビューアーが角からはみ出すことがあります。ClipRRectで表示領域全体を囲むことで、3Dビューアーを含むすべての子Widgetが同じ形に切り抜かれます。

9.2 ColoredBoxで背景色を設定する

3Dモデルの読み込み前や、モデルの背景が透明な場合に備えて、表示領域へ背景色を設定します。

ColoredBox(
  color: const Color(0xFFE8E8E5),
  child: Flutter3DViewer(
    controller: _controller,
    src: ModelSource.modelUrl,
    enableTouch: true,
    activeGestureInterceptor: true,
  ),
)

ColoredBoxは、指定した色で子Widgetの背景を塗ります。

今回は画面全体の背景より少し濃いグレーを使い、3Dモデルの表示領域が分かるようにします。

9.3 StackでWidgetを重ねる

操作案内は、3Dビューアーの下ではなく、ビューアー上の下端へ重ねて表示します。このように複数のWidgetを重ねる場合はStackを使用します。

Stack(
  fit: StackFit.expand,
  children: [
    Flutter3DViewer(
      controller: _controller,
      src: ModelSource.modelUrl,
      enableTouch: true,
      activeGestureInterceptor: true,
    ),
    const Text('操作案内'),
  ],
)

childrenの先頭にあるWidgetが奥側に表示され、後に書いたWidgetほど手前に表示されます。

StackFit.expandを指定すると、Stackの子Widgetが利用可能な領域いっぱいに広がります。これにより、3Dビューアーが表示領域全体を使用します。

9.4 Positionedで操作案内の位置を決める

Stackの中では、Positionedを使ってWidgetの位置を指定できます。

const Positioned(
  left: 16,
  right: 16,
  bottom: 16,
  child: Text('1本指で回転・2本指で拡大縮小'),
)

この設定では、操作案内を左右から16、下から16離れた位置へ配置します。

左右の両方を指定すると、操作案内の横幅は表示領域に合わせて自動調整されます。固定幅を指定しないため、画面サイズが異なる端末にも対応しやすくなります。

9.5 操作案内の見た目を整える

操作案内には、暗い半透明の背景、アイコン、説明文を追加します。

const 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,
            ),
          ),
        ),
      ],
    ),
  ),
)

DecoratedBoxは、背景色や角丸などの装飾を加えるWidgetです。

Color(0xD9151515)の先頭にあるD9は不透明度を表します。背景を完全な黒にせず、わずかに透過させることで、その後ろにある3Dモデルを確認できます。

Flexibleで説明文を囲むと、画面幅が狭い場合に文字が利用可能な範囲へ収まります。

9.6 IgnorePointerでモデル操作を妨げないようにする

操作案内が3Dビューアーの上にあると、その部分で行ったタッチ操作を案内Widgetが受け取ってしまう可能性があります。

そこで、操作案内をIgnorePointerで囲みます。

const IgnorePointer(
  child: DecoratedBox(
    decoration: BoxDecoration(
      color: Color(0xD9151515),
      borderRadius: BorderRadius.all(
        Radius.circular(16),
      ),
    ),
    child: Padding(
      padding: EdgeInsets.all(12),
      child: Text(
        '1本指で回転・2本指で拡大縮小',
      ),
    ),
  ),
)

IgnorePointerの内側にあるWidgetは、タッチ入力を受け取りません。そのため、案内が表示されている場所でも、背後の3Dビューアーを操作できます。

案内を見せながら操作性を維持するために重要な設定です。

9.7 この章で完成するコード

model_viewer_screen.dartを次の内容へ更新します。

import 'package:flutter/material.dart';
import 'package:flutter_3d_controller/flutter_3d_controller.dart';
import 'package:flutter_3d_model_viewer/config/model_source.dart';

/// リモートURLの3Dモデルを表示し、指操作を受け付ける画面です。
class ModelViewerScreen extends StatefulWidget {
  const ModelViewerScreen({super.key});

  /// 画面の状態オブジェクトを生成します。
  ///
  /// 入力: なし
  /// 出力: [ModelViewerScreen]の状態
  @override
  State<ModelViewerScreen> createState() => _ModelViewerScreenState();
}

class _ModelViewerScreenState extends State<ModelViewerScreen> {
  final Flutter3DController _controller = Flutter3DController();

  /// 画面全体のUIを構築します。
  ///
  /// 入力: [BuildContext]
  /// 出力: 3Dモデルと操作案内を含む[Widget]
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.fromLTRB(16, 14, 16, 16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    '3D MODEL',
                    style: TextStyle(
                      fontSize: 12,
                      fontWeight: FontWeight.w700,
                      letterSpacing: 1.8,
                    ),
                  ),
                  SizedBox(height: 3),
                  Text(
                    ModelSource.displayName,
                    style: TextStyle(
                      fontSize: 24,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 14),
              Expanded(
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(28),
                  child: ColoredBox(
                    color: const Color(0xFFE8E8E5),
                    child: Stack(
                      fit: StackFit.expand,
                      children: [
                        Flutter3DViewer(
                          controller: _controller,
                          src: ModelSource.modelUrl,
                          enableTouch: true,
                          activeGestureInterceptor: true,
                          progressBarColor: const Color(0xFF151515),
                        ),
                        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,
                                        ),
                                      ),
                                    ),
                                  ],
                                ),
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
              const SizedBox(height: 10),
              const Center(
                child: Text(
                  ModelSource.attribution,
                  style: TextStyle(
                    color: Color(0xFF777777),
                    fontSize: 11,
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

アプリを起動し、次の点を確認してください。

  • 3D表示領域の四隅が丸くなっている
  • 表示領域に薄いグレーの背景がある
  • 画面下部に操作案内が重なっている
  • 操作案内の上からでもモデルを回転できる
  • 文字が画面幅からはみ出していない

9.8 この章の到達目標

この章の終了時点で、次の内容を説明できれば完了です。

  • ClipRRectで子Widgetを角丸に切り抜ける
  • ColoredBoxで背景色を設定できる
  • Stackで複数のWidgetを重ねられる
  • Positionedで重ねたWidgetの位置を指定できる
  • IgnorePointerで背後のWidgetへタッチ操作を通せる
  • 操作方法が伝わる3D表示領域を作成できる 次章では、StatefulWidgetの仕組みを詳しく確認し、3Dモデルの読み込み状態を画面内で管理する準備を進めます。

https://app.notion.com/p/StatefulWidget-39fb4ab5d13680199d67e8ebd211f8c1?v=2c7b4ab5d136802d9a64000c46ee24e8&source=copy_link

FAQ

よくある質問

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