Flutter Responsive & Adaptive UI: フォルダブル、タブレット、デスクトップを単一コードベースで完全対応

Flutterの最大の強みの1つは、単一のコードベースで複数のプラットフォームをサポートできることです。しかし、「動く」ということと、「各デバイスに合わせて素晴らしいユーザーエクスペリエンスを提供する」ということは全く別の話です。スマートフォン、フォルダブルデバイス、タブレット、そして広いデスクトップモニターまで、多様なフォームファクタで完璧に動作するレスポンシブ(Responsive)およびアダプティブ(Adaptive)UIを構築する方法を見ていきましょう。
レスポンシブ (Responsive) vs アダプティブ (Adaptive)
- レスポンシブ: 画面のサイズや向きが変わったときに、レイアウトが流動的に調整されます。(例:
MediaQuery、LayoutBuilder、Flex) - アダプティブ: デバイスの種類(モバイル、デスクトップ、フォルダブルなど)やOS(iOS、Android、Windows)に合わせて、UIコンポーネントやインタラクションの方式が完全に変わります。(例:タッチ vs マウス、スワイプ vs ショートカットキー)
1. レスポンシブレイアウトの基礎: LayoutBuilder
画面サイズに応じて単にウィジェットを引き伸ばすのではなく、画面のスペースをどのように活用するかを決定する必要があります。LayoutBuilderは、親ウィジェットのサイズ制約に基づいてUIを異なるように描画することを可能にします。
class ResponsiveScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 1024) {
// デスクトップレイアウト
return DesktopLayout();
} else if (constraints.maxWidth >= 600) {
// タブレットレイアウト
return TabletLayout();
} else {
// モバイルレイアウト
return MobileLayout();
}
},
);
}
}
このアプローチを使用すると、広い画面ではサイドバー(NavigationRailまたはDrawer)を表示し、狭い画面では下部のタブバー(BottomNavigationBar)を表示するように構造を変更できます。
2. フォルダブルデバイスの対応: ヒンジを避ける
フォルダブルデバイスには、画面の中央にヒンジ(蝶番)が存在する場合があります。画面を半分に折りたたんだ「フレックスモード」などの状態を処理するには、MediaQuery.displayFeaturesを活用する必要があります。
Widget build(BuildContext context) {
final displayFeatures = MediaQuery.of(context).displayFeatures;
final hinge = displayFeatures.firstWhereOrNull(
(feature) => feature.type == DisplayFeatureType.hinge ||
feature.type == DisplayFeatureType.fold,
);
if (hinge != null) {
// ヒンジを基準に画面を分割してレンダリング
return FlexLayoutWithHinge(hingeBounds: hinge.bounds);
}
// 通常のディスプレイ
return NormalLayout();
}
Googleが提供するdual_screenパッケージやTwoPaneウィジェットを使用すると、フォルダブルデバイスでリスト・詳細構造を簡単に実装できます。
3. アダプティブUI: プラットフォームごとの最適化体験の提供
デスクトップ環境では、タッチスクリーンではなくマウスとキーボードを使用します。したがって、マウスのホバー(Hover)効果とショートカットキーのサポートが不可欠です。
マウスホバー効果
マウスを乗せたときのフィードバックを提供するために、FocusableActionDetectorやシンプルなMouseRegionを使用できます。
MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) => setState(() => _isHovering = true),
onExit: (_) => setState(() => _isHovering = false),
child: Container(
color: _isHovering ? Colors.blue.shade100 : Colors.white,
child: Text('ホバーしてみてください'),
),
)
フォームファクタに合わせたスクロール動作
デスクトップでは、マウスホイールによるスクロールが自然でなければなりません。最近のFlutterではデフォルトでサポートされていますが、カスタムのスクロール動作が必要な場合は、ScrollBehaviorをオーバーライドできます。
まとめ
完璧なクロスプラットフォームアプリを作成するには、単なる画面の引き伸ばしを超えた考慮が必要です。
LayoutBuilderによるブレークポイントベースのレイアウト分岐MediaQuery.displayFeaturesによるフォルダブルフォームファクタの対応- マウス、キーボード入力、OSごとのUIスタイルを考慮したアダプティブコンポーネントの設計
これらのテクニックを適用して、すべてのデバイスでネイティブアプリのように感じられる素晴らしいFlutterアプリを開発してください!