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モデルの読み込み状態を画面内で管理する準備を進めます。