flutter-build-responsive-layout について
デバイスの種類ではなく、アプリウィンドウの利用可能な空間に合わせる Flutter のアダプティブレイアウト設計スキルです。制約、大画面での幅調整、入力方法を扱います。
主な機能
- 01ウィンドウ空間に応じたレイアウト
- 02LayoutBuilder による幅判定
- 03大画面でのコンテンツ幅制限
- 04長いリストの遅延描画
- 05マウス、キーボード、タッチへの対応
活用シーン
- 01サイズ変更可能なウィンドウ向け UI 設計
- 02画面サイズに応じたレイアウト切り替え
- 03大画面でのコンテンツの横伸び防止
- 04大規模リストの描画最適化
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ flutter/ agent-plugins --skill flutter-build-responsive-layoutインストール後、Claude Code で/flutter-build-responsive-layoutを入力して読み込まれたか確認します。
詳細
アダプティブレイアウトの実装
目次
スペースの測定に関するガイドライン
レイアウトが物理デバイスだけでなく、アプリウィンドウに合わせて調整されるよう、利用可能なスペースを正確に把握してください。
MediaQuery.sizeOf(context)を使用すると、アプリウィンドウ全体のサイズを取得できます。LayoutBuilderを使用すると、親ウィジェットに割り当てられたスペースに基づいてレイアウトを決定できます。constraints.maxWidthを評価し、返すウィジェットツリーを決定してください。- ウィジェットツリーの上位では、レイアウト切り替えに
MediaQuery.orientationOfやOrientationBuilderを使用しないでください。デバイスの向きは、利用可能なアプリウィンドウのスペースを正確に反映しません。 - ハードウェアの種類(「スマートフォン」か「タブレット」かなど)を判定しないでください。Flutter アプリは、サイズ変更可能なウィンドウ、マルチウィンドウモード、ピクチャーインピクチャーで動作します。レイアウトの決定はすべて、利用可能なウィンドウのスペースだけに基づいて行ってください。
ウィジェットのサイズと制約
Flutter の基本的なレイアウトルールを理解し、適用してください。制約は下へ渡り、サイズは上へ伝わります。位置は親が設定します。
- スペースを配分する:
Row、Column、Flexウィジェット内でExpandedとFlexibleを使用します。Expandedを使用すると、子ウィジェットを残りの利用可能なスペースいっぱいに広げられます(fit: FlexFit.tightおよびflex係数が 1.0 のFlexibleと同等です)。Flexibleを使用すると、子ウィジェットのサイズを指定した上限まで許容しながら、拡大・縮小できます。兄弟ウィジェット間でスペースを消費する比率は、flex係数で指定します。
- 幅を制限する: 大画面でウィジェットが水平方向のスペースをすべて占有しないようにします。
GridViewやListViewなどのウィジェットをConstrainedBoxまたはContainerでラップし、BoxConstraintsでmaxWidthを指定してください。 - 遅延レンダリング: アイテム数が不明または多いリストをレンダリングする場合は、必ず
ListView.builderまたはGridView.builderを使用してください。
デバイスと向きの動作
あらゆるデバイスのフォームファクターや入力方法で、アプリが正しく動作するようにしてください。
- 画面の向きを固定しないでください。 向きを固定すると、折りたたみ式デバイスで深刻なレイアウトの問題が発生し、黒い枠で囲まれたアプリが中央に表示されるレターボックス表示になることがよくあります。Android の大画面デバイスでは、縦向きと横向きの両方への対応が必要です。
- 向きを固定する場合の代替策: 業務要件で向きの固定が厳密に必須の場合は、
MediaQueryではなくDisplay APIを使用して物理画面の寸法を取得してください。互換モードでは、MediaQueryはより大きなウィンドウサイズを取得できません。 - 複数の入力方法に対応する: 基本的なマウス、トラックパッド、キーボードショートカットに対応してください。タッチターゲットを適切なサイズにし、キーボード操作にも対応してください。
ワークフロー:アダプティブレイアウトの構築
このワークフローに沿って、利用可能な BoxConstraints に合わせて調整されるレイアウトを実装してください。
タスクの進捗:
- アダプティブな動作が必要な対象ウィジェットを特定する。
- ウィジェットツリーを
LayoutBuilderでラップする。 - ビルダーコールバックから
constraints.maxWidthを取得する。 - アダプティブなブレークポイントを定義する(例:
largeScreenMinWidth = 600)。 -
maxWidth > largeScreenMinWidthの場合: 大画面向けのレイアウトを返す(例:ナビゲーションサイドバーとコンテンツ領域を横並びに配置するRow)。 -
maxWidth <= largeScreenMinWidthの場合: 小画面向けのレイアウトを返す(例:Columnまたは標準的なナビゲーション形式のレイアウト)。 - バリデーターを実行 -> アプリケーションウィンドウのサイズを変更 -> レイアウトの切り替わりを確認 -> オーバーフローエラーを修正する。
ワークフロー:大画面向けの最適化
大きなディスプレイで UI 要素が不自然に横に広がらないよう、次のワークフローに従ってください。
タスクの進捗:
- 全幅のコンポーネント(例:
ListView、テキストブロック、フォーム)を特定します。 - リストを最適化する場合:
ListView.builderをSliverGridDelegateWithMaxCrossAxisExtentを使ったGridView.builderに変換し、ウィンドウサイズに応じて列数が自動調整されるようにします。 - フォームまたはテキストブロックを最適化する場合: コンポーネントを
ConstrainedBoxでラップします。 -
BoxConstraints(maxWidth: [optimal_width])をConstrainedBoxに適用します。 -
ConstrainedBoxをCenterウィジェットでラップし、大画面で制約されたコンテンツが中央に配置されるようにします。 - バリデーターを実行 -> デスクトップ/タブレットのターゲットでテスト -> 横方向の伸びを確認 ->
maxWidthまたはグリッドの範囲を調整します。
例
LayoutBuilder を使ったアダプティブレイアウト
利用可能な幅に応じてモバイル向けレイアウトとデスクトップ向けレイアウトを切り替える方法を示します。
import 'package:flutter/material.dart';
const double largeScreenMinWidth = 600.0;
class AdaptiveLayout extends StatelessWidget {
const AdaptiveLayout({super.key});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > largeScreenMinWidth) {
return _buildLargeScreenLayout();
} else {
return _buildSmallScreenLayout();
}
},
);
}
Widget _buildLargeScreenLayout() {
return Row(
children: [
const SizedBox(width: 250, child: Placeholder(color: Colors.blue)),
const VerticalDivider(width: 1),
Expanded(child: const Placeholder(color: Colors.green)),
],
);
}
Widget _buildSmallScreenLayout() {
return const Placeholder(color: Colors.green);
}
}
大画面での幅の制限
ウィジェットが水平方向のスペースをすべて使わないようにする方法を示します。
import 'package:flutter/material.dart';
class ConstrainedContent extends StatelessWidget {
const ConstrainedContent({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 800.0, // Maximum width for readability
),
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item $index'),
);
},
),
),
),
);
}
}
コメント
Markdown 対応